C与C++程序的启动代码(startup)

C与C++程序的启动代码(startup)
C或者C++语⾔,明⾯上的⼊⼝函数是main(argc,argv),或者tmain、wmain、WinMain等等。进⼀步,很容易获知,是C Runtime的startup代码中的void mainCRTStartup(void)函数,调⽤了编程者写的main函数。这个函数定义在Visual C++安装⽬录的crt\src\⽬录下的某个.c⽂件中(视VC++的版本不同,存放的⽂件也不同)。它在执⾏⼀些初始化操作,如获取命令⾏参数、获取环境变量值、初始化全局变量、初始化io的所需各项准备之后,调⽤main(argc,argv)。main函数返回后,mainCRTStartup还需要调⽤全局变量的析构函数或者atexit()所登记的⼀些函数。
往深⾥说,是在链接⽣成可执⾏⽂件时,告诉链接器这个可执⾏⽂件的entry就是mainCRTStartup。当然,编程者也可以在链接时的命令⾏或者在源程序中指定本程序的entry,例如:
腐蚀与防护
渣油四组分#pragma comment( linker, "/subsystem:windows /entry:WinMainCRTStartup" )
从⽽在可执⾏⽂件执⾏时,操作系统创建进程与主线程后,就从mainCRTStartup开始执⾏主线程。
mainCRTStartup所做的初始化准备⼯作,例如获取命令⾏参数、获取环境变量值,是通过调⽤相应的Windows系统调⽤来实现的。但是,初始化全局变量,就值得多思考了。众多的全局变量按照什么顺序初始化,按照什么顺序销毁?编程者如何指定全局变量初始化的先后顺序?实现机制是什么?答案是MSDN的关于C语⾔预处理pragma指令init_seg的帮助⽂章或者我在百度百科上写的。
简单说,编译器把全局变量的初始化代码的⼊⼝地址当作⼀个⽆参数⽆返回值类型的函数指针(即 void (*fp)()的原型),都依次存放在可执⾏⽂件的.CRT节中,构成了⼀个函数指针表。mainCRTStartup通过调⽤_initterm函数(定义在cinitexe.c⽂件)遍历这个函数指针数组,依次调⽤每个函数指针,完成全局变量的初始化⼯作。
nordost
那么,如何指定全局变量初始化的这些函数地址在.CRT节中存放的顺序呢?这就是链接器的⼀个⼩常识了,对于.CRT$xyz或者.CRT$uvw 这样的section name,在$前⾯的才是链接后的最终的节名,链接器会合并.obj⽂件中这些同名的节;⽽$后⾯的这些字符串,是告诉链接器,按照$后⾯的字符串的字典顺序依次合并这些同名的节。在C Runtime库中已经定义了两个节:.CRT$XCA和.CRT$XCZ作为函数指针表的表头和表尾。编译器默认把全局变量的初始化代码地址放⼊.CRT$XCU中。编程者也可以把⾃⼰写的全局变量的初始化代码的地址放⼊"XCA"与"XCZ"之间。例如:
母语迁移
#pragma init_seg(".CRT$XCM")
那么从这⾏起到编译单元(translate unit)的结束,所有在这个作⽤域内定义的全局变量的初始化代码⼊⼝地址都放在.obj⽂件
的.CRT$XCM节中。如果,另外⼀个编译单元的全局变量初始化代码⼊⼝地址放在了.CRT$XCN节中,那么这两个编译单元在全局变量初始化时就分出了先后,当然是"XCM"在"XCN"之前.
编程时,完全可以⾃⾏模仿上述的算法与数据结构,⾃⼰定义全局变量的初始化与销毁。例如,构建⼀个.myCRT节,作为函数指针表。在定义全局变量时,通过 #pragma init_seg(".myCRT",dtorRegister) 告诉编译器,把全局变量初始化代码⼊⼝地址都填⼊.myCRT节中,其析构代码⼊⼝地址通过调⽤编程者⾃⼰写的dtorRegister函数,登记⼊编程者⾃定义的⼀个全局函数指针数组中。这样,就可以在编程者选定的时机,依次遍历执⾏全局变量初始化,或者依次遍历执⾏全局变量的dtor了。海恩法则
关于C与C++的startup代码,最有权威的就是Matt Pietrek发表在《MSDN Magazine》2001年第1期的《Under the hood: Reduce EXE and DLL size with LIBCTINY.LIB》。他写了⼀个⾮常精简、因此⾮常容易读懂的tiny的C Runtime库,包括了startup,以及printf 这样的C标准库函数的实现,可以与简单的C源程序编译链接为尺⼨超级⼩的可执⾏⽂件,媲美直接⽤汇编写的同样功能的可执⾏⽂件的尺⼨。 此⽂与源代码在⽹上很容易搜到。
读者最后,需要知道,C与C++程序中的main函数具有特别的待遇。cpp⽂件中的main函数,⽆需指出它是extern "C",缺省地就具有了这⼀属性。其次,⽆论是int main(), void main();还是main(argc,argv),main(argc,argv,env);或者在已经定义了_UNICODE宏的情况下,仍然使⽤main函数⽽不是wmain(),都能得到正确的运⾏结果。以前我还发现exit()函数也是很特殊,⽆需头⽂件仍然能编译、运⾏正常。虽然exit()算是声明在STDLIB.H之中。

本文发布于:2024-09-21 22:13:29,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/435129.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议