我一直在研究C编译过程,但找不到这个问题的答案。
源代码和目标代码是在链接过程之前生成的。像这样的代码将毫无问题地编译成 .s 或 .o 文件。
int add (int a, int,b);
int main(){
int tmp = add(1,2);
return 0;
}
在这个阶段,编译器或汇编器不知道add函数的长度,但是必须为add函数分配一个内存段作为文本。例如,在汇编输出中,要执行add函数,就会有一个跳转指令到add函数。在这个阶段汇编器如何知道add函数的地址呢? 汇编器在这一步做了什么? 汇编器是否为可能的 add 函数保留一定量的内存?
目标模块包含的信息告诉链接器当目标模块与其他目标模块链接时需要进行哪些更改。
有多种对象模块格式,因此此答案中的信息是一般性和概念性的。假设程序中有一条
call
指令。在汇编语言中,它可能看起来像 call add
。假设调用指令的机器语言代码是 0x73
,后跟要调用的例程的四字节地址。然后,在目标模块的文本部分中,汇编器将写入五个字节:0x73 0x00 0x00 0x00 0x00
。目标模块的另一部分称为重定位表,它列出了链接模块时需要更改的位置。这些通常被称为“修复”。在此表中,汇编器将放置有关此 call
指令的信息。此表中的条目将提供有关要进行的每项更改的信息,包括:
因此,例如
call add
的表条目可能表明它位于文本部分,从开始处算起 34 个字节,它是地址的整个替换,并且符号的名称是 add
。当链接器决定 add
在程序中的位置时,它会将该地址写入文本部分,从开头算起 34 个字节。
这是使用绝对地址的简化示例。如今,使用相对地址更为常见。指令将计算一个地址作为距当前程序计数器的偏移量,而不是采用绝对地址,其中包含处理器正在执行或即将执行的指令的地址。对于相对寻址,重定位条目表示更改类型是相对的而不是绝对的。然后,链接器不会将绝对地址写入文本部分,而是计算
call
目标与 call
指令(或其后续指令)的地址之间的差异,并将该差异写入文本部分。
更改类型的其他代码可能表示必须将符号值添加到文本部分中已有的数据中,而不是完全替换它,或者必须将值放入特定类型的位字段中指令或以特定方式编码。但总体思路是相同的:文本部分中的值包含初始信息,重定位表指示在链接程序时如何更改值(可能包括将程序加载到内存中时发生的链接)。
C 编译器将源代码翻译为汇编代码(.s 文件)。由于 add 仅声明但未定义,因此编译器知道签名(参数和返回类型)但不知道实现。它将 add 视为外部符号,并假设稍后将提供定义(可能在另一个目标文件或库中)。汇编器将汇编代码转换为机器代码,创建一个目标文件(.o 文件)。汇编器创建一个条目对于add函数在符号表中作为未定义符号。汇编器不为add函数保留内存。相反,它只是记录了这样一个事实:add 是一个将在链接阶段解析的函数。在链接期间,链接器组合所有目标文件并解析外部符号(如 add)的地址。如果链接器找不到 add 的定义,它将抛出未定义的引用错误。
您可以在这里阅读更多相关信息:https://www.quora.com/What-happens-if-a-function-is-declared-but-not-define-in-C