Dica: Arrays e ponteiros

Não sei quanto a você, mas eu já topei com alguns castings que encheram a paciência. Vejam: Eu tenho a mania de criar typedefs para todo tipo complexo (estruturas e arrays, por exemplo), para lidar com eles como um tipo simples. Para isso temos que levar algumas coisas em consideração…

Sempre que você faz uma declaração assim:

float vector[4];

Você está declarando um ponteiro chamado vector que aponta para uma sequência de 4 floats. Com a minha mania a coisa fica mais ou menos assim:

typedef float vector4_t[4];

vector4_t vector;

Que é a mesma coisa! Acabamos de declarar um ponteiro vector que aponta para um item do tipo vector4_t. Sei que é um ponteiro porque o tipo é um array de 4 floats, igualzinho ao primeiro exemplo. Agora vejam isso:

typedef float vector4_t[4];
typedef float plane_t[4];
typedef plane_t frustum_t[6];

extern int PlaneDistance(plane_t, vector4_t, float);

int FrustumSphereTest(frustum_t fr,
                      vector4_t center,
                      float radius)
{
  int i;

  for (i = 0; i < 6; i++)
    if (PlaneDistance((plane_t)fr[i], center, radius) <= 0)
      return 0;
  return 1;
}

Este fragmento de código causa um erro de compilação, justamente na linha onde PlaneDistance() é chamada. “error: cast specifies array type”. Que coisa doida! O tipo frustum_t é justamente definido como sendo um array de plane_t‘s e a função toma um tipo  plane_t como parâmetro!

Well… coisas do compilador… mas eu vou mostrar um macete (meio perigoso) que sempre funciona… É, em essência, o mesmo macete usado neste artigo: Se substituirmos a linha do if que contém a chamada para PlaneDistance() por isso:

if (PlaneDistance(*&fr[i], center, radius) <= 0)

O erro desaparece! Tudo o que fiz foi obter um ponteiro e depois derrefernciá-lo. Veja que fr[i] é do tipo plane_t, mas é definido como um array de 4 floats. Se eu pego o endereço do item do array e depois pego o dado apontado, é como se eu estivesse passando fr[i] sem incomodar o compilador!! O macete de pegar o ponteiro e depois derreferenciá-lo serve para enganar o esquema de promoção de tipos de C.

Particularmente, acho o sistema de casting de C bem melhor do que o implementado em C++. Este último tem 3 tipos diferentes de casting (pelo menos): static, dynamic e const. A sintaxe também não me agrada muito… tem gente que gosta:

int *x = 10;
float *y;

y = static_cast<float *>(x);  /* Estilo C++ */
y = (float *)x;               /* Estilo C */

Eis um crash course nos tipos de casting de C++:

  • static_cast é a mesma coisa do estilo de casting do C;
  • dynamic_cast é um casting que pode devolver NULL se a promoção de tipo for inválida;
  • const_cast é uma violação das regras do modificador const. Transforma um tipo const num tipo não-const.

O dynamic_cast, na minha opinião, é coisa para preguiçosos… A idéia é que você não poderia poder converter ponteiros entre duas classes que não têm classes base em comum. E o const casting, para mim, é idiota, já que, se um objeto é definido como const, então o foi por uma boa causa (isto é, o desenvolvedor não quer que ele seja modificado!).

Voltando ao “macete” que mostrei aqui. Tome cuidado quando for usá-lo com tipos diferentes, usando casting, por exemplo:

typedef float vector3_t[3];
typedef float vector4_t[4];
extern int Vector4BoxTest(plane_t, vector4_t);

plane_t p;
vector3_t x;

if (Vector3BoxTest(p, *(vector4_t *)&x) < 0)
  ...

Aqui a função espera por um tipo vector4_t, mas estamos passando uma variável do tipo vector3_t. Como a variável x tem um float a menos, é possível obter um Segmentation Fault na chamada (quando a rotina tentar acessar o quarto float). Pior ainda: Se tivessemos outra variável declarada depois do x, um int, por exemplo, é provável que o compiador gerasse código que acessasse o conteúdo desse int como se fosse o quarto float, sem causar nenhum erro de compilação!

O compilador não reclamará com o if acima, já que convertemos o ponteiro para vector3_t para um do tipo vector4_t, mas isso pode ser perigoso… Antes de usar esse macete, entenda o que você está fazendo, ok?

Anúncios

2 comentários sobre “Dica: Arrays e ponteiros

  1. Shame on me…
    Ontem mesmo me deparei com treco destes, tentando resolver um problema de modo rapido.
    Faço o seguinte – solução de contorno e altamente deselegante – declaro um ponteiro para o array e passo com casting void * !
    Resolve sempre, mas é perigoso!
    Dai quanto tenho tempo conserto, mas confesso que não gosto de ficar criando typedefs…

    1. Outra técnica que eu acho interessante, com relação aos typedefs é declarar também o tipo “ponteiro” para o tipo definido no typedef. A primeira vez que vi isso foi no header windows.h (pelo menos eles servem pra algo!):

      typedef struct _vector3_s {
        float x;
        float y;
        float z;
      } vector3_s, *vector3_p;

      Assim, se eu precisar de um ponteiro para a estrutura, uso o tipo vector3_p, se precisar do tipo da estrutura, uso vector3_s e, se precisar declarar a estrutura ao estilo de C, no caso, por exemplo, de ter uma lista encadeada, uso struct _vector3_s.

      Esse estilo de nomenclatura para tipos não é o usado pela Microsoft, que prefere adotar símbolos em maiúsculos… Essa nomenclatura eu vi em códigos feitos para *nix. Adotei-a… coloco sufixos _s para estruturas, _t para “tipos”, _p para “ponteiros”, _e para enums, _u para unions.

      É claro que tenho a tendência a usar apenas _t e _p (porque o nome do tipo me dá uma dica do seu conteúdo), mas para projetos mais complicados, onde ficar rastreando os tipos é uma complicação, uso o que falei acima.

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s