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


125

我从同事那里得知,无需编写main()函数就可以编写和执行C程序。可以这样完成:

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

什么时候需要做这种事情?在现实世界中是否有任何有用的场景?



7
经典文章,展示了程序启动的一些内部工作原理:关于为Linux创建真正的TeenF ELF可执行文件的旋风教程。这是一本不错的读物,讨论了_start()和之外的其他一些优点main()

1
C语言本身对_start或任何入口点一无所知main(除了入口点的名称是针对独立(嵌入式)实现的实现定义的)。
基思·汤普森

Answers:


107

该符号_start是程序的入口。也就是说,该符号的地址是程序启动时跳转到的地址。通常,具有该名称的函数_start由一个名为的文件提供,该文件crt0.o包含C运行时环境的启动代码。它设置一些东西,填充参数数组argv,计算那里有多少个参数,然后调用main。后main返回时,exit被调用。

如果程序不想使用C运行时环境,则需要为其提供自己的代码_start。例如,Go编程语言的参考实现是这样做的,因为它们需要一个非标准的线程模型,该模型要求堆栈具有一定的魔力。_start当您要编写非常小的程序或执行非常规操作的程序时,提供自己的文件也很有用。


2
另一个例子是Linux的动态链接器/加载器,它定义了自己的_start。
PP

2
@BlueMoon但这也_start来自目标文件crt0.o
fuz 2015年

2
@ThomasMatthews标准未指定_start;实际上,它根本没有指定main调用之前会发生什么,而只是指定了main调用时必须满足的条件。入口点可以_start追溯到过去,这更像是一种约定。
2015年

1
“ Go编程语言的参考实现之所以这样做,是因为它们需要非标准的线程模型”,crt0.o是C特定的(crt-> C运行时)。没有理由期望它可以用于任何其他语言。和GO的线程模型是完全符合标准的
史蒂夫考克斯

8
@SteveCox许多编程语言都建立在C运行时之上,因为用这种方式实现语言更容易。Go不使用普通的线程模型。他们使用小的,堆分配的堆栈和自己的调度程序。这当然不是标准的线程模型。
fuz 2015年

45

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

在典型的C语言尤其是C ++程序中,在执行进入main之前已经完成了很多工作。特别是诸如全局变量初始化之类的东西。 在这里,您可以找到一个很好的解释,它之间_start()以及main()之后main再次退出之后发生的一切(请参见下面的评论)。
所需的代码通常由编译器编写者在启动文件中提供,但是带有标志,–nostartfiles您实际上会告诉编译器:“不要费心给我标准启动文件,让我完全控制启动时发生的事情。开始”。

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


全局变量是数据部分的一部分,因此在加载程序时进行设置(如果它们是const,则它们是文本部分的一部分,同一个故事)。_start函数与此完全无关。
Cheiron

@Cheiron:对不起,我的表现在C ++中,全局变量通常由内部运行的构造_start()函数(或实际上由它调用的另一个函数)初始化,在许多Bare-Metal-Programs中,您将所有全局数据从闪存显式复制到RAM首先,这也发生在中_start(),但是这个问题既与c ++或裸机代码无关。
MikeMB

1
请注意,在提供自己的程序中,_start除非您采取特殊措施自行完成C库,否则不会初始化C库-使用此类程序中的任何非异步信号安全函数可能都是不安全的。(没有官方保证任何库函数都能正常工作,但异步信号安全函数根本无法引用任何全局数据,因此它们必须竭尽全力进行故障处理。)
zwol

@zwol只是部分正确。例如,这种功能可能会分配内存。当内部数据结构malloc未初始化时,分配内存会出现问题。
2015年

1
@FUZxxl话虽如此,我注意到,异步信号安全功能可以修改errno(例如,readwrite是异步信号安全的,并且可以设定errno),当每个线程可以令人信服地依赖于准确的问题errno是分配位置。
zwol 2015年

2

以下是有关在程序启动之前 发生什么的很好概述main。特别是,它表明从OS角度来看,这__start是程序的实际入口点

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

那里的代码调用一些C运行时库例程只是为了做一些内务处理,然后调用your main,然后放下东西并exit使用main返回的任何退出代码进行调用。


一张图片胜过千言万语:

C运行时启动图


PS:这个答案是从另一个问题移植而来的,因此SO作为这个问题的副本已帮助解决了这个问题。


交叉发布以保留出色的分析和优美的画面。
ulidtko

1

什么时候需要做这种事情?

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

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函数并将其更改为其他内容:

不,我认为这样做没有任何好处。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.