2011-10-05 16 views
59

¿Alguien puede explicar lo que sucede en este código de C++? Compila y ejecuta bien en Linux.¿Qué está pasando aquí en este código C++?

#include <iostream> 
using namespace std; 
int main = (cout << "Hello world!\n", 195); 
+3

Aquí se compila, pero segfaults. – evnu

+6

¿Dónde encontraste ese código? (Quiero quedarme muy, muy lejos de eso, sea lo que sea. Este código es desagradable.) – Mat

+54

¿Por qué alguien votaría para cerrar esto? De acuerdo, es una pieza desagradable de código y uno no debería escribir ese código alguna vez, pero ¿es suficiente razón para votar para cerrar? Veo demasiadas Q que acabo de votar para cerrar estos días porque a la gente no le gusta * la Q. Lo siento, pero ese no es un criterio válido para cerrar Q's. –

Respuesta

68

El número "195" es el código de la instrucción RET en x86.

El compilador de C++ (gcc en mi caso) no puede reconocer que "main" no fue declarado como una función. El compilador solo ve que existe el símbolo "principal" y presume que se refiere a una función.

El C++ código

int main = (cout << "Hello world!\n", 195); 

está inicializando una variable en file-scope. Este código de inicialización se ejecuta antes de que el entorno C/C++ llame a main(), pero después de que inicialice la variable "cout". La inicialización imprime "¡Hola, mundo! \ N", y establece el valor de la variable "principal" en 195. Una vez completada la inicialización, el entorno C/C++ realiza una llamada a "principal". El programa vuelve inmediatamente de esta llamada porque ponemos una instrucción RET (código 195) en la dirección de "principal".

Ejemplo de salida de GDB:

$ gdb ./a 
(gdb) break _fini 
Breakpoint 1 at 0x8048704 
(gdb) print main 
$1 = 0 
(gdb) disass &main 
Dump of assembler code for function main: 
    0x0804a0b4 <+0>:  add %al,(%eax) 
    0x0804a0b6 <+2>:  add %al,(%eax) 
End of assembler dump. 
(gdb) run 
Starting program: /home/atom/a 
Hello world! 

Breakpoint 1, 0x08048704 in _fini() 
(gdb) print main 
$2 = 195 
(gdb) disass &main 
Dump of assembler code for function main: 
    0x0804a0b4 <+0>:  ret  
    0x0804a0b5 <+1>:  add %al,(%eax) 
    0x0804a0b7 <+3>:  add %al,(%eax) 
End of assembler dump. 
+8

+1 para una explicación detallada de por qué * does * work. – new123456

+3

@ new123456: sobre por qué * algunas veces * funciona. Como se dijo, en OS X se bloquea, y el estándar especifica que este no es un programa C++ válido. –

+0

Interesante, pensé que el segmento de datos y el segmento de código eran segmentos separados y que debería prohibirse saltar a una dirección en el segmento de datos. Pero probablemente esto no sea cierto para todas las implementaciones. – Giorgio

1

esta forma se establece la variable global main (un entero) con el valor de 195 después de la impresión a cabo Hola mundo. Aún necesitará definir la función principal para que se ejecute.

+2

Y si define la función main, tiene un comportamiento indefinido (debido a la ODR). –

39

No es un programa C++ válido. De hecho, se bloquea para mí en Mac OSX después de imprimir "Hello World".

Desmontaje muestra main es una variable estática, y hay inicializadores para ello:

global constructors keyed to main: 
0000000100000e20 pushq %rbp 
0000000100000e21 movq %rsp,%rbp 
0000000100000e24 movl $0x0000ffff,%esi 
0000000100000e29 movl $0x00000001,%edi 
0000000100000e2e leave 
0000000100000e2f jmp __static_initialization_and_destruction_0(int, int) 

¿Por qué se imprime "Hola mundo"?

La razón por la que ve "Hello World" impreso es porque se ejecuta durante la inicialización estática de main, la variable entera estática. Los inicializadores estáticos se invocan antes de que el tiempo de ejecución de C++ intente llamar al main(). Cuando lo hace, se bloquea, porque main no es una función válida, solo hay un entero 195 en la sección de datos del ejecutable.

Otras respuestas indican que esta es una instrucción válida ret y funciona bien en Linux, pero se bloquea en OSX, porque la sección está marcada como no ejecutable de forma predeterminada.

¿Por qué un compilador de C++ no puede decir que main() no es una función y se detiene con el error del enlazador?

main() tiene un enlace C, por lo que el vinculador no puede distinguir entre el tipo de símbolos. En nuestro caso, _main reside en la sección de datos.

start: 
0000000100000eac pushq $0x00 
0000000100000eae movq %rsp,%rbp 
... 
0000000100000c77 callq _main ; 1000010b0 
0000000100000c7c movl %eax,%edi 
0000000100000c7e callq 0x100000e16 ; symbol stub for: _exit 
0000000100000c83 hlt 
... 
; the text section ends at 100000deb 
+12

Hay [lugares en la web] (http://d.hatena.ne.jp/qnighy/20090418/1240064403) que afirman que se supone que esto funciona en las arquitecturas IA32 porque '195' es' 0xC3', es decir, ' Instrucción RET' Todavía me parece extraño, aunque ... –

+1

@ FrédéricHamidi, gracias, buen hallazgo. –

+1

@ FrédéricHamidi El enlace que das afirma que se supone que funciona en C, no en C++. Aún así, es ilegal en ambos idiomas, por razones ligeramente diferentes. En ambos idiomas, ** se requiere ** para definir una función global 'main', devolver' int', y tomar uno de los conjuntos de argumentos definidos por la implementación. Cualquier otra definición para 'main' es ilegal (y debería causar que un buen compilador se queje, especialmente en el caso de C++, donde' main' debe ser tratado especialmente de todos modos). –

5

No es un programa legal, pero creo que el estándar es un poco ambigua en cuanto a si se requiere un diagnóstico o es un comportamiento indefinido. (Desde el punto de vista de la calidad de implementación, esperaría un diagnóstico.)

+0

En cuanto a QOI: posiblemente en casos donde la implementación sea compatible con un entorno independiente, el compilador no puede decir a qué entorno está destinado el archivo de objeto (incluso si la TU incluye encabezados no garantizados como independientes), la implementación puede proveerles). Y luego el enlazador no puede decir si el símbolo es una función o no. Sin embargo, estoy especulando, no sé lo suficiente acerca de las entrañas del compilador para decir con certeza qué es lo que el compilador "debería" saber sobre alojado vs. independiente. –

+2

@SteveJessop Si está compilando para un entorno independiente, por supuesto, todo está definido por la implementación. Pero el compilador debe saber esto (ya que tiene que saber si tratar 'main' especialmente o no), y si trata la función' main' especialmente (sin modificar, etc.) para poder llamarla ' crt0', entonces sabe que 'main' en el espacio de nombres global es especial y puede generar un error para el código de muestra. –

Cuestiones relacionadas