一、如何得到可执行目标文件

链接程序collect2/ld,将所有的可重定位目标文件静态链接在一起,就得到了可执行目标文件
静态链接动态库时,只是留下函数的接口,当程序运行时再动态加载动态库,
简单理解的话,链接的过程,其实就是将所有的.o文件合并为一个可执行文件的过程

链接时,链接程序(静态链接器)主要做两件事情:

  • 第一:符号解析
  • 第二:地址重定位

有关这两件事情,我们在前面大概的介绍过,接下来我们将会较为详细的介绍下这两件事


二、符号解析

1. 符号解析的目的

确定模块中引用的每个符号都有明确的定义,并将每个符号的引用与定义关联起来,
如果你引用了一个符号,结果这符号没有被定义,程序是无法运行的

比如:

  • (1)如果引用的全局变量没有定义的话
    符号没有定义,这个变量就没有对应的空间,没有变量空间,就没办法进行读写了
  • (2)如果引用的函数没有定义的话
    函数体(函数指令)就不存在,函数没办法调用

三、如何解析符号

检查模块(.o)的.symtab符号表,看符号的定义情况

  • (1)情况1:符号就是在本模块定义的(本地符号)
    像这种情况的话,链接器不需要做什么太多的解析工作,因为符号就是在本模块中定义的,
    每个符号对应的空间就被定义在了本模块的某个节中(比如.text.data等节),引用该符号时肯定没有问题
  • (2)情况2:符号由本模块引用,但是在其它模块定义的(全局符号)
    被标记为UND的符号就是这种情况,UND表示此符号只是在本模块引用,但是在其它模块定义的,
    其实链接器进行符号解析时,重点解析的是标记为UND的符号,解析时会检查UND符号是否在其它模块中有定义

怎么解析 UND 符号的?

解析时,对于本模块中UND的符号,链接器会查看其它模块的.symtab符号表,
看该符号是否在其它模块中有定义,如果找到定义,就将符号的引用和定义关联起来,
如果找不到,链接器就会报undefined reference to ‘符号名’的错误

比如:
a.c

int fun();
int main(void)
{
    fun();
    return 0;
}
$ gcc a.c  b.c -o a
/tmp/ccTyxboK.o: In function 'main':
a.c:(.text+0xa): undefined reference to 'fun'
collect2: error: ld returned 1 exit status

undefined reference to 'fun'这个错误是由链接器报的,因为 b.c 并没有定义fun() 函数


四、有关编译链接时的报错

1. 预处理

a.c ---> a.i(cpp、cc1)

报预处理的错误,凡是宏、头文件包含、预编译等错误都是在预处理阶段,由“预处理器”报的


2. 编译阶段

a.i ----> a.s (cc1)

这个阶段报的都是C语法错误,因为这个阶段的主要目的就是按照C语法格式去解释C源码文件,然后翻译得到汇编,
所以编译阶段报的主要是C语法的错误,只要你不按照C语法格式的要求写,就会报C语法错误

各种C关键字用错、写错

  • 写错:return 10 写成了 retrun 10

用错:

switch(a)
{
    case 12.6: a = 10;//c语法要求,switch的case不能跟浮点数
}

各种重复定义,编译时,是以单个源码文件为单位来操作的,
单个源码文件(模块)中的一些关键内容,是不能重复定义的

int a = 100;
int a = 101; // 变量重复定义的错误

int main(void)
{
}

疑问:int a; int a = 100; 算不算重复定义?
答:这种不算,为什么不算,后面解释。

同一模块的重复定义,编译器编译时就会检查出来,不同模块之间的重复定义,只能留给链接器检查。

a.o                   b.o
int a = 10;           int a = 100;

类型重复定义的错误

struct student
{
int num;
};

struct student  // 类型重复定义
{
int num;
};

int main(void)
{
}

模块中函数重定义的错误

int fun()
{
    ...
}

int fun()
{
    ...
}

旁注:像int、float、char、struct student等类型,是C语法格式才有的东西,
是专门给cc1编译器用的,这些类型决定了数据空间的存储结构和大小,比如:

int   a; //整形存储结构,4字节
float b; //浮点存储结构,4字节

一旦编译器cc1将其编译为汇编后,这些类型就消失了
其实所有属于C语法格式的东西(比如,C的类型、关键字等),在cc1编译之后,都将不复存在


3. 汇编

a.s ——————> a.o (as)

事实上,汇编阶段并不会报什么错误,不报错的原因,并不是因为汇编器as没有检错的能力,
而是只要C源码被编译为a.s时通过了,也就是说只要C源码没有错误,
那么编译得到的a.s就没问题,从a.s汇编为a.o时,其实没有什么需要报的错误


4. 链接

报各种链接时产生的链接错误,比如

某模块引用的符号,找遍其它所有模块,都找不到它的定义时,
就会报undefined reference to ‘符号名’的错误

不同模块有强符号重复定义时,报重复定义的错误
初始化了全局变量的重复定义

a.o                    b.o
int a;                 int a = 100; // 因为他们都是强符号,所以报错重复定义

如果不想报错怎么?

  • 给其中一个加static,或者两个都加static,加了static后,符号就是本地的,对其它模块无影响
  • 将其中一个的初始化去掉,没有初始化的符号是弱符号,强弱符号不会冲突

有关强弱符号的问题,后面还会介绍


同名函数的重复定义

a.o                b.o
float fun(int a)   int fun(void)
{                  {

}                   }

对于C来说,不管函数参数一不一样,只要函数名相同,就被认为是重名
c++/java这类语言的函数允许重载,重载的意思就是说,函数名相同不一定重名,
只要参数不同,就是两个不同的函数算是重名