我试图在main()
中编译并运行以下没有C
函数的程序。我使用以下命令编译了我的程序。
gcc -nostartfiles nomain.c
编译器发出警告
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400340
好的,没问题。然后,我运行了可执行文件(a.out),两个printf
语句成功打印,然后获得分段错误。
所以,我的问题是,为什么在成功执行print语句后出现分段错误?
我的代码:
#include <stdio.h>
void nomain()
{
printf("Hello World...\n");
printf("Successfully run without main...\n");
}
输出:
Hello World...
Successfully run without main...
Segmentation fault (core dumped)
注意:
这里,-nostartfiles
gcc标志阻止编译器在链接时使用标准启动文件
答案 0 :(得分:127)
让我们看看你的程序生成的assembly:
.LC0:
.string "Hello World..."
.LC1:
.string "Successfully run without main..."
nomain:
push rbp
mov rbp, rsp
mov edi, OFFSET FLAT:.LC0
call puts
mov edi, OFFSET FLAT:.LC1
call puts
nop
pop rbp
ret
请注意ret
声明。您的计划的入口点被确定为nomain
,一切都很好。但是一旦函数返回,它就会尝试跳转到调用堆栈上的一个地址......但是没有填充。这是非法访问和分段错误。
快速解决方案是在程序结束时调用exit()
(假设C11我们也可以将函数标记为_Noreturn
):
#include <stdio.h>
#include <stdlib.h>
_Noreturn void nomain(void)
{
printf("Hello World...\n");
printf("Successfully run without main...\n");
exit(0);
}
事实上,现在您的函数的行为非常类似于常规main
函数,因为从main
返回后,exit
函数将使用main
&#39调用; s返回值。
答案 1 :(得分:21)
在C中,当调用函数/子例程时,堆栈被填充为(按顺序):
main()是起点,ELF以这样一种方式构造程序,即首先推送的任何指令都会先被推送,在这种情况下是printfs。
现在,程序被截断,没有返回地址OR __end__
,事实上它假设堆栈上那个(__end__
)位置的任何地方都是返回地址,但不幸的是它不会因此而崩溃。