C 中_start()的用途是什么?

我从我的同事那里了解到,可以编写和执行一个 C 程序,而不需要编写 main()函数。可以这样做:

my_main.c

/* Compile this with gcc -nostartfiles */


#include <stdlib.h>


void _start() {
int ret = my_main();
exit(ret);
}


int my_main() {
puts("This is a program without a main() function!");
return 0;
}

用以下命令编译:

gcc -o my_main my_main.c –nostartfiles

用以下命令运行它:

./my_main

什么时候需要做这种事?在现实世界中,这种方法是否有用?

38546 次浏览

从程序员的角度来看,main是程序的入口点,而从操作系统的角度来看,_start是通常的入口点(程序从操作系统启动后执行的第一条指令)

在一个典型的 C,特别是 C + + 程序中,在执行进入 main 之前已经完成了大量的工作。特别是像全局变量初始化这样的东西。 给你你可以找到一个很好的解释之间发生的一切 _start()main(),也主要已经退出后再次(见下面的评论)。
必要的代码通常是由编译器的作者在一个启动文件中提供的,但是如果标记为 –nostartfiles,你实际上就是在告诉编译器: “不用麻烦给我标准的启动文件,让我从一开始就完全控制正在发生的事情。”。

这有时是必要的,并经常用于嵌入式系统。例如,如果你没有操作系统,在初始化全局对象之前,你必须手动启用内存系统的某些部分(比如缓存)。

符号 _start是程序的 入口。也就是说,这个符号的地址就是程序启动时跳转到的地址。通常,名为 _start的函数由一个名为 crt0.o的文件提供,该文件包含 C 执行期函式库的启动代码。它设置一些内容,填充参数数组 argv,计算有多少个参数,然后调用 main。返回 main之后,调用 exit

如果一个程序不想使用 C 语言执行期函式库,它需要提供自己的代码。例如,Go 编程语言的参考实现之所以这样做,是因为它们需要一个非标准的线程模型,这需要一些堆栈魔术。当您想要编写非常小的程序或者执行非常规操作的程序时,提供您自己的 _start也很有用。

什么时候需要做这种事?

当您需要自己的程序启动代码时。

main不是 C 程序的第一个入口,_start是幕后的第一个入口。

Linux 的例子:

_start: # _start is the entry point known to the linker
xor %ebp, %ebp            # effectively RBP := 0, mark the end of stack frames
mov (%rsp), %edi          # get argc from the stack (implicitly zero-extended to 64-bit)
lea 8(%rsp), %rsi         # take the address of argv from the stack
lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
xor %eax, %eax            # per ABI and compatibility with icc
call main                 # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main


mov %eax, %edi    # transfer the return of main to the first argument of _exit
xor %eax, %eax    # per ABI and compatibility with icc
call _exit        # terminate the program

在现实世界中,这种方法是否有用?

如果你的意思是,实现我们自己的 _start:

是的,在我工作过的大多数商业嵌入式软件中,我们需要根据我们特定的内存和性能要求实现我们自己的 _start

如果您的意思是,删除 main函数,并将其改为其他函数:

不,我不觉得这样做有什么好处。

这里 很好地概述了程序启动 之前 main期间发生的情况。特别是,从操作系统的角度来看,__start是程序的 实际的入口点

这是第一个地址,从 指令指针将开始计数在您的程序。

这里的代码调用一些 C 运行时库例程来做一些内部管理,然后调用您的 main,然后调用返回退出代码 mainexit


一张图片胜过千言万语:

C runtime startup diagram


附注: 这个答案是从 另一个问题移植过来的。