Pergunta

Esta questão vai para os gurus C lá fora:

C, é possível para declarar um ponteiro como se segue:

char (* p)[10];

.. o qual, basicamente, afirma que este ponteiro pontos a uma matriz de 10 caracteres. A coisa interessante sobre declarando um ponteiro como este é que você vai obter um erro de tempo de compilação se você tentar atribuir um ponteiro de uma matriz de tamanho diferente para p. Ele também lhe dará um erro de tempo de compilação se você tentar atribuir o valor de um ponteiro de char simples p. Eu tentei isso com gcc e parece trabalhar com ANSI, C89 e C99.

Parece-me como declarar um ponteiro como este seria muito útil - especialmente, ao passar um ponteiro para uma função. Normalmente, as pessoas iria escrever o protótipo de tal função um como este:

void foo(char * p, int plen);

Se você estava esperando um buffer de um tamanho específico, você poderia simplesmente testar o valor de plen. No entanto, você não pode ser garantido que a pessoa que passa p a você vai realmente dar-lhe plen posições de memória válidos nesse buffer. Você tem que confiar que a pessoa que chamou esta função é fazer a coisa certa. Por outro lado:

void foo(char (*p)[10]);

.. forçaria o chamador para dar-lhe um buffer do tamanho especificado.

Isto parece muito útil, mas eu nunca vi um ponteiro declarado como este em qualquer código que eu já corri em toda.

A minha pergunta é: Existe alguma razão para que as pessoas não declarar ponteiros como este? Que não estou vendo alguma armadilha óbvia?

Foi útil?

Solução 2

Eu gostaria de adicionar à resposta de AndreyT (em ninguém tropeça de caso sobre esta página procurando mais informações sobre esse tópico):

Como eu começar a jogar mais com estas declarações, eu percebo que há grande desvantagem associada com eles em C (aparentemente não em C ++). É bastante comum ter uma situação onde você gostaria de dar um chamador um const Ponteiro para uma reserva que você tenha escrito em. Infelizmente, isso não é possível quando declarar um ponteiro como este em C. Em outras palavras, o padrão C (6.7.3 - n.º 8) está em desacordo com algo parecido com isto:


   int array[9];

   const int (* p2)[9] = &array;  /* Not legal unless array is const as well */

Esta restrição não parece estar presente em C ++, tornando este tipo de declarações muito mais útil. Mas no caso de C, é necessário recorrer a uma declaração ponteiro regular, sempre que quiser um ponteiro const para o buffer de tamanho fixo (a menos que o const tampão em si foi declarado para começar). Você pode encontrar mais informações neste segmento mail: texto do link

Esta é uma limitação grave na minha opinião e que poderia ser uma das principais razões pelas quais as pessoas não costumam declarar ponteiros como este em C. O outro é o fato de que a maioria das pessoas nem sequer sabem que você pode declarar um ponteiro assim como AndreyT assinalou.

Outras dicas

O que você está dizendo em seu post é absolutamente correto. Eu diria que todo desenvolvedor C trata de exatamente a mesma descoberta e exatamente à mesma conclusão quando (se) eles atingem certo nível de proficiência com a linguagem C.

Quando as especificidades de sua chamada área de aplicação para uma matriz de tamanho específico fixo (tamanho da matriz é uma constante em tempo de compilação), a única maneira correta de passar uma matriz como a uma função é usando uma matriz ponteiro-to- parâmetro

void foo(char (*p)[10]);

(em linguagem C ++ isso é feito também com referências

void foo(char (&p)[10]);

).

Isto irá permitir a verificação de tipo de nível de linguagem, que irá certificar-se que a matriz de tamanho exatamente correto é fornecido como um argumento. Na verdade, em muitos casos, as pessoas usam esta técnica implicitamente, mesmo sem perceber, escondendo o tipo de matriz atrás de um nome de typedef

typedef int Vector3d[3];

void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);

nota, adicionalmente, que o código de cima é invariante com relação ao tipo Vector3d ser uma matriz ou um struct. Você pode alternar a definição de Vector3d a qualquer momento a partir de uma matriz para uma struct e para trás, e você não terá que mudar a declaração da função. Em ambos os casos as funções receberá um objeto agregado "por referência" (existem exceções para isso, mas dentro do contexto desta discussão isso é verdade).

No entanto, você não verá esse método de disposição passando usado explicitamente, muitas vezes, simplesmente porque muitas pessoas ficam confusas com uma sintaxe bastante complicadas e simplesmente não são suficientes confortável com tais características da linguagem C para usá-los corretamente. Por esta razão, na vida real médio, passando uma matriz como um ponteiro para seu primeiro elemento é uma abordagem mais popular. Ela só olha "mais simples".

Mas, na realidade, usando o ponteiro para o primeiro elemento para passar matriz é uma técnica de nicho muito, um truque, que serve um propósito muito específico: a sua única finalidade é facilitar passando matrizes de tamanho diferente (tamanho ie de tempo de execução). Se você realmente precisa para ser capaz de matrizes de processo de tamanho em tempo de execução, em seguida, a maneira correta de passar uma tal variedade é por um ponteiro para seu primeiro elemento com o tamanho concreto fornecido por um parâmetro adicional

void foo(char p[], unsigned plen);

Na verdade, em muitos casos, é muito útil para ser capaz de processar matrizes de tamanho em tempo de execução, o que também contribui para a popularidade do método. Muitos desenvolvedores de C simplesmente nunca encontro (ou nunca reconhecer) a necessidade de processar uma matriz de tamanho fixo, mantendo-se assim alheio à técnica de tamanho fixo adequada.

No entanto, se o tamanho da matriz é fixo, passando-o como um apontador para um elemento

void foo(char p[])

é um grande erro de nível de técnica, que, infelizmente, é bastante difundido nos dias de hoje. Uma técnica ponteiro-para-matriz é uma abordagem muito melhor em tais casos.

Outra razão que pode dificultar a adoção da técnica de matriz de passagem de tamanho fixo é o domínio de abordagem ingênua para tipagem de matrizes alocadas dinamicamente. Por exemplo, se as chamadas de programas para matrizes fixos de tipo char[10] (como no seu exemplo), um desenvolvedor médio vai malloc tais matrizes como

char *p = malloc(10 * sizeof *p);

Esta matriz não pode ser passado para uma função declarada como

void foo(char (*p)[10]);

que confunde o desenvolvedor médio e torna-los abandonar a declaração de parâmetro de tamanho fixo sem dar-lhe um novo pensamento. Na realidade, porém, a raiz das mentiras problema na abordagem malloc ingênuo. O formato malloc mostrado acima deve ser reservada para matrizes de tamanho de tempo de execução. Se o tipo de matriz tem tamanho em tempo de compilação, a melhor maneira de malloc ele ficaria da seguinte forma

char (*p)[10] = malloc(sizeof *p);

Isto, naturalmente, pode ser facilmente transmitida ao acima foo declarou

foo(p);

eo compilador irá executar a verificação de tipo adequado. Mas, novamente, este é excessivamente confucantar para um desenvolvedor C despreparados, e é por isso que você não vai vê-lo em muitas vezes no código de todos os dias "típico" média.

A razão óbvia é que este código não compila:

extern void foo(char (*p)[10]);
void bar() {
  char p[10];
  foo(p);
}

A promoção padrão de uma matriz é um ponteiro não qualificado.

Veja também esta questão , usando foo(&p) deve funcionar.

Bem, simplesmente, C não fazer as coisas dessa forma. Uma matriz do tipo T é passado em torno de como um ponteiro para o primeiro T na matriz, e isso é tudo que você começa.

Isso permite que para alguns frescos e elegantes algoritmos, como loop através da matriz com expressões como

*dst++ = *src++

A desvantagem é que a gestão do tamanho é com você. Infelizmente, a incapacidade de fazer isso conscientemente também levou a milhões de erros em C codificação e / ou oportunidades para a exploração malévola.

O que vem perto do que você pedir em C é passar em torno de um struct (por valor) ou um ponteiro para um (por referência). Enquanto o mesmo tipo struct é usado em ambos os lados desta operação, tanto o código que mão para fora da referência eo código que usa-lo estão de acordo sobre o tamanho dos dados a serem manipulados.

Seu struct pode conter todos os dados que você quer; poderia conter sua matriz de um tamanho bem definido.

Ainda assim, nada impede que você ou um incompetente ou codificador malévolo de usar moldes para enganar o compilador para o tratamento de sua estrutura como um de um tamanho diferente. A capacidade quase Unshackled para fazer esse tipo de coisa é uma parte do projeto de C.

Você pode declarar um array de caracteres de várias maneiras:

char p[10];
char* p = (char*)malloc(10 * sizeof(char));

O protótipo para uma função que recebe um array por valor é:

void foo(char* p); //cannot modify p

ou por referência:

void foo(char** p); //can modify p, derefernce by *p[0] = 'f';

ou pela sintaxe de array:

void foo(char p[]); //same as char*

Eu não recomendaria essa solução

typedef int Vector3d[3];

desde que obscurece o fato de que Vector3D tem um tipo que você deve saber sobre. Os programadores geralmente esperam não faça variáveis ??do mesmo tipo de ter tamanhos diferentes. Considere o seguinte:

void foo(Vector3d a) {
   Vector3D b;
}

onde sizeof um! = Sizeof b

Eu também quero usar essa sintaxe para permitir mais a verificação de tipo.

Mas eu também concorda que a sintaxe e modelo mental de usando ponteiros é mais simples e mais fácil de lembrar.

Aqui estão mais alguns obstáculos que se deparam.

  • Como acessar a matriz requer usando (*p)[]:

    void foo(char (*p)[10])
    {
        char c = (*p)[3];
        (*p)[0] = 1;
    }
    

    É tentador usar um char ponteiro-para-local em vez disso:

    void foo(char (*p)[10])
    {
        char *cp = (char *)p;
        char c = cp[3];
        cp[0] = 1;
    }
    

    Mas isso seria parcialmente derrotar o propósito de usar o tipo correto.

  • Um tem que lembre-se de usar o endereço-de operador ao atribuir o endereço de uma matriz para um ponteiro-para-matriz:

    char a[10];
    char (*p)[10] = &a;
    

    O endereço-de operador obtém o endereço de toda a matriz na &a, com o tipo correto atribuí-la a p. Sem que o operador, a é automaticamente convertido para o endereço do primeiro elemento da matriz, mesmo que em &a[0], que tem um tipo diferente.

    Uma vez que esta conversão automática já está acontecendo, estou sempre intrigado que o & é necessário. É consistente com o uso de & em variáveis ??de outros tipos, mas eu tenho que lembrar que uma matriz é especial e que eu preciso do & para obter o tipo correto de endereço, mesmo que o valor do endereço é o mesmo.

    Uma das razões para o meu problema pode ser que eu aprendi K & R C de volta na década de 80, que não permitem o uso do operador & em matrizes inteiras ainda (embora alguns compiladores ignorado isso ou tolerada a sintaxe). Que, por sinal, pode ser outra razão pela qual ponteiros-to-matrizes têm dificuldade para se adotada:. Eles só funcionam corretamente desde ANSI C, ea limitação operador & pode ter sido outra razão para considerá-los muito estranho

  • Quando typedef é não usado para criar um tipo para a matriz ponteiro-to-(em um arquivo de cabeçalho comum), então uma matriz ponteiro ao global precisa de uma extern mais complicado declaração de compartilhá-lo através de arquivos:

    fileA:
    char (*p)[10];
    
    fileB:
    extern char (*p)[10];
    

Talvez eu estou faltando alguma coisa, mas ... já que matrizes são ponteiros constantes, basicamente, o que significa que não há nenhum ponto de passagem em torno de ponteiros para eles.

Você não pode apenas usar void foo(char p[10], int plen);?

No meu compilador (vs2008) trata char (*p)[10] como uma matriz de ponteiros de caracteres, como se não houvesse nenhum parênteses, mesmo que eu compilar como um arquivo C. É o suporte de compilador para este "variável"? Se assim for, que é um dos principais motivos para não usá-lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top