2011-10-05 41 views
59

任何人都可以請解釋一下這個C++代碼。它在Linux上編譯和執行得很好。在這個C++代碼中發生了什麼?

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

這編譯,但段錯誤。 – evnu

+6

你從哪裏找到該代碼? (我想遠離它,不管它是什麼,這個代碼很討厭) – Mat

+54

爲什麼有人會投票結束這個呢?同意,這是一段令人討厭的代碼,人們不應該編寫這樣的代碼,但是這個理由足以投票結束嗎?我看到太多的Q只被投票結束,因爲人們不喜歡Q.對不起,但這不是關閉Q的有效標準。 –

回答

68

數字「195」是x86上RET指令的代碼。

C++編譯器(在我的情況下是gcc)無法識別「main」沒有被聲明爲函數。編譯器只會看到有「主」符號,並假定它指向一個函數。

的C++代碼

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

正在初始化在文件範圍的變量。這個初始化代碼在C/C++環境調用main()之前執行,但在它初始化「cout」變量之後執行。初始化打印「Hello,world!\ n」,並將變量「main」的值設置爲195.完成所有初始化後,C/C++環境調用「main」。該程序立即從此調用中返回,因爲我們在RETURN指令(代碼195)的地址爲「main」。

樣品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爲深入解釋爲什麼*做*工作。 – new123456

+3

@ new123456:爲什麼它*有時*工作。如上所述,在OS X上它崩潰了,標準指出這不是一個有效的C++程序。 –

+0

有趣的是,我認爲數據段和代碼段是不同的段,它應該被禁止跳轉到數據段中的地址。但是,對於所有的實現來說可能都不是這樣。 – Giorgio

1

它會打印出世界,你好後設置的全局變量main(整數),以195的價值。您仍然需要定義要執行的main函數。

+2

如果你定義了main函數,你有未定義的行爲(因爲ODR)。 –

39

這不是一個有效的C++程序。事實上,打印「Hello World」後,它在Mac OSX上崩潰了。

拆卸顯示main是一個靜態變量,有初始化它:

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) 

爲什麼它打印的 「Hello World」?

您看到「Hello World」打印出來的原因是因爲它在靜態初始化期間運行,即靜態整數變量main。在C++運行時甚至試圖調用main()之前調用靜態初始化程序。當它發生時,它會崩潰,因爲main不是一個有效的函數,在可執行文件的數據部分中只有一個整數195。

其他答案表明這是一個有效的ret指令,它在Linux中運行良好,但在OSX上崩潰,因爲該部分在默認情況下被標記爲不可執行。

爲什麼C++編譯器不能告訴main()不是函數並停止鏈接器錯誤?

main()有C鏈接,所以鏈接器無法區分符號的類型。在我們的例子中,_main駐留在數據部分。

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

有[網上的地方](http://d.hatena.ne.jp/qnighy/20090418/1240064403)聲稱這應該在IA32體系結構上工作,因爲'195'是'0xC3',即' RET'指令。仍然看起來很奇怪,但我... –

+1

@FrédéricHamidi,謝謝,很好的發現。 –

+1

@FrédéricHamidi您提供的鏈接聲稱它應該用C而不是C++工作。儘管如此,這兩種語言都是非法的,原因略有不同。在這兩種語言中,你都需要**來定義一個全局函數'main',返回'int',並取一個實現定義的參數集。任何其他'main'的定義都是非法的(並且應該會導致一個好的編譯器投訴,尤其是在C++的情況下,無論如何都必須特別處理'main')。 –

5

這不是一個合法的程序,但我認爲該標準對於診斷是必需的還是未定義的行爲有點含糊不清。 (從實施質量的角度來看,我期待診斷)

+0

關於QOI:可能在實現支持獨立環境的情況下,編譯器本身並不能真正知道目標文件的目的地是什麼環境(即使TU包含不保證獨立存在的頭文件,也允許實現提供他們)。然後鏈接程序不能分辨符號是否是函數。儘管如此,我還是不清楚編譯器內部人員是否知道編譯器應該知道託管與獨立的知識。 –

+2

@SteveJessop如果您爲獨立環境編譯,當然,它的所有實現都已定義。但編譯器應該知道這一點(因爲它必須知道是否要專門處理'main'),並且如果它正在處理函數'main'(特別是不加修飾等),以便從' crt0',那麼它知道全局命名空間中的'main'是特殊的,並且可能會爲示例代碼生成一個錯誤。 –