C语言全局变量重复定义检查-fno-common

赫连瀚
2023-12-01

在用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,告诉编译器这是用的同一个

当然该参数还有其他作用,可参考以下文章:

https://blog.csdn.net/yyz_1987/article/details/107569192

 类似资料: