在 C 中编译和运行没有 main() 的程序
Posted
技术标签:
【中文标题】在 C 中编译和运行没有 main() 的程序【英文标题】:Compile and run program without main() in C 【发布时间】:2017-07-08 18:20:49 【问题描述】:我正在尝试在C
中不使用main()
函数来编译和运行以下程序。我已经使用以下命令编译了我的程序。
gcc -nostartfiles nomain.c
编译器给出警告
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400340
好的,没问题。然后,我运行了可执行文件(a.out),printf
语句都打印成功,然后得到 segmentation fault。
所以,我的问题是,为什么在成功执行打印语句后出现分段错误?
我的代码:
#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 标志阻止编译器在链接时使用标准启动文件
【问题讨论】:
我很惊讶这完全有效。坦率地说,我认为链接器的这种处理是错误的(或者至少是一件坏事):没有入口点,所以链接器只是从任何方便的功能中产生幻觉。布莱赫。 @imallett,至少链接器很友好地通过警告引起了人们的注意,并解释了它正在采取的后备行动!你是对的,不过,这可能是一个错误而不是一个警告更好。 为什么不使用 main? @PieterB - 与有关 unices 的讨论不太相关,但 Windows 程序的入口点不一定是main
,而是 WinMain
或 wWinMain
。
@StoryTeller 实际上在 Windows 和 Linux 中都可以设置任意入口点:对于 Linux 的 ld
它将是 -e
选项,对于 Windows 的 MSVC 链接器它将是 /ENTRY
选项。
【参考方案1】:
让我们看看你的程序生成的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
的返回值。
【讨论】:
我认为有一些架构/操作系统组合,您可以直接从程序中“返回”; MS-DOS .COM 可执行文件?无论如何,我们深入研究特定于实现的行为。 @pjc50 - 我们确实是。尽管 OP 中的路径建议使用 Unix 变体。再加上某些架构和指令集的流行,这是我觉得在答案中呈现生成的程序集很舒服的唯一原因。 只是一个观察。-nostartfiles
也可以使 C 库不可用。如果不执行 C 启动,则对 C 库函数的后续调用可能会意外失败。在 Linux 上,如果您使用 -nostartupfiles
和 -static
进行编译,您可能会发现程序会出错。有像 MUSL 这样的 C 库不需要预先初始化就可以在这种环境中工作。【参考方案2】:
在 C 中,当调用函数/子例程时,堆栈被填充为(按顺序):
-
论据,
退货地址,
局部变量,--> 栈顶
main() 是起点,ELF 以这样一种方式构建程序,即无论什么指令先出现都会先被推送,在这种情况下 printfs 是。
现在,程序在没有返回地址或__end__
的情况下被截断,实际上它假定堆栈上的任何东西(__end__
)位置都是返回地址,但不幸的是它不是,因此它崩溃。
【讨论】:
栈数据的顺序是C标准定义的吗?我认为这取决于系统架构 这就是为什么我提到 ELF(可执行和可链接文件格式),这是通过在所需操作系统上为特定 ARCH 类型交叉编译生成的。 为了挑剔,即使在没有堆栈的系统上,您也可以使用 ELF 格式。这种系统的一个例子是 Freescale RS08 与 Codewarrior 编译器,它生成 ELF 链接器文件。以上是关于在 C 中编译和运行没有 main() 的程序的主要内容,如果未能解决你的问题,请参考以下文章