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

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

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

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

#include <stdio.h>

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 <stdio.h>

__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 <iostream>

using namespace std;

inline int startup_1()
{
    cout<<"startup_1 run"<<endl;
    return 0;
}



int static no_use_variable_startup_1 = startup_1();

int main(int argc, const char * argv[]) 
{
    cout<<"this is main"<<endl;
    return 0;
}

  至此,我们就聊完了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<stdio.h>
#include<stdlib.h>

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 " );
}

猜你喜欢

转载自blog.csdn.net/z_ryan/article/details/80985101