what - C não é tão difícil: void(*(* f[])())()




what is pointer in c (9)

Então essa "leitura em espiral" é algo válido?

A aplicação de regra em espiral ou o uso do cdecl nem sempre são válidos. Ambos falham em alguns casos. A regra espiral funciona para muitos casos, mas não é universal .

Para decifrar declarações complexas, lembre-se destas duas regras simples:

  • Sempre leia as declarações de dentro para fora : Comece entre parênteses mais internos , se houver. Localize o identificador que está sendo declarado e comece a decifrar a declaração a partir daí.

  • Quando houver uma opção, sempre favoreça [] e () sobre * : se * precede o identificador e [] segue, o identificador representa uma matriz, não um ponteiro. Da mesma forma, se * precede o identificador e () segue, o identificador representa uma função, não um ponteiro. (Parênteses sempre podem ser usados ​​para substituir a prioridade normal de [] e () sobre * .)

Essa regra, na verdade, envolve ziguezague de um lado do identificador para o outro.

Agora decifrando uma declaração simples

int *a[10];

Aplicando regra:

int *a[10];      "a is"  
     ^  

int *a[10];      "a is an array"  
      ^^^^ 

int *a[10];      "a is an array of pointers"
    ^

int *a[10];      "a is an array of pointers to `int`".  
^^^      

Vamos decifrar a declaração complexa como

void ( *(*f[]) () ) ();  

aplicando as regras acima:

void ( *(*f[]) () ) ();        "f is"  
          ^  

void ( *(*f[]) () ) ();        "f is an array"  
           ^^ 

void ( *(*f[]) () ) ();        "f is an array of pointers" 
         ^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function"   
               ^^     

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer"
       ^   

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function" 
                    ^^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function returning `void`"  
^^^^

Aqui está um GIF demonstrando como você vai (clique na imagem para aumentar a exibição):

As regras mencionadas aqui são retiradas do livro C Programming A Modern Approach de KN KING .

Acabei de ver uma foto hoje e acho que apreciaria explicações. Então, aqui está a imagem:

Achei isso confuso e me perguntei se esses códigos são práticos. Pesquisei a imagem no Google e encontrei outra imagem this entrada do reddit, e aqui está essa imagem:

Então essa "leitura em espiral" é algo válido? É assim que os compiladores C analisam?
Seria ótimo se houvesse explicações mais simples para esse código estranho.
Além de tudo, esses tipos de códigos podem ser úteis? Em caso afirmativo, onde e quando?

uma pergunta sobre "regra espiral", mas não estou apenas perguntando como ela é aplicada ou como as expressões são lidas com essa regra. Estou questionando o uso de tais expressões e a validade da regra espiral também. Com relação a isso, algumas respostas legais já foram publicadas.


Lembre-se destas regras para C declara
E a precedência nunca estará em dúvida:
Comece com o sufixo, continue com o prefixo,
E leia os dois conjuntos de dentro para fora.
- eu, meados dos anos 80

Exceto quando modificado por parênteses, é claro. E observe que a sintaxe para declarar isso reflete exatamente a sintaxe para usar essa variável para obter uma instância da classe base.

Sério, não é difícil aprender a fazer de relance; você só precisa estar disposto a gastar algum tempo praticando a habilidade. Se você deseja manter ou adaptar o código C escrito por outras pessoas, definitivamente vale a pena investir esse tempo. Também é um truque divertido para assustar outros programadores que não o aprenderam.

Para seu próprio código: como sempre, o fato de que algo pode ser escrito como uma linha não significa que deveria ser, a menos que seja um padrão extremamente comum que se tornou um idioma padrão (como o loop de cópia de cadeia) . Você e aqueles que o seguem serão muito mais felizes se você criar tipos complexos a partir de typedefs em camadas e dereferências passo a passo, em vez de confiar na sua capacidade de gerar e analisar esses "de uma só vez". O desempenho será igualmente bom e a legibilidade e a manutenção do código serão tremendamente melhores.

Poderia ser pior, você sabe. Havia uma declaração legal PL / I que começava com algo como:

if if if = then then then = else else else = if then ...

A declaração

void (*(*f[])())()

é apenas uma maneira obscura de dizer

Function f[]

com

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

Na prática, serão necessários nomes mais descritivos em vez de ResultFunction e Function . Se possível, também especificaria as listas de parâmetros como void .


A regra "espiral" cai das seguintes regras de precedência:

T *a[]    -- a is an array of pointer to T
T (*a)[]  -- a is a pointer to an array of T
T *f()    -- f is a function returning a pointer to T
T (*f)()  -- f is a pointer to a function returning T

Os operadores subscrito [] e chamada de função () têm maior precedência que unário * , portanto *f() é analisado como *(f()) e *a[] é analisado como *(a[]) .

Portanto, se você deseja um ponteiro para uma matriz ou um ponteiro para uma função, é necessário agrupar explicitamente o * com o identificador, como em (*a)[] ou (*f)() .

Então você percebe que f podem ser expressões mais complicadas do que apenas identificadores; em T (*a)[N] , a poderia ser um identificador simples ou uma chamada de função como (*f())[N] ( a -> f() ) ou uma matriz como (*p[M])[N] , ( a -> p[M] ) ou pode ser uma matriz de ponteiros para funções como (*(*p[M])())[N] ( a - > (*p[M])() ) etc.

Seria bom se o operador indireto * fosse postfix em vez de unário, o que tornaria as declarações um pouco mais fáceis de ler da esquerda para a direita ( void f[]*()*(); definitivamente flui melhor que void (*(*f[])())() ), mas não é.

Quando você se deparar com uma declaração peluda como essa, comece encontrando o identificador mais à esquerda e aplique as regras de precedência acima, aplicando-as recursivamente a qualquer parâmetro de função:

         f              -- f
         f[]            -- is an array
        *f[]            -- of pointers  ([] has higher precedence than *)
       (*f[])()         -- to functions
      *(*f[])()         -- returning pointers
     (*(*f[])())()      -- to functions
void (*(*f[])())();     -- returning void

A função de signal na biblioteca padrão é provavelmente o tipo de amostra para esse tipo de insanidade:

       signal                                       -- signal
       signal(                          )           -- is a function with parameters
       signal(    sig,                  )           --    sig
       signal(int sig,                  )           --    which is an int and
       signal(int sig,        func      )           --    func
       signal(int sig,       *func      )           --    which is a pointer
       signal(int sig,      (*func)(int))           --    to a function taking an int                                           
       signal(int sig, void (*func)(int))           --    returning void
      *signal(int sig, void (*func)(int))           -- returning a pointer
     (*signal(int sig, void (*func)(int)))(int)     -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int);    -- and returning void

Neste ponto, a maioria das pessoas diz "use typedefs", o que certamente é uma opção:

typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);

innerfunc *f[N];

Mas...

Como você usaria f em uma expressão? Você sabe que é um conjunto de ponteiros, mas como você o utiliza para executar a função correta? Você precisa revisar os typedefs e descobrir a sintaxe correta. Por outro lado, a versão "nua" é bastante óbvia, mas mostra exatamente como usar f em uma expressão (a saber, (*(*f[i])())(); assumindo que nenhuma função aceite argumentos).


Duvido que construções como essa possam ter algum uso na vida real. Eu até detesto eles como perguntas da entrevista para os desenvolvedores regulares (provavelmente OK para escritores de compiladores). typedefs deve ser usado.


Em C, a declaração reflete o uso - é assim que é definida no padrão. A declaração:

void (*(*f[])())()

É uma afirmação de que a expressão (*(*f[i])())() produz um resultado do tipo void . Que significa:

  • f deve ser uma matriz, pois você pode indexá-la:

    f[i]
  • Os elementos de f devem ser ponteiros, pois você pode desreferenciá-los:

    *f[i]
  • Esses ponteiros devem ser ponteiros para funções sem argumentos, pois você pode chamá-los:

    (*f[i])()
  • Os resultados dessas funções também devem ser indicadores, pois você pode desreferenciá-los:

    *(*f[i])()
  • Esses ponteiros também devem ser ponteiros para funções sem argumentos, pois você pode chamá-los:

    (*(*f[i])())()
  • Esses ponteiros de função devem retornar void

A "regra espiral" é apenas um mnemônico que fornece uma maneira diferente de entender a mesma coisa.


Eu achei o método descrito por Bruce Eckel útil e fácil de seguir:

Definindo um ponteiro de função

Para definir um ponteiro para uma função que não possui argumentos nem valor de retorno, você diz:

void (*funcPtr)();

Quando você está olhando para uma definição complexa como essa, a melhor maneira de atacá-la é começar pelo meio e sair do lugar. “Iniciando no meio” significa começar com o nome da variável, que é funcPtr. "Trabalhar para sair" significa olhar para a direita para o item mais próximo (nada neste caso; o parêntese direito o interrompe), depois para a esquerda (um ponteiro indicado pelo asterisco) e depois para a direita (um lista de argumentos vazia indicando uma função que não aceita argumentos) e, em seguida, olhando para a esquerda (vazio, que indica que a função não tem valor de retorno). Esse movimento da direita-esquerda-direita funciona com a maioria das declarações.

Para revisar, "comece no meio" ("funcPtr é um ..."), vá para a direita (nada lá - você é interrompido pelo parêntese direito), vá para a esquerda e encontre o '*' (" ... apontador para um ... "), vá para a direita e encontre a lista de argumentos vazia (" ... função que não aceita argumentos ... "), vá para a esquerda e encontre o vazio (" funcPtr is um ponteiro para uma função que não aceita argumentos e retorna nula ”).

Você pode se perguntar por que * funcPtr requer parênteses. Se você não os usasse, o compilador veria:

void *funcPtr();

Você declararia uma função (que retorna um nulo *) em vez de definir uma variável. Você pode pensar no compilador como passando pelo mesmo processo que ele faz quando descobre o que deveria ser uma declaração ou definição. Ele precisa desses parênteses para "esbarrar" para voltar à esquerda e encontrar o '*', em vez de continuar à direita e encontrar a lista de argumentos vazia.

Declarações e definições complicadas

Além disso, depois de descobrir como a sintaxe das declarações C e C ++ funciona, você pode criar itens muito mais complicados. Por exemplo:

//: C03:ComplicatedDefinitions.cpp

/* 1. */     void * (*(*fp1)(int))[10];

/* 2. */     float (*(*fp2)(int,int,float))(int);

/* 3. */     typedef double (*(*(*fp3)())[10])();
             fp3 a;

/* 4. */     int (*(*f4())[10])();


int main() {} ///:~ 

Percorra cada uma delas e use a diretriz direita-esquerda para descobrir. O número 1 diz que "fp1 é um ponteiro para uma função que pega um argumento inteiro e retorna um ponteiro para uma matriz de 10 ponteiros nulos".

O número 2 diz que "fp2 é um ponteiro para uma função que recebe três argumentos (int, int e float) e retorna um ponteiro para uma função que recebe um argumento inteiro e retorna um float".

Se você estiver criando muitas definições complicadas, convém usar um typedef. O número 3 mostra como um typedef salva digitando sempre a descrição complicada. Ele diz “Um fp3 é um ponteiro para uma função que não aceita argumentos e retorna um ponteiro para uma matriz de 10 ponteiros para funções que não aceitam argumentos e retornam dobras”. Depois, diz que “a é um desses tipos de fp3”. Typedef geralmente é útil para criar descrições complicadas a partir de descrições simples.

O número 4 é uma declaração de função em vez de uma definição de variável. Ele diz que "f4 é uma função que retorna um ponteiro para uma matriz de 10 ponteiros para funções que retornam números inteiros".

Você raramente precisará de declarações e definições complicadas como essas. No entanto, se você realizar o exercício de descobri-las, nem ficará levemente perturbado com as que são um pouco complicadas que você pode encontrar na vida real.

Retirado de: Thinking in C ++ Volume 1, segunda edição, capítulo 3, seção "Function Addresses", de Bruce Eckel.


Existe uma regra chamada " Regra no sentido horário / espiral" para ajudar a encontrar o significado de uma declaração complexa.

Do c-faq :

Existem três etapas simples a seguir:

  1. Começando com o elemento desconhecido, mova-se no sentido espiral / horário; ao encontrar os seguintes elementos, substitua-os pelas frases em inglês correspondentes:

    [X] ou []
    => Tamanho da matriz X de ... ou tamanho indefinido da matriz de ...

    (type1, type2)
    => função passando type1 e type2 retornando ...

    *
    => ponteiro (s) para ...

  2. Continue fazendo isso no sentido espiral / horário até que todos os tokens estejam cobertos.

  3. Sempre resolva qualquer coisa entre parênteses primeiro!

Você pode verificar o link acima para ver exemplos.

Observe também que, para ajudá-lo, também existe um site chamado:

cdecl

Você pode inserir uma declaração C e ela dará o seu significado em inglês. Para

void (*(*f[])())()

produz:

declare f como matriz de ponteiro para retornar função ponteiro para retornar função vazia

EDITAR:

Como apontado nos comentários de Random832 , a regra espiral não trata da matriz de matrizes e levará a um resultado errado (na maioria) dessas declarações. Por exemplo, para int **x[1][2]; a regra da espiral ignora o fato de que [] tem maior precedência sobre * .

Quando na frente da matriz de matrizes, é possível adicionar primeiro parênteses explícitos antes de aplicar a regra em espiral. Por exemplo: int **x[1][2]; é o mesmo que int **(x[1][2]); (também válido C) devido à precedência e a regra espiral o lê corretamente como "x é uma matriz 1 da matriz 2 do ponteiro para o ponteiro para int", que é a declaração correta em inglês.

Observe que esse problema também foi abordado nesta answer por James Kanze (apontado por haccks nos comentários).


  • void (*(*f[]) ()) ()

Resolução de void >>

  • (*(*f[]) ()) () = nulo

Resoiving () >>

  • (* (*f[]) () ) = função retornando (nula)

Resolução * >>

  • (*f[]) () = ponteiro para (função retornando (vazio))

Resolução () >>

  • (* f[] ) = função retornando (ponteiro para (função retornando (vazio)))

Resolução * >>

  • f [] = ponteiro para (função retornando (ponteiro para (função retornando (vazio))))

Resolvendo [ ] >>

  • f = matriz de (ponteiro para (função retornando (ponteiro para (função retornando (vazio)))))




void-pointers