链接器与加载器:从符号解析到动态链接的四十年技术演进
程序构建的沉默机械 每个程序员都写过编译命令,但很少有人真正理解ld在做什么。当编译器完成了词法分析、语法分析、中间代码生成、优化和目标代码生成后,它产出了一个或多个目标文件。这些文件包含了机器码,但还不能执行——它们之间存在引用关系,全局变量的地址未确定,外部函数的位置未知。链接器的任务就是将这些分散的碎片组装成一个完整的程序。 ...
程序构建的沉默机械 每个程序员都写过编译命令,但很少有人真正理解ld在做什么。当编译器完成了词法分析、语法分析、中间代码生成、优化和目标代码生成后,它产出了一个或多个目标文件。这些文件包含了机器码,但还不能执行——它们之间存在引用关系,全局变量的地址未确定,外部函数的位置未知。链接器的任务就是将这些分散的碎片组装成一个完整的程序。 ...
每一个 C 程序员都遇到过这样的错误:undefined reference to 'foo' 或 multiple definition of 'bar'。编译通过了,但链接器拒绝了你的代码。那一刻,你可能只会机械地检查头文件、库路径或声明顺序,然后继续工作。但你是否想过,链接器究竟在做什么?为什么它能容忍某些重复定义,却对另一些报错?为什么同一个程序在静态链接和动态链接下行为不同? ...
一个全局变量的"分身术" 1979年,Unix V7引入了一个特殊的全局变量——errno。当系统调用失败时,它会将错误码写入这个变量,供后续代码检查。这在单线程时代完美运作。但到了1990年代,多线程编程成为主流,问题出现了:如果两个线程同时执行系统调用,它们会覆盖彼此的errno值。 ...
1995年,Linux从a.out二进制格式迁移到ELF。这个改变带来了更灵活的共享库支持——不再需要中央分配的虚拟地址空间槽位,库可以按需加载和替换。但灵活性是有代价的:程序启动变慢了。 ...