当前位置:   article > 正文

C运行时库- CRT(C Runtime)_crt运行库

crt运行库

基本概念

CRT(C Runtime)是指C运行时库,它为C和C++程序提供了一组初始化和终止程序的基本构建块。这些构建块确保在main()函数执行之前和之后进行适当的初始化和清理。

CRT的主要任务包括:

  1. 初始化静态数据:分配和初始化静态和全局变量。
  2. 调用全局构造函数:在C++中,全局或静态对象的构造函数需要在main()之前被调用。
  3. 设置堆:对于动态内存分配(如mallocnew)。
  4. 处理程序终止:当main()函数退出或调用exit()时,确保适当地调用全局和静态对象的析构函数(在C++中)。

当编译和链接一个程序时,链接器将自动选择正确的CRT文件,以确保程序的生命周期管理正确。如果使用特定的编译和链接选项,如-fPIC-pie,链接器可能会选择不同的CRT文件,如Scrt1.o而不是crt1.o,以支持这些选项。

为了更好地理解这些文件是如何工作的,可以考虑它们为程序的生命周期提供了一个框架:从程序的开始,到 main 函数的执行,再到程序的结束,每个阶段都有相应的初始化和清理工作需要完成。这些 crt 文件就是为此目的而存在的。

C运行时组件在链接过程中的一般顺序

当使用C或C++编译器(如 gccg++)来编译和链接一个程序时,C运行时的这些组件会按照特定的顺序被包含在生成的可执行文件中,以确保全局对象、构造函数、析构函数和程序的main()函数在正确的顺序中执行。

以下是这些组件在链接过程中的一般顺序:

  1. crti.o:

    • 定义初始化(如全局构造函数)的开始部分。
    • 设置.init段的开头。
  2. crt1.o/Scrt1.o:

    • 定义程序的实际入口点_start
    • _start是程序的启动点,它会进行一些基本的初始化,然后调用全局的构造函数,接着调用main(),最后调用全局的析构函数。
    • Scrt1.o是用于位置无关代码的版本,通常在动态共享库中使用。
  3. crtbegin.o/crtbeginS.o:

    • 插入构造函数列表的开始部分,这些构造函数是由程序的不同部分(如不同的编译单元或链接的库)提供的。
    • crtbeginS.o是用于位置无关代码的版本。
  4. 你的代码:

    • 这是你实际编写的程序代码。
    • main()函数和其他全局/静态对象的定义都在这里。
  5. crtend.o/crtendS.o:

    • 插入析构函数列表的结束部分。
    • 就像crtbegin.o有一个与之相对应的版本crtbeginS.ocrtendS.ocrtend.o的位置无关代码版本。
  6. crtn.o:

    • 定义初始化和清理函数的结束部分。
    • 设置.fini段的结尾。

在链接过程中,链接器确保按照这个顺序包含这些组件,从而确保程序在运行时具有正确的初始化和清理顺序。

注意: 在不同的系统、编译器版本和配置中,具体的文件名和顺序可能会有所不同。上述描述是基于通用的和常见的行为,但具体细节可能会根据环境而有所变化。

crt1.o 和 Scrt1.o

crt1.oScrt1.o 是C运行时(CRT, C Runtime)的一部分,它们定义了程序的真正的入口点——_start。尽管当我们编写C程序时通常会以 main() 作为起点,但实际上在进入 main() 之前还会执行很多初始化操作,这些操作由这些运行时对象文件中的代码进行。

下面是一个简化的、高层次的说明:

  1. 编写一个简单的C程序,例如:

    #include <stdio.h>
    
    int main() {
        printf("Hello, World!\n");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  2. 当编译并链接此程序时,链接器除了链接我们的代码外,还会链接C运行时的一部分。这确保了 _start 是实际的程序入口点。

  3. 当程序开始执行时,它首先进入 _start

    • _start 负责执行多种初始化任务,例如设置堆栈,初始化全局变量,调用全局构造函数等。
    • 一旦完成所有的初始化,_start 调用 main() 函数。
    • main() 函数执行,打印 “Hello, World!”。
    • main() 返回后,控制权返回给 _start,它接着负责调用全局析构函数并执行其他清理任务。
    • 最后,_start 调用系统调用以退出程序。

这就是为什么,如果我们使用工具(如 objdumpnm)查看一个编译好的可执行文件,会看到除了 main 之外还有其他符号和入口点,如 _start

请注意,crt1.oScrt1.o(及其相关的CRT文件)的具体行为和内容可能会根据操作系统、编译器和系统架构而异。

crti.o 和 crtn.o

crti.ocrtn.o 是 C 运行时的组件,它们为全局构造函数和析构函数的初始化和清理提供所需的框架。这些构造函数和析构函数不应与 C++ 的类构造函数和析构函数混淆;在这里,我们指的是全局和静态对象的初始化和终止函数。

在 ELF(可执行和链接格式)系统上,例如大多数 Unix-like 系统,crti.ocrtn.o 提供了 .init.fini 段的前导和后继代码。它们确保正确地设置和执行构造函数和析构函数。

如何工作?

  1. crti.o 提供 .init.fini 段的开头部分。
  2. 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这个示例中,我们定义了一个构造函数 my_constructor 和一个析构函数 my_destructor。这些函数分别在 main() 函数之前和之后执行。

当编译并运行此程序时,输出应如下:

Before main()
Inside main()
After main()
  • 1
  • 2
  • 3

这就是 crti.ocrtn.o 起作用的地方:

  1. crti.o 提供了 .init 段的开头,这段代码确保 my_constructormain() 之前执行。
  2. crtn.o 提供了 .fini 段的结束部分,这段代码确保 my_destructormain() 之后执行。

在实际的系统中,还有其他机制和细节确保了构造函数和析构函数的正确执行顺序,以及与其他库和组件的互操作性。但从高层次来看,crti.ocrtn.o 提供了为这些函数设置和执行所需的基础框架。

crtbegin.o / crtbeginS.o 和 crtend.o / crtendS.o

当在C++中使用静态对象或全局对象,这些对象的构造函数和析构函数需要在程序的main()函数执行前后被调用。crtbegin.o, crtbeginS.o, crtend.o, 和 crtendS.o 这些文件正是负责这些操作。

crtbegin.o 和 crtbeginS.o

  • 这些文件的主要目的是收集程序中所有静态或全局C++对象的构造函数,并将它们放在一个列表中。
  • crtbeginS.o 特别用于生成位置无关代码(PIC, Position-Independent Code),这在动态共享对象(如.so文件)中是必需的。

crtend.o 和 crtendS.o

  • 这些文件负责收集所有静态或全局C++对象的析构函数。
  • 同样,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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

当这个程序启动时,我们希望首先看到“Constructor called!”,接着是“Inside main()”,最后是“Destructor called!”。

这正是 crtbegin.ocrtend.o(或它们的PIC版本)的作用:它们确保在进入main()之前调用全局对象的构造函数,而在main()之后调用全局对象的析构函数。

为了达到这一目的,crtbegin.o 创建了一个指向所有构造函数的列表,并确保在main()前调用它们;而 crtend.o 则为析构函数做了同样的事情,但是在main()后。

这是为什么当我们使用g++或其他C++编译器链接C++程序时,这些CRT对象文件会自动被包括在内,以确保正确的程序初始化和终止顺序。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/722526
推荐阅读
相关标签
  

闽ICP备14008679号