c语言main函数的顺序,深度剖析c语言main函数---main函数的执行顺序

在之前的文章中,介绍了main函数的返回值 和 main函数的传参,本文主要介绍一下main函数的执行顺序。可能有的人会说,这还用说,main函数肯定是程序执行的第一个函数。那么,事实果然如此吗?相信在看了本文之后,会有不一样的认识。

为什么说main()是程序的入口

linux系统下程序的入口是”_start”,这个函数是linux系统库(Glibc)的一部分,当我们的程序和Glibc链接在一起形成最终的可执行文件的之后,这个函数就是程序执行初始化的入口函数。

通过一个测试程序来说明:

#include

int main()

{

printf("Hello world\n");

return 0;

}

编译:

gcc testmain.c -nostdlib     # -nostdlib (不链接标准库)

程序执行会引发错误:/usr/bin/ld: warning: cannot find entry symbol _start; 未找到这个符号

所以说:1. 编译器缺省是找 __start 符号,而不是 main

2. __start 这个符号是程序的起始

3. main 是被标准库调用的一个符号

那么,这个_start和main函数有什么关系呢?下面我们来进行进一步探究。

_start函数的实现

该入口是由ld链接器默认的链接脚本指定的,当然用户也可以通过参数进行设定。_start由汇编代码实现。大致用如下伪代码表示:

void _start()

{

%ebp = 0;

int argc = pop from stack

char ** argv = top of stack;

__libc_start_main(main, argc, argv, __libc_csu_init, __linc_csu_fini,

edx, top of stack);

}

对应的汇编代码如下:

_start:

xor ebp, ebp //清空ebp

pop esi //保存argc,esi = argc

mov esp, ecx //保存argv, ecx = argv

push esp //参数7保存当前栈顶

push edx //参数6

push __libc_csu_fini//参数5

push __libc_csu_init//参数4

push ecx //参数3

push esi //参数2

push main//参数1

call _libc_start_main

hlt

可以看出,在调用_start之前,装载器就会将用户的参数和环境变量压入栈中。

main函数运行之前的工作

从_start的实现可以看出,main函数执行之前还要做一系列的工作。主要就是初始化系统相关资源:

Some of the stuff that has to happen before main():

set up initial stack pointer

initialize static and global data

zero out uninitialized data

run global constructors

Some of this comes with the runtime library's crt0.o file or its __start() function. Some of it you need to do yourself.

Crt0 is a synonym for the C runtime library.

1.设置栈指针

2.初始化static静态和global全局变量,即data段的内容

3.将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容

4.运行全局构造器,类似c++中全局构造函数

5.将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数

main之前运行的代码

下面,我们就来说说在mian函数执行之前到底会运行哪些代码:

(1)全局对象的构造函数会在main 函数之前执行。

(2)一些全局变量、对象和静态变量、对象的空间分配和赋初值就是在执行main函数之前,而main函数执行完后,还要去执行一些诸如释放空间、释放资源使用权等操作

(3)进程启动后,要执行一些初始化代码(如设置环境变量等),然后跳转到main执行。全局对象的构造也在main之前。

(4)通过关键字attribute,让一个函数在主函数之前运行,进行一些数据初始化、模块加载验证等。

示例代码

①、通过关键字attribute

#include

__attribute__((constructor)) void before_main_to_run()

{

printf("Hi~,i am called before the main function!\n");

printf("%s\n",__FUNCTION__);

}

__attribute__((destructor)) void after_main_to_run()

{

printf("%s\n",__FUNCTION__);

printf("Hi~,i am called after the main function!\n");

}

int main( int argc, char ** argv )

{

printf("i am main function, and i can get my name(%s) by this way.\n",__FUNCTION__);

return 0;

}

②、全局变量的初始化

#include

using namespace std;

inline int startup_1()

{

cout<

}

int static no_use_variable_startup_1 = startup_1();

int main(int argc, const char * argv[])

{

cout<

}

至此,我们就聊完了main函数执行之前的事情,那么,你是否还以为main函数也是程序运行的最后一个函数呢?结果当然不是,在main函数运行之后还有其他函数可以执行,main函数执行完毕之后,返回到入口函数,入口函数进行清理工作,包括全局变量析构、堆销毁、关闭I/O等,然后进行系统调用结束进程。

main函数之后执行的函数

1、全局对象的析构函数会在main函数之后执行;

2、用atexit注册的函数也会在main之后执行。

atexit函数

原形:

int atexit(void (*func)(void));

atexit 函数可以“注册”一个函数,使这个函数将在main函数正常终止时被调用,当程序异常终止时,通过它注册的函数并不会被调用。编译器必须至少允许程序员注册32个函数。如果注册成功,atexit 返回0,否则返回非零值,没有办法取消一个函数的注册。在 exit 所执行的任何标准清理操作之前,被注册的函数按照与注册顺序相反的顺序被依次调用。每个被调用的函数不接受任何参数,并且返回类型是 void。被注册的函数不应该试图引用任何存储类别为 auto 或 register 的对象(例如通过指针),除非是它自己所定义的。多次注册同一个函数将导致这个函数被多次调用。

函数调用的最后的操作就是出栈过程。main()同样也是一个函数,在结束时,按出栈的顺序调用使用atexit函数注册的,所以说,函数atexit是注册的函数和函数入栈出栈一样,是先进后出的,先注册的后执行。

通过atexit可以注册回调清理函数。可以在这些函数中加入一些清理工作,比如内存释放、关闭打开的文件、关闭socket描述符、释放锁等等。

示例代码

#include

#include

void fn0( void ), fn1( void ), fn2( void ), fn3( void ), fn4( void );

int main( void )

{

//注意使用atexit注册的函数的执行顺序:先注册的后执行

atexit( fn0 );

atexit( fn1 );

atexit( fn2 );

atexit( fn3 );

atexit( fn4 );

printf( "This is executed first.\n" );

printf("main will quit now!\n");

return 0;

}

void fn0()

{

printf( "first register ,last call\n" );

}

void fn1(

{

printf( "next.\n" );

}

void fn2()

{

printf( "executed " );

}

void fn3()

{

printf( "is " );

}

void fn4()

{

printf( "This " );

}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值