首页 /  技术专区  /  C 宽屏模式 >

可执行目标文件——符号解析

1. 可执行目标文件

1.1 如何得到可执行目标文件

链接程序collect2/ld,将所有的可重定位目标文件静态链接在一起,就得到了可执行目标文件。

静态链接动态库时,只是留下函数的接口,当程序运行时再动态加载动态库。

简单理解的话,链接的过程,其实就是将所有的.o文件合并为一个可执行文件的过程。


链接时,链接程序(静态链接器)做了什么事情 

        链接器主要做两件事情,

        第一:符号解析

        第二:地址重定位。

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


1.2 符号解析

1.2.1 符号解析的目的

确定模块中引用的每个符号都有明确的定义,并将每个符号的引用与定义关联起来。

如果你引用了一个符号,结果这符号没有被定义,程序是无法运行的。

比如:

(1)如果引用的全局变量没有定义的话

        符号没有定义,这个变量就没有对应的空间,没有变量空间,就没办法进行读写了。

(2)如果引用的函数没有定义的话

        函数体(函数指令)就不存在,函数没办法调用。

1.2.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编译链接

zxf@ubuntu:~/Desktop$ 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'这个错误是由链接器报的。

(3)说说有关编译链接时的报错

        其实编译链接时,四个阶段都有可能会报错误。

1)预处理

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

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

2)编译阶段

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

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

比如:

(a)各种c关键字用错、写错

· 写错:return 10  写成了 retrun 10

· 用错:

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

(b)各种重复定义

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

· 变量重复定义的错误

a.c

int a = 100;
int a = 101;

int main(void)
{
}

疑问:int a; 

int a = 100;

算不算重复定义。

答:这种不算,为什么不算,后面解释。


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

a.o                   

int a = 10;

b.o

int a = 100;

· 类型重复定义的错误

比如:

a.c 

struct student
{
    int num;
};

struct student
{
    int num;
};

int main(void)
{
}

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

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

一旦编译器cc1将其编译为汇编后,这些类型就消失了。

其实所有属于C语法格式的东西(比如,c的类型、关键字等),在cc1编译之后,都将不复存在。

· 模块中函数重定义的错误

a.c

int fun()
{
    ...
}

int fun()
{
    ...
}

(c)汇编

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

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

(d)链接

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

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

· 不同模块有强符号重复定义时,报重复定义的错误

- 初始化了全局变量的重复定义

a.o                    

int a;

b.o

int a = 100;

链接时会报a重复定义了。

如果不想报错怎么?

+ 给其中一个加static,或者两个都加static

        加了static后,符号就是本地的,对其它模块无影响。

+ 将其中一个的初始化去掉

        没有初始化的符号是弱符号,强弱符号不会冲突,有关强弱符号的问题,后面还会介绍。

- 同名函数的重复定义

a.o           

float fun(int a)  
{   
           
}

b.o

int fun(void)
{

}

旁注:有关c++/java中同名函数的问题。

· c的情况

        对于c来说,不管函数参数一不一样,只要函数名相同,就被认为是重名。

· c++/java等

        这类语言的函数允许重载,重载的意思就是说,函数名相同不一定重名,只要参数不同,就是两个不同的函数。

int fun(int a)
{

}

int fun(int b, int c)
{

}

因为在进行函数重名的判断时,函数名和函数参数会合在一起判断,只有当函数名和参数完全相同,才能算是重名。




头像
0/200
图片验证码