我们在链接脚本在编程中的高级运用之一可变长数组中已经讲述了编译链接的原理,并且以uboot命令为例详细介绍链接脚本如何实现可变长数组。本章在前者的基础上继续讲述链接脚本在运行时库中的高级应用技巧,以及编译器如何支持类对象的构造和析构函数。本章的应用原则上类似于可变长数组,但本章更加侧重讲述运行时库的实现原理,其不仅通过链接脚本的section来实现可变长数组去支持任意多类对象的构造函数和析构函数,而且还支持特定函数体的“可变长”。
一、运行时库和类对象的构造、析构函数
很多程序员以为程序的开始就是main,事实上main只是程序的中间的一部分,在main之前和之后都要完成很多工作。其中就包括以下几个主要的部分:
-
类对象的构造函数需要在main函数执行前完成,而类对象的析构函数需要在main函数执行后完成。
-
我们都知道现代操作系统都有多进程多线程的概念,而main函数怎么没看到相关的数据结构呢?这些都是运行时库的工作。
-
程序里面可以直接printf将数据输出到0对应的显示控制台,这个设备文件怎么初始化的,是不是应该先初始化和先打开才能用的。
运行时库才是程序的真正开始和真正结束的地方。本章重点链接脚本如何支持类对象的构造和析构函数。其他特性内容分析留待以后再做讲解。
以下是基于X86架构的ubuntu64平台对Glibc的运行时库进行分析。
二、程序演示例程
1.程序
2.运行结果
给某函数添加__attribute__((constructor))属性,编译器会将该函数名指针添加到名为.ctors的section, 添加__attribute__((destructor))属性,则会将函数名指针添加到.dtors。即是将函数名地址添加到.ctors和.dtors指示的可变长数组。记住,只是函数名地址,而不是函数体。
另外,classTest()是类classTest的同名函数,是构造函数,编译器也会将该函数地址填入到.ctors,即编译器判定某个函数为类的构造函数后,自动给该函数添加__attribute__((constructor))属性;同理,对析构函数~classTest()添加__attribute__((destructor))属性,将该函数地址加入.dtors。