`
driftcloudy
  • 浏览: 132518 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

从Entry Point到main函数调用(6):exit

    博客分类:
  • C
阅读更多

本章是该系列最后一篇,打算看一下 exit 函数中究竟做了些什么。

 

main函数的返回值

在第(5)篇里完成了_cinit() 的分析之后,mainCRTStartup中接下来代码是:

__initenv = _environ;
mainret = main(__argc, __argv, _environ);
exit(mainret);

很显然, 其实main函数是可以接受第三个参数的,_environ是一个环境变量的指针,只不过一般情况下写程序的时候用不到。从代码中可以看出,调用完main函数后,其返回值mainret会被传递给exit 用作参数。

 

这里首先要解决一个问题,如果main函数的返回值类型是void呢?

 

其实准确说写成void main是不对的T T...根据C99的规定,main的返回类型必须是int,并且如果 main 函数的最后没有写 return 语句,编译器要自动加入 return 0 ,表示程序正常退出。例如:

#include <stdio.h>
void main()
{
	printf("%d",100);
}

利用VS2010进行build,OD进入main函数:

 

注意倒数第二行,这里将EAX清0。其实 main 函数也是一个标准的__cdecl 函数,其return的值会存放在EAX中,因此这里等于会返回一个0 。可见VS2010 这点上还是满足C99 标准的,即使程序员写的是 void main,它依然悄悄的在最后添上 return 0。

 

来看看VC 6,如果用VC 6来build同样一段代码,则main函数为:

很显然,这里并没有将EAX的值清0再retn,但是接下来依然会从EAX 中拿值赋给mainret 。换句话说,用VC6 编译的时候,main函数并不会有默认的返回值,真正传进exit函数的还是main调用完后的EAX值,不过鬼知道这个时候EAX 是什么。这里可以看出 VC6并没有遵循C99的规范,貌似VC6是98年出来的,想想也算情有可原了...

 

 

exit   _exit   _cexit   _c_exit

由于有一系列和 exit 类似的函数,这里一起顺便看下~

void __cdecl exit ( int status )
{
        doexit(status, 0, 0); /* full term, kill process */
}

void __cdecl _exit ( int status)
{
        doexit(status, 1, 0); /* quick term, kill process */
}

void __cdecl _cexit ( void )
{
        doexit(0, 0, 1);    /* full term, return to caller */
}

void __cdecl _c_exit ( void )
{
        doexit(0, 1, 1);    /* quick term, return to caller */
}

在crt0dat.c中定义了上面四个乍一看名字让人很纠结的函数。根据代码中的注释,它们的大概作用为:

  • exit 函数先进行清理工作(比如析构处理、关闭所有标准IO流),然后利用main 函数返回的status 来终结当前进程
  • _exit 函数用于快速终结进程,它并不进行那些“高层次”的清理
  • _cexit 同exit 函数一样执行清理,它并不终结进程
  • _c_exit 同_exit 一样执行清理,它并不终结进程

用通俗的话说,exit 是 _exit 的安全增强版,_cexit是_c_exit 的安全增强版。不过从它们的实现上看,本质上都是 doexit 函数在起作用。在doexit 的内部负责进行各种清理,然后再终结进程或者返还控制权给程序。

 

来看一下doexit 的大概实现,这里忽略了一些条件编译:

// 是否需要终结进程,0表示终结当前进程,1表示返回控制权给程序
char _exitflag = 0;

/*
 * 两个标志
 * 一旦进入了doexit ,_C_Termination_Done会被设置为true
 * 在doexit 完成了所有清理工作后(进入内核之前),_C_Exit_Done 会被设置为true
 */
int _C_Termination_Done = FALSE;
int _C_Exit_Done = FALSE;

static void __cdecl doexit ( int code, int quick, int retcaller )
{
        if (_C_Exit_Done == TRUE)                          /*如果doexit()被递归的调用*/
                TerminateProcess(GetCurrentProcess(),code);/*直接TerminateProcess终结当前进程*/
        _C_Termination_Done = TRUE;

        /* 在执行其他清理的时候可能会用到retcaller,因此先将它赋值给全局变量_exitflag */
        _exitflag = (char) retcaller;  /* 0 = term, !0 = callable exit */

        if (!quick) {
            /*
             * 如果该程序曾经利用_onexit 或者 atexit  注册过函数,那么在退出前需要执行这些函数。
             * 执行的顺序与被注册的顺序相反,即采用LIFO的模式。
             * 利用atexit 来注册函数的时候,内存中会生成一张函数指针列表,
             * __onexitbegin 和__onexitend 分别指向列表的头部和尾部。
             *
             * 注意:
             * 是先从__onexitend指针开始,逐渐向前遍历,直到__onexitbegin,
             * 这样就能确保LIFO的调用顺序。
             */

            if (__onexitbegin) {
                _PVFV * pfend = __onexitend;

                while ( --pfend >= __onexitbegin )
                /*
                 * if current table entry is non-NULL,
                 * call thru it.
                 */
                if ( *pfend != NULL )
                    (**pfend)();
            }

            /*
             * 会进行endstdio之类的操作,进行清理
             */
            _initterm(__xp_a, __xp_z);
        }

        /*
         * 调用C terminators,貌似实际上没调用什么函数
         */
        _initterm(__xt_a, __xt_z);

        /* 如果定义了retcaller,那么需要将控制权返回 */
        if (retcaller) {
            return;
        }

        _C_Exit_Done = TRUE;

        /* 结束进程 */
        ExitProcess(code);
}
 

从上述实现可以看出,如果是对于正常的退出,doexit 进行4个步骤操作:

1. 执行 _onexit 或者 atexit 中已经注册了的函数

2. _initterm(__xp_a, __xp_z)

3. _initterm(__xt_a, __xt_z)

4. ExitProcess(code)

 

析构

如果对象是定义在一个函数的内部,相当于局部变量,那么在函数调用结束之前,会自动析构该对象。

如果是一个全局对象,那么析构其实运行在上面4个步骤中的第1步,即调用_onexit、atexit 注册过的函数时发生。

可以用一段简单的示例代码来说明这些问题:

#include <stdio.h>
#include <stdlib.h>

typedef struct foo1 {
	foo1() { printf("1"); }
	~foo1() { printf("2"); }
	static void bar() { printf("3"); }
} Foo1;

typedef struct foo2 {
	foo2() { printf("4"); }
	~foo2() { printf("5"); }
} Foo2;

Foo1 f1;

void main()
{
	Foo2 f2;
	atexit(&Foo1::bar);
}

这是一段C++代码,因为C中的struct是不被允许定义方法的。最终的输出结果是:

运行结果
14532
 

这段示例代码中定义了两个变量,全局变量f1和局部变量f2,并且利用atexit注册了一个函数bar 。

 

根据第(5)篇中的描述,f1 的初始化工作在_cinit 函数中调用_initterm( __xc_a, __xc_z )时完成,至于f2 的初始化,肯定是在运行至main函数中Foo2 f2 一句时才开始进行。当main函数中的语句都执行完毕(此时尚未退出main函数),开始对f2 执行析构。析构完毕随后就退出main 调用,进入exit----> doexit,开始上述的4个步骤。在第1步中会运行注册的bar函数,然后调用f1 的析构函数,在第2步中调用endstdio 关闭IO,第3步没做啥,第4步ExitProcess。

 

因此从 cinit ----> main ----> exit 大概发生的事情顺序如下所示:

 

 

 

 

 

 

 

0
0
分享到:
评论

相关推荐

    《你必须知道的495个C语言问题》

    可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 12  数组大小 13 1.23 能否声明和传入数组大小一致的局部数组,或者由...

    C语言FAQ 常见问题列表

    o 5.8 我看到了用指针调用函数的不同语法形式。到底怎么回事? o 5.9 我怎样把一个 int 变量转换为 char * 型?我试了类型转换, 但是不行。 * 6. 空 (null) 指针 o 6.1 臭名昭著的空指针到底是什么? o 6.2 ...

    你必须知道的495个C语言问题(PDF)

    4.8 我看到了用指针调用函数的不同语法形式。到底怎么回事? . . . 19 4.9 我怎样把一个int 变量转换为char * 型?我试了类型转换, 但是不 行。. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...

    C# for CSDN 乱七八糟的看不懂

    没法下载,到这里折腾一把试试。 本文由abc2253130贡献 doc文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到本机查看。 C#(WINFORM)学习 一、 C#基础 基础 类型和变量 类型和变量 类型 C# 支持两...

    避开10大常见坑:DeepSeekAPI集成中的错误处理与调试指南.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    前端分析-2023071100789

    前端分析-2023071100789

    基于kinect的3D人体建模C++完整代码.cpp

    基于kinect的3D人体建模C++完整代码.cpp

    搞机工具箱10.1.0.7z

    搞机工具箱10.1.0.7z

    GRU+informer时间序列预测(Python完整源码和数据)

    GRU+informer时间序列预测(Python完整源码和数据),python代码,pytorch架构,适合各种时间序列直接预测。 适合小白,注释清楚,都能看懂。功能如下: 代码基于数据集划分为训练集测试集。 1.多变量输入,单变量输出/可改多输出 2.多时间步预测,单时间步预测 3.评价指标:R方 RMSE MAE MAPE,对比图 4.数据从excel/csv文件中读取,直接替换即可。 5.结果保存到文本中,可以后续处理。 代码带数据,注释清晰,直接一键运行即可,适合新手小白。

    性价比革命:DeepSeekAPI成本仅为GPT-4的3%的技术揭秘.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

    基于ANSYS LSDyna的DEM-SPH-FEM耦合模拟滑坡入水动态行为研究,基于ANSYS LSDyna的DEM-SPH-FEM耦合的滑坡入水模拟分析研究,基于ansys lsdyna的滑坡入水

    基于ANSYS LSDyna的DEM-SPH-FEM耦合模拟滑坡入水动态行为研究,基于ANSYS LSDyna的DEM-SPH-FEM耦合的滑坡入水模拟分析研究,基于ansys lsdyna的滑坡入水模拟dem-sph-fem耦合 ,基于ANSYS LSDyna; 滑坡入水模拟; DEM-SPH-FEM 耦合,基于DEM-SPH-FEM耦合的ANSYS LSDyna滑坡入水模拟

    auto_gptq-0.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

    auto_gptq-0.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

    复件 复件 建设工程可行性研究合同[示范文本].doc

    复件 复件 建设工程可行性研究合同[示范文本].doc

    13考试真题最近的t64.txt

    13考试真题最近的t64.txt

    Microsoft Visual C++ 2005 SP1 Redistributable PackageX86

    好用我已经解决报错问题

    嵌入式开发入门:用C语言点亮LED灯的全栈开发指南.pdf

    # 踏入C语言的奇妙编程世界 在编程的广阔宇宙中,C语言宛如一颗璀璨恒星,以其独特魅力与强大功能,始终占据着不可替代的地位。无论你是编程小白,还是有一定基础想进一步提升的开发者,C语言都值得深入探索。 C语言的高效性与可移植性令人瞩目。它能直接操控硬件,执行速度快,是系统软件、嵌入式开发的首选。同时,代码可在不同操作系统和硬件平台间轻松移植,极大节省开发成本。 学习C语言,能让你深入理解计算机底层原理,培养逻辑思维和问题解决能力。掌握C语言后,再学习其他编程语言也会事半功倍。 现在,让我们一起开启C语言学习之旅。这里有丰富教程、实用案例、详细代码解析,助你逐步掌握C语言核心知识和编程技巧。别再犹豫,加入我们,在C语言的海洋中尽情遨游,挖掘无限可能,为未来的编程之路打下坚实基础!

    auto_gptq-0.4.2-cp38-cp38-win_amd64.whl

    auto_gptq-0.4.2-cp38-cp38-win_amd64.whl

    自动立体库设计方案.pptx

    自动立体库设计方案.pptx

    手把手教你用C语言实现贪吃蛇游戏:从算法设计到图形渲染.pdf

    # 踏入C语言的奇妙编程世界 在编程的广阔宇宙中,C语言宛如一颗璀璨恒星,以其独特魅力与强大功能,始终占据着不可替代的地位。无论你是编程小白,还是有一定基础想进一步提升的开发者,C语言都值得深入探索。 C语言的高效性与可移植性令人瞩目。它能直接操控硬件,执行速度快,是系统软件、嵌入式开发的首选。同时,代码可在不同操作系统和硬件平台间轻松移植,极大节省开发成本。 学习C语言,能让你深入理解计算机底层原理,培养逻辑思维和问题解决能力。掌握C语言后,再学习其他编程语言也会事半功倍。 现在,让我们一起开启C语言学习之旅。这里有丰富教程、实用案例、详细代码解析,助你逐步掌握C语言核心知识和编程技巧。别再犹豫,加入我们,在C语言的海洋中尽情遨游,挖掘无限可能,为未来的编程之路打下坚实基础!

    性能对决:DeepSeek-V3与ChatGPTAPI在数学推理场景的基准测试.pdf

    在日常的工作和学习中,你是否常常为处理复杂的数据、生成高质量的文本或者进行精准的图像识别而烦恼?DeepSeek 或许就是你一直在寻找的解决方案!它以其高效、智能的特点,在各个行业都展现出了巨大的应用价值。然而,想要充分发挥 DeepSeek 的优势,掌握从入门到精通的知识和技能至关重要。本文将从实际应用的角度出发,为你详细介绍 DeepSeek 的基本原理、操作方法以及高级技巧。通过系统的学习,你将能够轻松地运用 DeepSeek 解决实际问题,提升工作效率和质量,让自己在职场和学术领域脱颖而出。现在,就让我们一起开启这场实用又高效的学习之旅吧!

Global site tag (gtag.js) - Google Analytics