赞
踩
CRT(C Runtime)是指C运行时库,它为C和C++程序提供了一组初始化和终止程序的基本构建块。这些构建块确保在main()
函数执行之前和之后进行适当的初始化和清理。
CRT的主要任务包括:
main()
之前被调用。malloc
和new
)。main()
函数退出或调用exit()
时,确保适当地调用全局和静态对象的析构函数(在C++中)。当编译和链接一个程序时,链接器将自动选择正确的CRT文件,以确保程序的生命周期管理正确。如果使用特定的编译和链接选项,如-fPIC
和-pie
,链接器可能会选择不同的CRT文件,如Scrt1.o
而不是crt1.o
,以支持这些选项。
为了更好地理解这些文件是如何工作的,可以考虑它们为程序的生命周期提供了一个框架:从程序的开始,到 main 函数的执行,再到程序的结束,每个阶段都有相应的初始化和清理工作需要完成。这些 crt 文件就是为此目的而存在的。
当使用C或C++编译器(如 gcc
或 g++
)来编译和链接一个程序时,C运行时的这些组件会按照特定的顺序被包含在生成的可执行文件中,以确保全局对象、构造函数、析构函数和程序的main()
函数在正确的顺序中执行。
以下是这些组件在链接过程中的一般顺序:
crti.o:
.init
段的开头。crt1.o/Scrt1.o:
_start
。_start
是程序的启动点,它会进行一些基本的初始化,然后调用全局的构造函数,接着调用main()
,最后调用全局的析构函数。Scrt1.o
是用于位置无关代码的版本,通常在动态共享库中使用。crtbegin.o/crtbeginS.o:
crtbeginS.o
是用于位置无关代码的版本。你的代码:
main()
函数和其他全局/静态对象的定义都在这里。crtend.o/crtendS.o:
crtbegin.o
有一个与之相对应的版本crtbeginS.o
,crtendS.o
是crtend.o
的位置无关代码版本。crtn.o:
.fini
段的结尾。在链接过程中,链接器确保按照这个顺序包含这些组件,从而确保程序在运行时具有正确的初始化和清理顺序。
注意: 在不同的系统、编译器版本和配置中,具体的文件名和顺序可能会有所不同。上述描述是基于通用的和常见的行为,但具体细节可能会根据环境而有所变化。
crt1.o
和 Scrt1.o
是C运行时(CRT, C Runtime)的一部分,它们定义了程序的真正的入口点——_start
。尽管当我们编写C程序时通常会以 main()
作为起点,但实际上在进入 main()
之前还会执行很多初始化操作,这些操作由这些运行时对象文件中的代码进行。
下面是一个简化的、高层次的说明:
编写一个简单的C程序,例如:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
当编译并链接此程序时,链接器除了链接我们的代码外,还会链接C运行时的一部分。这确保了 _start
是实际的程序入口点。
当程序开始执行时,它首先进入 _start
。
_start
负责执行多种初始化任务,例如设置堆栈,初始化全局变量,调用全局构造函数等。_start
调用 main()
函数。main()
函数执行,打印 “Hello, World!”。main()
返回后,控制权返回给 _start
,它接着负责调用全局析构函数并执行其他清理任务。_start
调用系统调用以退出程序。这就是为什么,如果我们使用工具(如 objdump
或 nm
)查看一个编译好的可执行文件,会看到除了 main
之外还有其他符号和入口点,如 _start
。
请注意,crt1.o
和 Scrt1.o
(及其相关的CRT文件)的具体行为和内容可能会根据操作系统、编译器和系统架构而异。
crti.o
和 crtn.o
是 C 运行时的组件,它们为全局构造函数和析构函数的初始化和清理提供所需的框架。这些构造函数和析构函数不应与 C++ 的类构造函数和析构函数混淆;在这里,我们指的是全局和静态对象的初始化和终止函数。
在 ELF(可执行和链接格式)系统上,例如大多数 Unix-like 系统,crti.o
和 crtn.o
提供了 .init
和 .fini
段的前导和后继代码。它们确保正确地设置和执行构造函数和析构函数。
如何工作?
crti.o
提供 .init
和 .fini
段的开头部分。crtn.o
提供 .init
和 .fini
段的结尾部分。现在,我们来看一个简化的例子:
#include <stdio.h>
void __attribute__((constructor)) my_constructor(void) {
printf("Before main()\n");
}
void __attribute__((destructor)) my_destructor(void) {
printf("After main()\n");
}
int main(void) {
printf("Inside main()\n");
return 0;
}
在这个示例中,我们定义了一个构造函数 my_constructor
和一个析构函数 my_destructor
。这些函数分别在 main()
函数之前和之后执行。
当编译并运行此程序时,输出应如下:
Before main()
Inside main()
After main()
这就是 crti.o
和 crtn.o
起作用的地方:
crti.o
提供了 .init
段的开头,这段代码确保 my_constructor
在 main()
之前执行。crtn.o
提供了 .fini
段的结束部分,这段代码确保 my_destructor
在 main()
之后执行。在实际的系统中,还有其他机制和细节确保了构造函数和析构函数的正确执行顺序,以及与其他库和组件的互操作性。但从高层次来看,crti.o
和 crtn.o
提供了为这些函数设置和执行所需的基础框架。
当在C++中使用静态对象或全局对象,这些对象的构造函数和析构函数需要在程序的main()
函数执行前后被调用。crtbegin.o
, crtbeginS.o
, crtend.o
, 和 crtendS.o
这些文件正是负责这些操作。
crtbegin.o 和 crtbeginS.o
crtbeginS.o
特别用于生成位置无关代码(PIC, Position-Independent Code),这在动态共享对象(如.so文件)中是必需的。crtend.o 和 crtendS.o
crtendS.o
特别用于支持位置无关代码。示例
假设我们有一个简单的C++程序:
#include <iostream> class MyClass { public: MyClass() { std::cout << "Constructor called!" << std::endl; } ~MyClass() { std::cout << "Destructor called!" << std::endl; } }; MyClass globalObject; // 全局对象 int main() { std::cout << "Inside main()" << std::endl; return 0; }
当这个程序启动时,我们希望首先看到“Constructor called!”,接着是“Inside main()”,最后是“Destructor called!”。
这正是 crtbegin.o
和 crtend.o
(或它们的PIC版本)的作用:它们确保在进入main()
之前调用全局对象的构造函数,而在main()
之后调用全局对象的析构函数。
为了达到这一目的,crtbegin.o
创建了一个指向所有构造函数的列表,并确保在main()
前调用它们;而 crtend.o
则为析构函数做了同样的事情,但是在main()
后。
这是为什么当我们使用g++或其他C++编译器链接C++程序时,这些CRT对象文件会自动被包括在内,以确保正确的程序初始化和终止顺序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。