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


一、模块之间全局符号重名的情况是无法避免的

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

a.c                         b.c

int fun();
int a;                      int a = 100;

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

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


二、强符号与若符号,解决全局符号重命名问题

链接器在符号解析时,根据全局符号的强弱共存规则来解析

全局符号的强弱之分

全局变量

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

函数

  • (a)强符号:函数定义
  • (b)弱符号:函数声明

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

重名符号的强弱共存规则

  • (a)不允许多个同名的强符号同时存在,存在的话就报错
  • (b)强符号只有一个,其它同名的都是弱符号的话,统一时,选强符号,舍弃弱符号
  • (c)同名符号如果全都是弱符号的话,留其中某个,其它舍弃,留谁由链接器来决定

举例

a.c                           b.c
int a = 100;int a = 200;

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

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

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

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

如何解决:

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

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

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

a.c                       b.c
int a;                    int a;
int main(void)
{
    fun();
}

三、cc1 编译器处理模块内符号重名的情况

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

1. 例子1:

int a = 100;
int a = 300;

int fun()
{

}
int fun()
{

}

int main(void)
{

}

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

2. 例子2:

int a;
int a;


int main(void)
{

}
int a = 10;

没有错误,因为不存在强符号的冲突问题,编译时会统一为一个,.symtab只会记录一个a
在经典的C讲解中,这种情况将int a 解释成“int a = 100;这个定义”的声明,实际上本质关系是强弱符号关系,
只不过在单个模块中,弱符号(声明)可以将强符号(定义)的作用域提前

3. 例子3

static int a = 20;
int a = 10;

int main(void)
{
}

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

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

a.c                     b.c

int a = 10;             static int a = 20;

int main(void)
{

}

四、再说说声明和定义

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

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

1. 能够区分的例子

(a)例子1
a.c

int a;
int a = 100;
...

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

(b)例子2

a.c               b.c

int a;            int a = 0;

...               ...

前面说过,没有明写出extern,默认就是extern修饰的,有初始化的是定义,没有初始化的是声明
在以上两个例子中,都能明确的区分出定义和声明

2. 不能区分的例子

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

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

又比如:

int a;

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

又比如工程中有两个.c

a.c               b.c

int a;            int a;

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

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

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

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