在用C语言开发大型的工程中,由于文件众多,在各种机缘巧合下会出现全局变量重复定义,那假如出现这种情况是什么现象呢
首先来看看一个全局变量重复定义用例
例一:
testA.c文件
#include <stdio.h>
#include <stdlib.h>
extern void printf_testB();
int a;
int main()
{
a=10;
printf("testA.c a=%d,&a=%p\n",a,&a);
printf_testB();
return 0;
}
testB.c文件
#include <stdio.h>
#include <stdlib.h>
int a=20;
void printf_testB()
{
printf("testB.c a=%d,&a=%p\n",a,&a);
}
在上面的两个文件中分别定义了整型变量a,并且都赋值了,在各自的文件中打印a的值和地址,在这里大家可以先猜一猜,两个printf打印出来的a分别是多少?
利用gcc编译两个文件
[root@localhost fno-common]# gcc testA.c testB.c -o targetExe
[root@localhost fno-common]#
[root@localhost fno-common]# ll
总用量 20
-rwxr-xr-x 1 root root 8608 2月 2 16:09 targetExe
-rwxrwxrwx 1 root root 177 2月 2 15:55 testA.c
-rwxrwxrwx 1 root root 120 2月 2 15:55 testB.c
[root@localhost fno-common]#
可以看到在不带任何检查参数的情况,没有任何报错,运行目标文件
[root@localhost fno-common]# ./targetExe
testA.c a=10,&a=0x601034
testB.c a=10,&a=0x601034
可以看到两个文件里打印出来的变量a和a的地址是完全一样的,指向的是同一块内存,而且a的值就是testA文件里的值。
再看一个例子,假如不对testA.c中的全局变量进行赋值呢
例二:
testA.c文件
#include <stdio.h>
#include <stdlib.h>
extern void printf_testB();
int a;
int main()
{
printf("testA.c a=%d,&a=%p\n",a,&a);
printf_testB();
return 0;
}
testB.c文件不变
看看运行的结果
[root@localhost fno-common]# ./targetExe
testA.c a=20,&a=0x601034
testB.c a=20,&a=0x601034
这次运行的结果变成了testB文件里a的值了,并且和例一种的一样,变量a和a的地址是完全一样的。
这是为什么呢?
首先说一下C语言中的弱符号和强符号
1,弱符号:未初始化的变量是弱符号
2,强符号:已初始化的变量是强符号
对于链接器来说,所有的全局符号可分为两种:强符号(Strong symbols),弱符号(Weak symbols)。gcc的attribute中有个__attribute__((weak)),就是用来声明这个符号是弱符号的。gcc手册中这样写道:
The weak attribute causes the declaration to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions which can be overridden in user code, though it can also be used with non-function declarations. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker.
对于这个gcc扩展,这里作了一个简洁的介绍。我们来看更通用的情况。;)
一般来说,函数和已初始化的变量是强符号,而未初始化的变量是弱符号。对于它们,下列三条规则适用:
1. 同名的强符号只能有一个。
2. 有一个强符号和多个同名的弱符号是可以的,但定义会选择强符号的。
3. 有多个弱符号时,链接器可以选择其中任意一个。
上述例一和例二中,testA.c的全局变量a没有初始化,是弱符号,而testB.c的全局变量初始化了是强符号,则根据上述的规则二,例一种在testA.c中全局变量a指向的是testB.c中定义全局变量的地址,在testA.c中对a进行赋值,则改变了a的值。而在例二中testA.c并没有对a赋值,所以取得还是20。
可以看到如果没注意到这个问题,本意是在testA.c中对定义的全局变量进行赋值,给相关功能使用,结果意外的改变了testB.c中的值,危害不言而喻。那如何避免这种情况呢,至少要检查的出来,给个提醒。
避免这种错误的一个方法是,给gcc加上-fno-common选项,加上该选项后再进行编译
[root@localhost fno-common]# gcc testA.c testB.c -o targetExe -fno-common
/tmp/cc8StOtH.o:(.data+0x0): multiple definition of `a'
/tmp/cc3yyF0p.o:(.bss+0x0):第一次在此定义
collect2: 错误:ld 返回 1
可以看到编译不通过,给出了报错,有多重定义变量a。
那么,如果出现了这种多重定义的问题,怎么改呢
1,如果确定两个变量只是碰巧重名了,则很简单改掉一个,不重名即可
2,如果确定两个变量是要用同一个,则在使用的文件里加extern,告诉编译器这是用的同一个
当然该参数还有其他作用,可参考以下文章: