在 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,而是 WinMainwWinMain @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() 的程序的主要内容,如果未能解决你的问题,请参考以下文章

C语言程序是如何执行的

为啥C程序多文件编译,没有导入自己的头文件也能正常编译通过?

C程序从编译到运行

eclipse 运行不了程序 点运行后显示:

用DEV C++ 编译好不能运行怎么办

在linux下怎么运行main函数