从这个问题中,我看到了一个有趣的代码,它编译(尽管有警告)并产生分段错误(gcc 4.4.4;clang 2.8):
main;
如果我们扩展它,结果是:
int main = 0;
那么链接器在这里的行为是什么呢?
main
的符号。它有4个字节长,初始化为0。通常,它会在程序的代码段(通常称为.text
)中创建一个符号,其中包含main
函数的可执行代码。
C运行时在一个固定的入口点(通常称为_start
)启动,初始化一堆东西(例如设置程序的参数),并调用main
函数。当main
是可执行代码时,这一切都很好,但如果它是4个零字节,程序将把控制权转移到这些零字节并尝试执行它们。
通常,数据和BSS段被标记为不可执行,因此当你试图在那里执行代码时,处理器会引发一个异常,操作系统会对此进行解释,然后用信号终止你的程序。如果它所在的段是可执行的,那么它将尝试执行00 00 00 00
定义的机器指令。在x86和x86-64中,这是一条非法指令,因此在POSIX操作系统中也会得到SIGILL
信号。
在我的系统(CentOS 6.3)下,main被放入BSS并包含所有0,因此崩溃:
Program received signal SIGSEGV, Segmentation fault.
0x00000000006007f0 in main ()
(gdb) where
#0 0x00000000006007f0 in main ()
(gdb) l
"main" is not a function
(gdb) disass 0x6007f0
Dump of assembler code for function main:
=> 0x00000000006007f0 <+0>: add %al,(%rax)
0x00000000006007f2 <+2>: add %al,(%rax)
End of assembler dump.
(gdb) info symbol &main
main in section .bss of /home/ajd/tmp/x
(gdb) x/16b 0x6007f0
0x6007f0 <main>: 0 0 0 0 0 0 0 0
0x6007f8: 0 0 0 0 0 0 0 0
(gdb)
符号main
应为函数,而不是整数。然而,链接器并不太关心main
的类型;符号被定义。如果符号main
不是具有指定签名之一的函数,则调用未定义行为。
然后启动代码调用"函数",它实际上是程序数据段中的一个地址。它出错是因为存储在该地址的"代码"无效——前4个字节可能是零;以后会发生什么是任何人的猜测。数据段可能被标记为不可执行,在这种情况下,试图执行数据将触发该帐户崩溃。
当您调用未定义的行为时,任何事情都可能发生。在这里撞车是很明智的。