Como eu entendo declarações complicadas de function?

Como entendo depois de declarações complicadas?

char (*(*f())[])(); char (*(*X[3])())[5]; void (*f)(int,void (*)()); char far *far *ptr; typedef void (*pfun)(int,float); int **(*f)(int**,int**(*)(int **,int **)); 

Como outros apontaram, o cdecl é a ferramenta certa para o trabalho.

Se você quiser entender esse tipo de declaração sem a ajuda do cdecl, tente ler de dentro para fora e da direita para a esquerda

Tomando um exemplo random de sua lista char (*(*X[3])())[5];
Comece pelo X, que é o identificador sendo declarado / definido (e o identificador mais interno):

 char (*(*X[3])())[5]; ^ 

X é

 X[3] ^^^ 

X é uma matriz de 3

 (*X[3]) ^ /* the parenthesis group the sub-expression */ 

X é uma matriz de 3 pointers para

 (*X[3])() ^^ 

X é uma matriz de 3 pointers para funcionar aceitando um número não especificado (mas fixo) de argumentos

 (*(*X[3])()) ^ /* more grouping parenthesis */ 

X é uma matriz de 3 pointers para funcionar aceitando um número não especificado (mas fixo) de argumentos e retornando um ponteiro

 (*(*X[3])())[5] ^^^ 

X é uma matriz de 3 pointers para funcionar aceitando um número não especificado (mas fixo) de argumentos e retornando um ponteiro para uma matriz de 5

 char (*(*X[3])())[5]; ^^^^ ^ 

X é uma matriz de 3 pointers para funcionar aceitando um número não especificado (mas fixo) de argumentos e retornando um ponteiro para uma matriz de 5 caracteres .

Leia-o de dentro, semelhante a como você resolveria equações como {3+5*[2+3*(x+6*2)]}=0 – você começaria resolvendo o que está dentro de () então [] e finalmente {} :

 char (*(*x())[])() ^ 

Isso significa que x é alguma coisa .

 char (*(*x())[])() ^^ 

x é uma function .

 char (*(*x())[])() ^ 

x retorna um ponteiro para algo .

 char (*(*x())[])() ^ ^^^ 

x retorna um ponteiro para uma matriz .

 char (*(*x())[])() ^ 

x retorna um ponteiro para uma matriz de pointers .

 char (*(*x())[])() ^ ^^^ 

x retorna um ponteiro para uma matriz de pointers para funções

 char (*(*x())[])() ^^^^ 

Ou seja, o ponteiro de matriz retornado por x aponta para uma matriz de pointers de function que apontam para funções que retornam um caractere .

Mas sim, use o cdecl . Eu mesmo usei para verificar minha resposta :).

Se isso ainda estiver confundindo você (e provavelmente deveria), tente fazer a mesma coisa em um pedaço de papel ou em seu editor de texto favorito. Não há como saber o que isso significa apenas olhando para ele.

Soa como um trabalho para a ferramenta cdecl:

 cdecl> explain char (*(*f())[])(); declare f as function returning pointer to array of pointer to function returning char 

Procurei uma página oficial para a ferramenta, mas não consegui encontrar uma que parecesse genuína. No Linux, normalmente você pode esperar que sua distribuição de escolha inclua a ferramenta, então eu apenas a instalei para gerar a amostra acima.

Você deve estar usando a ferramenta cdecl . Deve estar disponível na maioria das distribuições Linux.

por exemplo, para esta function, ele retornará:

char (*(*f())[])(); – declara f como function retornando ponteiro para array de ponteiro para function retornando char

void (*f)(int,void (*)()); – protótipo do ponteiro de function f. f é uma function que aceita dois parâmetros, o primeiro é int e o segundo é um ponteiro de function para uma function que retorna void.

char far *far *ptr; – ptr é um ponteiro distante para um ponteiro distante (que aponta para algum caractere / byte).

char (*(*X[3])())[5]; – X é uma matriz de 3 pointers para funcionar aceitando um número indeterminado de argumentos e retornando um ponteiro para uma matriz de 5 caracteres.

typedef void (*pfun)(int,float); – declarando o ponteiro de function pfun. pfun é um fator que leva dois parâmetros, o primeiro é int, o segundo é do tipo float. a function não possui um valor de retorno;

por exemplo

 void f1(int a, float b) { //do something with these numbers }; 

Btw, declarações complicadas como a última não são vistas com freqüência. Aqui está um exemplo que acabei de inventar para esse propósito.

 int **(*f)(int**,int**(*)(int **,int **)); typedef int**(*fptr)(int **,int **); int** f0(int **a0, int **a1) { printf("Complicated declarations and meaningless example!\n"); return a0; } int ** f1(int ** a2, fptr afptr) { return afptr(a2, 0); } int main() { int a3 = 5; int * pa3 = &a3; f = f1; f(&pa3, f0); return 0; } 

Parece que a sua pergunta real é esta:

Qual é o caso de uso de um ponteiro para um ponteiro?

Um ponteiro para um ponteiro tende a aparecer quando você tem uma matriz de algum tipo T, e T em si é um ponteiro para outra coisa. Por exemplo,

  • O que é uma string em C? Normalmente, é um char * .
  • Você gostaria de um conjunto de strings de tempos em tempos? Certo.
  • Como você declararia um? char *x[10] : x é uma matriz de 10 pointers para char , também conhecidos como 10 strings.

Neste ponto, você pode estar se perguntando onde o char ** entra. Ele entra na imagem a partir do relacionamento muito próximo entre aritmética de pointers e arrays em C. Um nome de array, x é (quase) sempre convertido em um ponteiro para seu primeiro elemento .

  • Qual o primeiro elemento? Um char * .
  • O que é um ponteiro para o primeiro elemento? Um char ** .

Em C, o vetor E1[E2] é definido como equivalente a *(E1 + E2) . Normalmente, E1 é o nome da matriz, digamos x , que automaticamente convertido em char ** , e E2 é algum índice, digamos 3. (Essa regra também explica porque 3[x] e x[3] são a mesma coisa. )

Ponteiros para pointers também aparecem quando você deseja uma matriz alocada dinamicamente de algum tipo T , que é em si um ponteiro . Para começar, vamos fingir que não sabemos o tipo T.

  • Se quisermos um vetor de Ts dinamicamente alocado, de que tipo precisamos? T *vec .
  • Por quê? Como podemos executar a aritmética de pointers em C, qualquer T * pode servir como base de uma seqüência contígua de T ‘s na memory.
  • Como alocamos esse vetor, digamos, de n elementos? vec = malloc(n * sizeof(T)) ;

Esta história é verdadeira para qualquer tipo T , e é verdade para o char * .

  • Qual é o tipo de vec se T é char * ? char **vec .

Ponteiros para pointers também aparecem quando você tem uma function que precisa modificar um argumento do tipo T, ele próprio um ponteiro .

  • Observe a declaração de strtol : long strtol(char *s, char **endp, int b) .
  • O que é isso tudo? strtol converte uma string da base b para um inteiro. Ele quer te dizer o quão longe a corda chegou. Talvez possa retornar uma estrutura contendo tanto um long quanto um char * , mas não é assim que é declarado.
  • Em vez disso, ele retorna seu segundo resultado passando o endereço de uma string que ele modifica antes de retornar.
  • O que é uma string novamente? Ah sim, char *
  • Então, qual é o endereço de uma string? char ** .

Se você percorrer esse caminho por tempo suficiente, também poderá encontrar tipos T *** , embora quase sempre possa reestruturar o código para evitá-los.

Por fim, os pointers para os pointers aparecem em certas implementações complicadas de listas vinculadas . Considere a declaração padrão de uma lista duplamente vinculada em C.

 struct node { struct node *next; struct node *prev; /* ... */ } *head; 

Isso funciona bem, embora eu não reproduza as funções de inserção / exclusão aqui, mas tem um pequeno problema. Qualquer nó pode ser removido da lista (ou ter um novo nó inserido antes dele) sem referência ao header da lista. Bem, não exatamente qualquer nó. Isso não é verdade no primeiro elemento da lista, onde prev será nulo. Isso pode ser moderadamente irritante em alguns tipos de código C, em que você trabalha mais com os próprios nós do que com a lista como um conceito. Esta é uma ocorrência razoavelmente comum no código de sistemas de baixo nível.

E se nós rewritemos o node assim:

 struct node { struct node *next; struct node **prevp; /* ... */ } *head; 

Em cada nó, os pontos prevp não estão no nó anterior, mas no next ponteiro dos nós anteriores. E o primeiro nó? É prevp aponta para a head . Se você desenhar uma lista como essa (e precisar desenhá-la para entender como isso funciona), você verá que pode remover o primeiro elemento ou inserir um novo nó antes do primeiro elemento sem referenciar explicitamente head por nome.

x: function retornando ponteiro para array [] do ponteiro para function retornando char “- huh?

Você tem uma function

Essa function retorna um ponteiro.

Esse ponteiro aponta para uma matriz.

Essa matriz é uma matriz de pointers de function (ou pointers para funções)

Essas funções retornam char *.

  what's the use case for a pointer to a pointer? 

Uma é facilitar os valores de retorno por meio de argumentos.

Vamos dizer que você tem

 int function(int *p) *p = 123; return 0; //success ! } 

Você chama assim

 int x; function(&x); 

Como você pode ver, para a function poder modificar nosso x , temos que passar um ponteiro para o nosso x.

E se x não fosse um int, mas um char * ? Bem, ainda é o mesmo, temos que passar um ponteiro para isso. Um ponteiro para um ponteiro:

 int function(char **p) *p = "Hello"; return 0; //success ! } 

Você chama assim

 char *x; function(&x); 

A resposta de Remo.D para funções de leitura é uma boa sugestão. Aqui estão algumas respostas para os outros.

Um caso de uso para um ponteiro para um ponteiro é quando você deseja passá-lo para uma function que irá modificar o ponteiro. Por exemplo:

 void foo(char **str, int len) { *str = malloc(len); } 

Além disso, isso pode ser uma matriz de strings:

 void bar(char **strarray, int num) { int i; for (i = 0; i < num; i++) printf("%s\n", strarray[i]); } 

Normalmente, não se deve usar declarações tão complicadas, embora às vezes você precise de tipos que são bem complicados para coisas como pointers de function. Nesses casos, é muito mais legível usar typedefs para tipos intermediários; por exemplo:

 typedef void foofun(char**, int); foofun *foofunptr; 

Ou, para o seu primeiro exemplo de "function retornando ponteiro para array [] do ponteiro para function retornando char", você pode fazer:

 typedef char fun_returning_char(); typedef fun_returning_char *ptr_to_fun; typedef ptr_to_fun array_of_ptrs_to_fun[]; typedef array_of_ptrs_to_fun *ptr_to_array; ptr_to_array myfun() { /*...*/ } 

Na prática, se você estiver escrevendo algo sensato, muitas dessas coisas terão nomes significativos; por exemplo, estas podem ser funções retornando nomes (de algum tipo), então fun_returning_char poderia ser name_generator_type e array_of_ptrs_to_fun poderia ser name_generator_list . Assim, você poderia recolheu algumas linhas e apenas definirá essas duas typedefs - que provavelmente serão úteis em outro lugar, em qualquer caso.

 char far *far *ptr; 

Este é um formulário obsoleto da Microsoft, que remonta ao MS-DOS e aos primeiros dias do Windows. A versão SHORT é que este é um ponteiro distante para um ponteiro distante para um caractere, em que um ponteiro distante pode apontar para qualquer lugar na memory, em oposição a um ponteiro próximo que pode apontar apenas em qualquer lugar no segmento de dados de 64K. Você realmente não quer saber os detalhes sobre os modelos de memory da Microsoft para trabalhar com a arquitetura de memory segmentada Intel 80×86 totalmente sem cérebro.

 typedef void (*pfun)(int,float); 

Isso declara pfun como um typedef para um ponteiro para um procedimento que leva um int e um float. Você normalmente usaria isso em uma declaração de function ou um protótipo, viz.

 float foo_meister(pfun rabbitfun) { rabbitfun(69, 2.47); } 

Temos que avaliar todas as declarações de declaração de ponteiro da esquerda para a direita, iniciando de onde o nome do ponteiro ou o nome da declaração é declarado na instrução.

Ao avaliar a declaração, temos que começar do parêntese mais interno.

Comece com o nome do ponteiro ou nome da function, seguido pelos caracteres mais à direita nos parenters e, em seguida, seguido pelos caracteres mais à esquerda.

Exemplo:

 char (*(*f())[])(); ^ char (*(*f())[])(); ^^^ In here f is a function name, so we have to start from that. 

f ()

 char (*(*f())[])(); ^ Here there are no declarations on the righthand side of the current parenthesis, we do have to move to the lefthand side and take *: char (*(*f())[])(); ^ f() * 

Nós completamos os caracteres entre parênteses internos, e agora temos que voltar a um nível por trás disso:

 char (*(*f())[])(); ------ 

Agora pegue [], porque isso está do lado direito do parêntese atual.

 char (*(*f())[])(); ^^ f() * [] 

Agora pegue o * porque não há nenhum caractere no lado direito.

 char (*(*f())[])(); ^ char (*(*f())[])(); ^ f() * [] * char (*(*f())[])(); 

Em seguida, avalie os parênteses externos de abertura e fechamento, indicando uma function.

 f() * [] * () char (*(*f())[])(); 

Agora podemos adicionar o tipo de dados no final da instrução.

 f() * [] * () char. char (*(*f())[])(); 

Resposta final:

  f() * [] * () char. 

f é uma function que retorna o ponteiro para array [] de pointers para funcionar retornando char.

Esqueça 1 e 2 – isso é apenas teórico.

3: Isto é usado na function de input do programa int main(int argc, char** argv) . Você pode acessar uma lista de strings usando um char** . argv [0] = primeira string, argv [1] = segunda string, …

Passar um ponteiro como um argumento para uma function permite que essa function mude o conteúdo da variável apontada, o que pode ser útil para retornar informações por outros meios que não o valor de retorno da function. Por exemplo, o valor de retorno já pode ser usado para indicar erro / sucesso ou você pode querer retornar vários valores. A syntax para isso no código de chamada é foo (& var), que usa o endereço var, isto é, um ponteiro para var.

Então, como tal, se a variável cujo conteúdo você deseja que a function mude é ela própria um ponteiro (por exemplo, uma string), o parâmetro seria declarado como um ponteiro para um ponteiro .

 #include  char *some_defined_string = "Hello, " ; char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated int point_me_to_the_strings(char **str1, char **str2, char **str3) { *str1 = some_defined_string ; *str2 = alloc_string() ; *str3 = "!!" ; if (str2 != 0) { return 0 ; //successful } else { return -1 ; //error } } main() { char *s1 ; //uninitialized char *s2 ; char *s3 ; int success = point_me_to_the_strings(&s1, &s2, &s3) ; printf("%s%s%s", s1, s2, s3) ; } 

Note que main () não aloca armazenamento para as strings, então point_me_to_the_strings () não escreve para str1, str2 e str3 como se elas fossem passadas como pointers para chars. Em vez disso, point_me_to_the_strings () altera os próprios pointers, fazendo com que eles apontem para lugares diferentes, e isso pode ser feito porque possui pointers para eles.