Significado de int (*) (int *) = 5 (ou qualquer valor inteiro)

87

Eu não consigo descobrir isso:

int main() {
    int (*) (int *) = 5;
    return 0;
}

A atribuição acima é compilada com g ++ c ++ 11. Eu sei que int (*) (int *)é um ponteiro para uma função que aceita um (int *)como argumento e retorna um int, mas não entendo como você poderia igualá-lo a 5. No início, pensei que fosse uma função que retornava constantemente 5 (do meu aprendizado recente em F #, provavelmente, haha), então pensei, brevemente, que o ponteiro de função aponta para o local de memória 5, mas isso não funciona, claramente, e nem os valores hexadecimais.

Pensando que poderia ser porque a função retorna um int e que atribuir um int está ok (de alguma forma), eu também tentei isso:

int * (*) (int *) = my_ptr

onde my_ptré do tipo int *, o mesmo tipo deste segundo ponteiro de função, como no primeiro caso com o tipo int. Isso não compila. Atribuir 5 ou qualquer valor int, em vez de my_ptr, também não compila para este ponteiro de função.

Então, o que significa a atribuição?

Atualização 1

Temos a confirmação de que se trata de um bug, conforme demonstrado na melhor resposta. No entanto, ainda não se sabe o que realmente acontece com o valor que você atribui ao ponteiro de função, ou o que acontece com a atribuição. Quaisquer (boas) explicações sobre isso seriam muito apreciadas! Consulte as edições abaixo para obter mais clareza sobre o problema.

Editar 1

Estou usando o gcc versão 4.8.2 (no Ubuntu 4.8.2)

Editar 2

Na verdade, igualar a qualquer coisa funciona no meu compilador. Mesmo igualando-o a uma variável std :: string, ou um nome de função que retorna um duplo, funciona.

Editar 2.1

Curiosamente, torná-lo um ponteiro de função para qualquer função que retorna um tipo de dados que não seja um ponteiro, permitirá que ele seja compilado, como

std::string (*) () = 5.6;

Mas assim que o ponteiro de função é para uma função que retorna algum ponteiro, ele não compila, como com

some_data_type ** (*) () = any_value;
Konrad Kapp
fonte
3
Hmm ... não parece certo e o clang não aceita isso. Pode ser uma extensão gcc (ou bug).
Wintermute de
4
g ++ compila, mas gcc não funciona:error: expected identifier or '(' before ')' token
tivn
3
@ 0x499602D Observe que o código não dá um nome ao ponteiro. Comint *x = 5 você o nomeou x. Com int * (*x) (int *) = 5ele não vai compilar. (embora isso seja compilado como código C).
nos
5
Caso de teste reduzido: int(*) = 5; eint(*);
Johannes Schaub - litb

Respostas:

59

É um bug no g ++.

 int (*) (int *) 

é um nome de tipo.

Em C ++, você não pode ter uma declaração com um nome de tipo sem um identificador.

Portanto, isso compila com g ++.

 int (*) (int *) = 5;

e isso também compila:

 int (*) (int *);

mas ambas são declarações inválidas.

EDITAR :

TC menciona nos comentários o bugzilla bug 60680 com um caso de teste semelhante, mas ainda não foi aprovado . O bug é confirmado no bugzilla.

EDIT2 :

Quando as duas declarações acima estão no escopo do arquivo, o g ++ emite corretamente um diagnóstico (ele falha em emitir o diagnóstico no escopo do bloco).

EDIT3 :

Eu verifiquei e posso reproduzir o problema na última versão do g ++ versão 4 (4.9.2), na última versão de pré-lançamento 5 (5.0.1 20150412) e na última versão experimental 6 (6.0.0 20150412).

ouah
fonte
5
MSVC rejeitou o código publicado editado comerror C2059: syntax error : ')'
Weather Vane
Se for um nome de tipo, por que 'int (*) (int *) int_func;' trabalhos?
Konrad Kapp
1
Para o bugzilla do GCC, "NEW" é um bug confirmado. (Bugs não confirmados são "NÃO CONFIRMADOS").
TC
4
@KonradKapp: funciona muito bem se você disser o int (*int_func)(int *); que declara um ponteiro de função chamado int_func.
Edward
3
@KonradKapp C ++ usa notação infixa para colocar o identificador; mesma razão que é int x[5];e nãoint[5] x;
MM
27

Não é C ++ válido. Lembre-se de que, como seu compilador específico compila, ele não o torna válido. Compiladores, como todo software complexo, às vezes têm bugs e este parece ser um.

Em contraste, clang++reclama:

funnycast.cpp:3:11: error: expected expression
    int (*) (int *) = 5;
          ^
funnycast.cpp:3:18: error: expected '(' for function-style cast or type construction
    int (*) (int *) = 5;
             ~~~ ^
funnycast.cpp:3:19: error: expected expression
    int (*) (int *) = 5;
                  ^
3 errors generated.

Este é o comportamento esperado porque a linha incorreta não é C ++ válido. Ele pretende ser uma atribuição (devido ao =), mas não contém nenhum identificador.

Edward
fonte
9

Como outras respostas apontaram, é um bug que

int (*) (int *) = 5;

compila. Uma aproximação razoável desta declaração que se esperaria ter um significado é:

int (*proc)(int*) = (int (*)(int*))(5);

Agora procé um ponteiro para função que espera que o endereço 5seja o endereço base de uma função que recebe um int*e retorna um int.

Em alguns microcontroladores / microprocessadores 5 pode haver um endereço de código válido e pode ser possível localizar essa função lá.

Na maioria dos computadores de uso geral, a primeira página de memória (endereços 0-1023para páginas de 4 K) é propositalmente inválida (não mapeada) para capturarnull acessos de ponteiro.

Assim, embora o comportamento dependa da plataforma, pode-se esperar razoavelmente que uma falha de página ocorra quando *procfor invocado (por exemplo, (*proc)(&v)). Antes da hora em que*proc é invocado, nada de anormal acontece.

A menos que você esteja escrevendo um vinculador dinâmico, quase certamente não deveria estar calculando endereços numericamente e atribuindo-os a variáveis ​​de ponteiro para função.

Atsby
fonte
2
/usr/lib/gcc/x86_64-pc-cygwin/4.9.2/cc1plus.exe -da so.cpp

Esta linha de comando gera muitos arquivos intermediários. O primeiro deles so.cpp.170r.expand, diz:

...
int main() ()
{
  int D.2229;
  int _1;

;;   basic block 2, loop depth 0
;;    pred:       ENTRY
  _1 = 0;
;;    succ:       3

;;   basic block 3, loop depth 0
;;    pred:       2
<L0>:
  return _1;
;;    succ:       EXIT

}
...

Isso ainda não responde exatamente ao que acontece, mas deve ser um passo na direção certa.

Roland Illig
fonte
Interessante. Qual é o propósito desses arquivos intermediários?
Konrad Kapp
@KonradKapp Produzir código de máquina a partir de código humano é um processo bastante complexo (especialmente se você deseja que seu compilador otimize sua saída). Como a compilação é muito complexa, não é feita em uma etapa, a maioria dos compiladores tem alguma forma de Representação Intermediária (IR).
11684
2
Outra razão para ter um IR é que, se você tiver um IR bem definido, poderá separar o front-end e o back-end de seu compilador. (Por exemplo, o front-end compila C para o seu IR, o back-end compila o IR para o código de máquina Intel. Agora, se você quiser adicionar suporte a ARM, você só precisa de um segundo back-end. E se quiser compilar Go, você só precisa de um segundo front-end e, além disso, o compilador Go suporta imediatamente Intel e ARM, já que você pode reutilizar os dois back-ends.
11684
@ 11684 OK, faz sentido. Muito interessante. Não consegui determinar que linguagem Roland deu nesta resposta ... parece algum tipo de montagem misturada com C.
Konrad Kapp
IR não precisa ser imprimível; Não tenho ideia do que o gcc usa, pode ser apenas uma representação para impressão @KonradKapp
11684