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

可执行目标文件——链接器如何解决全局符号重名的问题

1.链接器在符号解析时,是如何解决全局符号的重名问题的

前面说过,对于模块内部符号重名的情况,在编译时cc1编译器会检查出来,对于不同模块之间的全局符号重名情况,只能由链接器解析时来检查。

(1)模块之间全局符号重名的情况是无法避免的

        为了方便工程的组织管理,程序肯定是某块化组织管理的,此时模块之间必然会涉及全局变量和函数的声明,

比如:

a.c                           

int fun();
int a; 
                    
int main(void)                            
{ 
    a = a + 100;
    fun();                                     
}

b.c

int a = 100;

int fun()
{
    ...
}

a.c中为了使用b.c中定义的a和fun,需要在a.c中进行声明,声明的符号与定义的符号是同名的,链接器将这两个文件链接为一个可执行文件,同名的符号就必须统一(合并)为一个,所以链接器进行符号解析时,重名符号的统一是必不可少的。

(2)链接器解析符号时,是如何解决全局符号重名的情况

        链接器根据符号的强弱共存规则来解析。

1)全局符号的强弱之分

        1)全局变量

                (a)强符号:初始化了的全局变量

                (b)弱符号:未初始化的全局变量

        2)函数

                (a)强符号:函数定义

                (b)弱符号:函数声明

                   每个全局符号是强符号还是弱符号,在.symtab表中会有相应的记录。

2)重名符号的强弱共存规则

        1)不允许多个同名的强符号同时存在,存在的话就报错

        2)强符号只有一个,其它同名的都是弱符号的话,统一时,选强符号,舍弃弱符号

        3)同名符号如果全都是弱符号的话,留其中某个,其它舍弃,留谁由链接器来决定

3)举例

(a)例子1

a.c                       

int a = 100;

int fun()                
{                         
    a += 10;                    
} 
         
int main(void)            
{
    static int a = 10;
    fun();
}

b.c

int a = 200;

(extern) int fun()
{
    a = a - 100;
}


由于文件中定义的全局变量a和函数fun都是全局的强符号(extern修饰的),在各自的.symtab符号表中,a和fun都会被标记为全局强符号。

链接器进行符号解析时,会去检测这两个模块的.symtab表中的符号,发现这各自的a、fun都是全局强符号,根据强弱符号的共存规则,一山不容二虎,存在多个同名全局强符号时会报错。

如何解决:

· 将其中的某个强符号变为弱符号

int a = 100;  ————> int a;
int fun(){...} ————> int fun();

留强去弱。

· 使用static来解决

只要将其中的某个或者两个使用static修饰,将其变为本地符号,就不会存在冲突的情况。

int a = 100;  ————> static int a = 200;
int fun(){...} ————> static int fun();

旁注:全局变量和函数加static,目的是将全局符号变为本地符号。

局部变量加static,是将局部变量的从自动局部变量(栈)变为静态局部变量(.data/.bss)。

(b)例子2

a.c                       

int a;

int main(void)            
{
    fun();
}								

b.c

int a;

两个模块的a都是弱符号,统一符号时,留下其中一个即可。

(3)cc1编译器处理模块内符号重名的情况

链接器处理模块间的全局符号的重名时,是按照强弱符号的规则来处理的。

对于模块内部的符号重名问题,编译器cc1在编译时,其实也是按照强弱符号的规则来处理的。

1)例子1:

a.c 

int a = 100;
int a = 300;

int fun()
{
}

int fun()
{
}

int main(void)
{
}

编译时会报重复定义的错误(强符号只能有一个)。

2)例子2:

a.c

int a;
int a;

int main(void)
{
}

int a = 10;

没有错误,因为不存在强符号的冲突问题,编译时会统一为一个,.symtab只会记录一个a。

在经典的c讲解中,这种情况将int a解释成“int a = 100;这个定义”的声明,实际上本质关系是强弱符号关系。

只不过在单个模块中,弱符号(声明)可以将强符号(定义)的作用域提前。

3)例子3

a.c

static int a = 20;
int a = 10;

int main(void)
{
}

大家觉得会报错吗?

答:会报错,虽然一个是全局的,一个是本地的,但是由于都在一个模块中,因此编译时肯定报错。

但是如果它们两个是在不同的模块中的的话,链接器链接时就不会报错,这个前面讲过。

a.c                   

int a = 10; 
           
int main(void)
{
}

b.c

static int a = 20;


(4)再说说声明和定义

事实上,对于编译器、链接器来说,并不知道什么是声明、什么是定义,声明和定义只是学习C语言时会了表述的方便,我们自己的给的,编译器/链接器只知道强符号和弱符号,解析时是按照强弱符号的规则来处理的。

我们以全局变量为例,你会发现其实不太好明确的区分定义和声明。

1)能够区分的例子

(a)例子1

a.c 

int a;
int a = 100;
...

针对这种情况来说,我么认为后面有初始化的a是定义,前面那个没初始化的a是后面a的声明。

(b)例子2

工程中有两个.c

a.c              

int a;
...

b.c

int a = 0;
...

前面说过,没有明写出extern,默认就是extern修饰的。

有初始化的是定义,没有初始化的是声明。

在以上两个例子中,都能明确的区分出定义和声明。

2)不能区分的例子

a.c 

int a;
int a;
int a;
....

在这种情况下,到底那个是定义,那个是声明呢,不好讲了,你或许会说都是定义,都是声明,这就没有什么明确区分了。

又比如:

a.c

int a;

只有一个int a时,到底应该说成是定义,还是说成声明呢?

又比如工程中有两个.c

a.c               

int a;

b.c

int a;

你告诉我那个是声明,那个是定义,也是同样的情况。

所以说对于编译器/链接器来说,它不关心什么是定义、什么是声明,也没办法以定义和声明来区分,人家只关心强弱符号。

不过以后为了表述的方便,我们还是会继续使用“定义”和“声明”这两个概念,只不过在理解了强弱符号之后,大家心里应该清楚本质到底是什么。

以后不会再严格区分定义和声明,像int a;这种未初始化的情况,我们可能即会说成是定义,也可能会说成是声明,只要大家理解了强弱符号,说成什么其实都无所谓,只要方便表达和理解即可。



头像
0/200
图片验证码