c++ - con - gcc compilador
Código extraño que se compila con g++ (3)
El siguiente código compila con éxito con g ++ 4.8.1:
int main()
{
int(*)();
}
Parece una simple declaración de un puntero a la función:
int(*f)();
No compila con clang 3.4 y vc ++ 2013.
¿Es un error de compilación o uno de los lugares oscuros del estándar?
Lista de piezas de códigos extraños similares que compilan bien con g ++ 4.8.1 (actualizado):
int(*)();
int(*);
int(*){};
int(*());
Ejemplo en vivo con estos extraños fragmentos de código .
Actualización 1: @Ali agregó información interesante en los comentarios:
Los 4 casos dan un error de compilación con troncal clang 3.5 (202594) y compilan bien con la troncal gcc 4.9 (20140302). El comportamiento es el mismo con
-std=c++98 -pedantic
, excepto paraint(*){};
lo cual es comprensible; listas ampliadas de inicializadores solo disponibles con-std=c++11
.
Actualización 2: Como @CantChooseUsernames señalados en su respuesta aún compilan bien incluso con la inicialización y no se genera ningún ensamblado para ellos por g ++ (ni con ni sin inicialización), incluso sin ninguna optimización habilitada:
int(*)() = 0;
int(*) = 0;
int(*){} = 0;
int(*()) = 0;
Ejemplo en vivo con inicializaciones .
Actualización 3: Me sorprendió mucho descubrir que int(*)() = "Hello, world!";
también compila bien (while int(*p)() = "Hello, world!";
no compila, por supuesto).
Actualización 4: Es fantástico pero int(*){} = Hello, world!;
compila bien Y la siguiente pieza extremadamente extraña de código: int(*){}() = -+*/%&|^~.,:!?$()[]{};
( ejemplo en vivo ).
Actualización 5: como @zwol señaló @zwol en su comentario
Esto y una serie de problemas sintácticos relacionados se están rastreando como error 68265 de gcc.
De acuerdo con el Estándar C ++ (p. # 6 de la sección 7 Declaraciones)
6 Cada init-declarator en la lista init-declarator contiene exactamente un declarator-id , que es el nombre declarado por ese init-declarator y, por lo tanto, uno de los nombres declarados por la declaración
Entonces es simplemente un error de compilación.
El código válido podría verse como por ejemplo (aparte de la declaración del puntero a la función mostrada por usted) aunque no puedo compilarlo con mi MS VC ++ 2010.
int(*p){};
Parece que el compilador que está utilizando para probar permite declaraciones sin un ID de declarador.
Tenga también en cuenta el siguiente párrafo de la sección 8.1 Nombres de tipos
1 Para especificar las conversiones de tipo explícitamente, y como un argumento de sizeof, alignof, new o typeid , se debe especificar el nombre de un tipo. Esto se puede hacer con un ID de tipo, que es sintácticamente una declaración para una variable o función de ese tipo que omite el nombre de la entidad.
No estoy seguro de cuánto ayuda esto, pero probé lo siguiente (clang 3.3, g ++ 4.8.1):
using P = int(*)();
using Q = int*;
P; // warning only
Q; // warning only
int(*)(); // error (but only in clang)
int*; // error
int(*p)(); // ok
int *q; // ok
Por otro lado, todo compila bien en g ++ 4.8.2 y 4.9.0. No tengo clang 3.4, desafortunadamente.
Muy aproximadamente , una declaración [iso sección 7] consta de las siguientes partes en orden:
- especificadores de prefijos opcionales (por ejemplo,
static
,virtual
) - tipo de base (por ejemplo,
const double
,vector<int>
) - declarador (por ejemplo,
n
,*p
,a[7]
,f(int)
) - especificadores de función de sufijo opcionales (por ejemplo,
const
,noexcept
) - inicializador opcional o cuerpo de función (p. ej.
= {1,2,3}
o{ return 0; }
Ahora, un declarador consiste más o menos en un nombre y, opcionalmente, algunos operadores declaradores [iso 8/4].
Operadores de prefijo, por ejemplo:
-
*
(puntero) -
*const
(puntero constante) -
&
(referencia lvalue) -
&&
(referencia rvalue) -
auto
(tipo de retorno de función, al final)
Operadores de Postfix, por ejemplo:
-
[]
(matriz) -
()
(función) -
->
(función de retorno de tipo de retorno)
Los operadores anteriores fueron diseñados para reflejar su uso en expresiones. Los operadores de Postfix se vinculan más estrictamente que el prefijo, y los paréntesis se pueden usar para cambiar su orden: int *f()
es una función que devuelve un puntero a int
, mientras que int (*f)()
es un puntero a una función que retorna int
.
Tal vez estoy equivocado, pero creo que estos operadores no pueden estar en la declaración sin el nombre. Entonces cuando escribimos int *q;
, entonces int
es el tipo de base, y *q
es el declarador que consiste en el operador de prefijo *
seguido del nombre q
. Pero int *;
no puede aparecer solo
Por otro lado, cuando definimos using Q = int*;
, luego declaración Q;
está bien por sí mismo porque Q
es el tipo de base. Por supuesto, como no estamos declarando nada, podemos recibir un error o una advertencia según las opciones del compilador, pero este es un error diferente.
Lo anterior es solo mi entendimiento. Lo que el estándar (por ejemplo, N3337) dice es [iso 8.3 / 1]:
Cada declarador contiene exactamente un identificador de declarante ; nombra el identificador que se declara. Una identificación no calificada que se produce en un identificador-declarador debe ser un identificador simple excepto para la declaración de algunas funciones especiales (12.3 [ conversiones definidas por el usuario ], 12.4 [destructores], 13.5 [operadores sobrecargados]) y para la declaración de especializaciones de plantillas o especializaciones parciales (14.7).
(las notas entre corchetes son mías). Entonces entiendo int(*)();
debería ser inválido y no puedo decir por qué tiene un comportamiento diferente en clang y diferentes versiones de g ++.
Puede usar esto: http://gcc.godbolt.org/ para ver el ensamblaje ..
int main()
{
int(*)() = 0;
return 0;
}
Genera:
main:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
popq %rbp
ret
Que es equivalente a: int main() {return 0;}
Así que incluso sin optimización, gcc simplemente no genera ensamblaje para ello. ¿Debería dar una advertencia o un error? No tengo ni idea, pero no me importa ni hago nada por el puntero func sin nombre.
Sin embargo:
int main()
{
int (*p)() = 0;
return 0;
}
Sin optimización generará:
main:
pushq %rbp
movq %rsp, %rbp
movq $0, -8(%rbp)
movl $0, %eax
popq %rbp
ret
que asigna 8 bytes en la pila.