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 nesta 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?
Há uma pergunta sobre "regra espiral", mas não estou apenas perguntando sobre 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.
f
como uma matriz de ponteiros para funções que poderiam aceitar qualquer argumento ... se fossevoid (*(*f[])(void))(void);
, então sim, seriam funções que nãoRespostas:
Existe uma regra chamada " Regra no sentido horário / espiral" para ajudar a encontrar o significado de uma declaração complexa.
Do c-faq :
Você pode verificar o link acima para exemplos.
Observe também que, para ajudá-lo, também existe um site chamado:
http://www.cdecl.org
Você pode inserir uma declaração C e ela dará o seu significado em inglês. Para
produz:
EDITAR:
Conforme apontado nos comentários de Random832 , a regra espiral não aborda a matriz de matrizes e levará a um resultado errado (na maioria) dessas declarações. Por exemplo,
int **x[1][2];
a regra em espiral ignora o fato de que[]
tem maior precedência*
.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 queint **(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 de ponteiro para ponteiro para int", que é a declaração correta em inglês.Observe que esse problema também foi abordado nesta resposta por James Kanze (apontado por hackers nos comentários).
fonte
A regra "espiral" cai das seguintes regras de precedência:
O subscrito
[]
()
operadores de e chamada de função têm maior precedência que unário*
, portanto,*f()
são analisados como*(f())
e*a[]
são analisados como*(a[])
.Portanto, se você deseja um ponteiro para uma matriz ou um ponteiro para uma função, precisa agrupar explicitamente o
*
com o identificador, como em(*a)[]
ou(*f)()
.Então você percebe isso
a
ef
pode ser expressões mais complicadas do que apenas identificadores; inT (*a)[N]
,a
poderia ser um identificador simples ou uma chamada de função como(*f())[N]
(a
->f()
), ou poderia ser uma matriz como(*p[M])[N]
, (a
->p[M]
), ou poderia ser uma matriz de ponteiros para funções como(*(*p[M])())[N]
(a
->(*p[M])()
), etc.Seria bom se o operador de indireção
*
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 quevoid (*(*f[])())()
), mas não é.Quando você se deparar com uma declaração cabeluda 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:
o
signal
função na biblioteca padrão é provavelmente o tipo de amostra para esse tipo de insanidade:Neste ponto, a maioria das pessoas diz "use typedefs", o que certamente é uma opção:
Mas...
Como você usaria
f
em uma expressão? Você sabe que é uma matriz 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 usarf
uma expressão (ou seja(*(*f[i])())();
, supondo que nenhuma função use argumentos).fonte
f
árvore de desaceleração, explicando a precedência ... por alguma razão eu sempre obter um chute para fora de ASCII-art, especialmente quando se trata de explicar as coisas :)void
funções entre parênteses, caso contrário, ela pode receber quaisquer argumentos.Em C, a declaração reflete o uso - é assim que é definida no padrão. A declaração:
É uma afirmação de que a expressão
(*(*f[i])())()
produz um resultado do tipovoid
. Que significa:f
deve ser uma matriz, pois você pode indexá-la:Os elementos de
f
devem ser ponteiros, pois você pode desreferenciá-los:Esses ponteiros devem ser ponteiros para funções sem argumentos, pois você pode chamá-los:
Os resultados dessas funções também devem ser indicadores, pois você pode desreferenciá-los:
Esses ponteiros também devem ser ponteiros para funções sem argumentos, pois você pode chamá-los:
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.
fonte
vector< function<function<void()>()>* > f
, especialmente se você adicionar ostd::
s. (Mas bem, o exemplo é inventado ... mesmof :: [IORef (IO (IO ()))]
parece estranho.)a[x]
indica que a expressãoa[i]
é válida quandoi >= 0 && i < x
. Visto que,a[]
deixa o tamanho não especificado e, portanto, é idêntico a*a
: indica que a expressãoa[i]
(ou equivalente*(a + i)
) é válida para algum intervalo dei
.(*f[])()
é um tipo que você pode indexar, desreferenciar e depois chamar, por isso é uma matriz de indicadores para funções.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:
Leia sempre 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 há uma opção, sempre favorece
[]
e()
substitui*
: Se*
precede o identificador e o[]
segue, o identificador representa uma matriz, não um ponteiro. Da mesma forma, se*
precede o identificador e o()
segue, o identificador representa uma função, não um ponteiro. (Parênteses sempre podem ser usados para substituir a prioridade normal de[]
e para()
cima*
.)Esta regra realmente envolve ziguezague de um lado do identificador para o outro.
Agora decifrando uma declaração simples
Aplicando regra:
Vamos decifrar a declaração complexa como
aplicando as regras acima:
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 .
fonte
char (x())[5]
deve resultar em erro de sintaxe, mas, cdecl o analisa como: declarex
como função retornando matriz 5 dechar
.É apenas uma "espiral" porque, nesta declaração, existe apenas um operador de cada lado dentro de cada nível de parênteses. Afirmar que você continua "em espiral" geralmente sugere que você alterne entre matrizes e ponteiros na declaração
int ***foo[][][]
quando, na realidade, todos os níveis da matriz vêm antes de qualquer um dos níveis do ponteiro.fonte
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 aceitável para escritores de compiladores). typedefs deve ser usado.
fonte
Como um trivial aleatório, você pode achar divertido saber que existe uma palavra real em inglês para descrever como as declarações C são lidas: Boustrophedonically , ou seja, alternando da direita para a esquerda e da esquerda para a direita.
Referência: Van der Linden, 1994 - Página 76
fonte
Com relação à utilidade disso, ao trabalhar com o shellcode, você vê muito essa construção:
Embora não seja tão sintaticamente complicado, esse padrão específico surge muito.
Exemplo mais completo neste questão SO.
Portanto, embora a utilidade na extensão da imagem original seja questionável (eu sugiro que qualquer código de produção seja drasticamente simplificado), existem algumas construções sintáticas que surgem bastante.
fonte
A declaração
é apenas uma maneira obscura de dizer
com
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
.fonte
Eu achei o método descrito por Bruce Eckel útil e fácil de seguir:
Retirado de: Thinking in C ++ Volume 1, segunda edição, capítulo 3, seção "Function Addresses", de Bruce Eckel.
fonte
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 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 deve 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 de PL / I que começava com algo como:
fonte
IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIF
e é analisada comoif (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF)
.Por acaso, sou o autor original da regra espiral que escrevi há muitos anos (quando eu tinha muitos cabelos :) e fiquei honrado quando foi adicionado ao cfaq.
Escrevi a regra da espiral como uma maneira de tornar mais fácil para meus alunos e colegas ler as declarações C "na cabeça"; ou seja, sem ter que usar ferramentas de software como cdecl.org etc. Nunca foi minha intenção declarar que a regra espiral é a maneira canônica de analisar expressões em C. Estou, no entanto, feliz em ver que a regra ajudou literalmente milhares de estudantes e profissionais de programação C ao longo dos anos!
Para o registro,
Foi "corretamente" identificado várias vezes em muitos sites, inclusive por Linus Torvalds (alguém a quem eu respeito imensamente), que há situações em que minha regra em espiral "quebra". O ser mais comum:
Conforme apontado por outras pessoas neste segmento, a regra pode ser atualizada para dizer que, quando você encontrar matrizes, consuma todos os índices como se estivesse escrito como:
Agora, seguindo a regra da espiral, eu obteria:
"ar é uma matriz bidimensional 10x10 de ponteiros para char"
Espero que a regra da espiral continue sendo útil na aprendizagem de C!
PS:
Eu amo a imagem "C não é difícil" :)
fonte
(*(*f[]) ()) ()
Resolução
void
>>(*(*f[]) ())
() = nuloRecuperação
()
>>(*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))))Resolução
[ ]
>>fonte