C语言运行时

发布于:2025-06-14 ⋅ 阅读:(22) ⋅ 点赞:(0)

✅ 在高级语言中,“运行时”包括两个重要含义:


程序运行期间的整个生命周期(你已经掌握)

这是“时间”上的意义,指从程序启动开始执行,到退出这一段时间。


运行时环境 / 运行时库(这是“组件”或“功能”上的意义)

指的是程序在运行时依赖的一组代码、机制或服务,叫做“运行时库”(runtime library)或“运行时系统”(runtime system)。


✅ 不同语言的运行时库示例

编程语言 对应的运行时 提供的功能示例
C/C++ CRT(C Runtime) main() 启动前的初始化、malloc()atexit()__libc_start_main()
Java JVM(Java Virtual Machine) 字节码解释、GC(垃圾回收)、类加载器、安全模型等
Python Python 解释器 + 标准库 动态类型管理、异常处理、模块加载、内存管理等
Go Go Runtime Goroutine调度、GC、channel 等并发原语支持
Rust core/std crate(部分) panic处理、栈展开、内存分配等(可选)

✅ 以 C 语言为例:运行时库 CRT 的作用

当你写一个简单的程序:

#include <stdio.h>

int main() {
    printf("Hello\n");
    return 0;
}

虽然你只写了 main(),但实际执行前:

  1. 程序并不会直接从 main() 开始,而是从 _start 函数开始(由 CRT 提供);

  2. _start 会做以下操作:

    • 初始化堆栈;
    • 初始化 .data.bss(如果是裸机);
    • 调用构造函数(C++ 支持);
    • 调用 main()
    • 最后调用 exit(),完成清理工作;

这整个过程都是 运行时库(CRT)负责的


✅ 所以,“运行时”一词有两层含义:

含义 描述
时间层面 程序已经开始执行,从入口到退出这段时间
技术组件层面 支撑程序运行的底层库和机制,例如 malloc、GC、异常处理、线程调度等

如果你是嵌入式开发者,了解 C runtime 是非常有帮助的。如果你是做系统开发或高级语言(如 Python、Java)开发,这种“运行时”的理解会帮助你更清晰掌握程序行为背后的逻辑。

继续深入讲一下 C 程序执行背后的 _start → __libc_start_main → main 的全过程。需要吗?

非常好,深入了解 C 程序从 _start → __libc_start_main → main() 的执行流程,是理解 Linux 下程序运行机制的关键。下面我们分阶段详解它的全过程,包括涉及的关键符号、运行时库(CRT)、系统调用等。


🧭 总体流程图(Linux 下 C 程序运行)

[ELF 加载器]
     ↓
 [入口: _start]                ← 由编译器 + glibc 提供
     ↓
[__libc_start_main()]          ← glibc 提供
     ↓
  调用你的 main()
     ↓
  main() 返回后,调用 exit()

1️⃣ _start:真正的程序入口点

  • _start 是链接器设置的默认入口点(通过 ENTRY(_start))。

  • 它是由 glibc 提供的,位于 crt1.o(C runtime startup object)中。

  • _start 做了很多事情,包括:

    • 设置 argcargvenvp
    • 调用 __libc_start_main(),传入 main() 和初始化/清理函数。

_start 源码原型(简化):

void _start() {
    // setup stack, call __libc_start_main
    __libc_start_main(main, argc, argv, ..., init, fini, ...);
}

2️⃣ __libc_start_main():glibc 的运行时核心

这是整个 glibc 程序启动的“中控”。

它的主要工作:

  • 保存传入的 main() 函数指针;
  • 调用注册的构造函数(.init_array,比如 C++ 构造器);
  • 设置 TLS、locale 等环境;
  • 调用你的 main() 函数并传递参数
  • main() 返回后,调用 exit(),做清理工作。
int __libc_start_main(
    int (*main)(int, char **, char **),
    int argc,
    char **argv,
    ... /* envp, init, fini, rtld_fini, stack_end */
);

🔧 注意:你在 Linux 上写的 main() 并不是程序的入口点,它只是被 __libc_start_main() 调用的一个函数。


3️⃣ main():你的业务逻辑

到了这一步,程序正式进入你编写的逻辑。你拿到 argcargv,做你要做的事。


4️⃣ main() 返回 → 调用 exit()

  • 如果你的 main() 返回,glibc 会调用 exit(),做以下工作:

    • 调用 .fini_array 中的析构函数(C++ 析构器等);
    • 调用 atexit() 注册的清理函数;
    • 向内核发出 exit() 系统调用,终结进程。

🔍 示例:使用 readelf 观察

$ readelf -s ./a.out | grep main
   34: 000000000040

网站公告

今日签到

点亮在社区的每一天
去签到