Ponteiros: Voltando ao básico…

Tenho alguns colegas que insistem em dizer que não “gostam” de C por causa dos ponteiros: “Declarar ponteiros é uma coisa muito complicada e chata”, dizem. Esse post é uma tentativa de esclarecer, de vez, as dúvidas sobre essas declarações.

Um ponteiro é uma variável que contém um endereço. Isso, com certeza, você já entendeu há muito tempo. O problema é que, quando queremos declarar um ponteiro, podemos fazê-lo de forma não muito intuitiva. O primeiro exemplo é um dos parâmetros da função main:

int main(int argc, char *argv[]);

Essa é a declaração canônica da função main em qualquer programa escrito em C ou C++. O parâmetro argc é um inteiro contendo a contagem de parâmetros vindos da linha de comando e é sempre maior ou igual a 1 — porque o primeiro parâmetro é sempre o nome do executável que está sendo chamado!

O parâmetro argv é um array de ponteiros. E aqui temos o primeiro problema: Um array de ponteiros é o armazenamento, em sequência, de “argc” ponteiros. Cada um desses ponteiros, no array, aponta para uma sequência de chars, na memória. Se chamarmos um programinha test passando o parâmetro 10, teremos algo assim:

argv aponta para um vetor de ponteiros.

Como você pode ver, o vetor argv possui um item adicional (um ponteiro para NULL).

Ponteiros e arrays tem uma relação bem direta. Quando você declara um array o nome da variável é um ponteiro para o primeiro item do array. Basicamente o que você faz é dizer ao compilador que ele deve alocar memória suficiente para conter todos os itens do array e colocar o ponteiro do primeiro item na variável nomeada:

char s[10];  // O compilador aloca 10 chars e faz 's' apontar
             // para o primeiro item.

É por isso que o compilador permite o atalho de assinalamento via ponteiros:

char *s = "Fred";   // O compilador aloca 5 chars (incluindo o (char)0 final)
                    // e coloca o ponteiro em 's'.
char s[] = "Fred";  // Faz a mesma coisa!

Assim, todo array é, na verdade, um ponteiro. Mas as declarações acima têm uma pequena diferença: No primeiro caso, o compilador coloca a string literal “Fred” em alguma região da memória determinada por ele e depois atribui o ponteiro a ‘s’. No segundo caso, a região da memória é o array ‘s’. É uma diferença sutil, mas é suficiente para causar erros de compilação como os descritos neste artigo.

O motivo pelo qual o compilador chia é que arrays deveriam ter tamanho fixo. No exemplo acima, no segundo caso, o array apontado por ‘s’ jamais terá mais que 5 itens porque ele foi definido assim. Em contrapartida, ponteiros podem apontar para qualquer coisa. O compilador considera enão um erro passar ponteiros definidos como arrays para funções que esperam ponteiros “puros” — na minha humilde opinião isso é uma limitação boboca, que é facilmente cincundada com o macete que mostrei.

Existem algumas complicações:

char *argv[];   // 'argv' é um array de ponteiros para char.
char **argv;    // 'argv' é um ponteiro para ponteiros de char.

Ambas as declarações são intercambiáveis porque significam a mesma coisa. Lembre-se que um array é um ponteiro também. Mas a declaração abaixo tem um sentido diferente:

char (*p)[];    // 'p' é um ponteiro para um array de chars.

Felizmente essa construção não é muito frequente em códigos que eu já tenha visto ou feito. É a mesma coisa que escrever:

typedef char array[];
array *p;

Isso dá um nó na cabeça, huh? Sabe o que eu faço? Evito declarar ponteiros para arrays!

Outra confusão é na declaração de ponteiros retornados por funções e ponteiros para funções:

int *f(void);    // função retorna um ponteiro para int.
int (*f)(void);  // Este é um ponteiro para uma função que retorna um int.
int *(*f)(void); // E este é o pointero para uma função que retorna
                 // um ponteiro para int.

Eu evito declara ponteiros para arrays porque poderíamos acabar com uma declaração assim, algum dia:

/* Protótipo de um ponteiro para função que toma um ponteiro para um array de
   insts e retorna um ponteiro para um array de inst. */
int (*)[](*f)(int (*)[]);

Puuts! Complicação doida, né? Isso pode ser resumido com o uso do typedef que criamos acima:

array *(*f)(array *);

É possível criar declarações ainda mais malucas com ponteiros, mas eu tento me manter atento a algumas regras básicas:  Se preciso de um ponteiro para uma sequência de tipos (um array), uso a declaração abaixo (que não é lá muito “correta”):

char *s = "Fred";  // 's' é um ponteiro

char **p = &s;     // 'p' é um ponteiro para um ponteiro,
                    // mas não é um ponteiro para um ARRAY!
                    // É apenas uma questão de semântica!
                    // No entanto, se esse "array" de ponteiros só
                    // tiver 1 ponteiro, então me serve bem!

/*
char (*p)[] = &s; // ESSA deveria ser a declaração correta se
                  // 's' fosse definida como 'char s[]'.
                  // Mas, como vimos, dá muito trabalho!
*/

Reparem que declarar vetores como ponteiros é mais fácil (pelo menos, EU acho!)!  Mude a declaração de ‘s’ para char s[] e tente descobrir como declarar ‘p’ para acomodar o endereço de ‘s’, com ‘p’ definido como ‘char **p‘, proce ver o que é bom pra tosse!

Anúncios

Um comentário sobre “Ponteiros: Voltando ao básico…

  1. Ponteiros, sempre.
    Mas os typedef, hummm!
    Penso o seguinte, se tenho um string[256], depois preciso de um de 8 outro de 17, a coisa vai acumulando e acabamos com uma paleta type1x type39y e por ai vai.
    Concordo e acho recomendável o uso do do typedef, mas conter a proliferação, pelo menos para mim, é mandatório.
    Sem entrar nos meandros, pois é material farto para flame, o melhor é fazer o código de modo que você e outros entendam, daí a minha avareza com os typedefs. Já discutimos casos similares quanto aos #defines x enums… A questãos se resolve com estilo, praticidade e conforto.

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