gcc编译选项--fcommon
multiple definition
最近在龙蜥操作系统编译时,出现了multiple definition的错误,重复定义?查看了代码,有几个变量确实是重复定义了,简单来讲就是在一个.h文件定义了一个变量,多个.c文件又包含了这个.h,导致这个变量在多个.c文件重复定义,在编译时报错。
修复也很简单,.h文件的中的变量统一加extern,然后只有一个.c文件才能定义初始化这个变量,这样就可以避免重复定义,也顺利编译成功。
gcc的版本
代码确实存在问题,不过其他操作系统并不会报错,只是一个警告,因此我很好奇这是为什么。网上搜索后,发现了一篇很好的文章:https://club.rt-thread.org/ask/article/5fb1ecf297a83492.html ,gcc版本在10版本后,关闭了 fcommon选项,使用了fno-common,来看看这个选项的描述:
1 |
|
文章已经讲解很清楚,这里作为我的理解
-fno-common
- 作用:当使用
-fno-common
选项时,编译器将未初始化的全局变量放置在BSS段中。 - 链接器行为:未初始化的全局变量会被视为强符号。在链接阶段,如果相同的未初始化全局变量在多个编译单元中被定义,链接器会将其视为重复定义,并报告错误。也就是说,链接器不会合并这些变量的定义,任何多个定义的冲突都会导致链接错误。
-fcommon
- 作用:当使用
-fcommon
选项时,编译器会将未初始化的全局变量放入一个COMMON
块中。 - 链接器行为:
COMMON
块是一种特殊的内存区域,允许链接器在链接阶段合并同名的未初始化全局变量的定义。即使这些变量在多个编译单元中被定义,链接器会将它们合并为一个单一的变量对象。
也就是使用了fcommon,未初始化的全局变量放在COMMON
块,允许重复定义,链接期会合并为1个。
GCC 默认的链接器行为是,如果在链接过程中发现重复的符号,它会选择第一个找到的符号,并忽略后续的符号。这意味着,链接器会使用第一个定义的符号(包括它的数据类型),而忽略后续定义的符号。但是2个变量使用的内存初始地址是一样的,也就是一个int a和double a其实共用一块内存,int占4个字节,double占8个字节,这样其实可能导致问题。int a中可能还有一个int b,这个b占用了a后面的4个字节,2个变量(b 和 double a)使用了一块内存,肯定会有问题。
而使用了fno-common属性后,未初始化的全局变量现在会放到BSS段,属于强符号,不允许重复定义
内存模型
C语言程序的内存通常被分为几个主要区域:
- Text段(代码段):
- 作用:存放程序的可执行代码,包括所有函数和指令。
- 属性:通常是只读的,以防止程序代码在运行时被修改,并且可以在多个进程间共享。
- Data段(数据段):
- 作用:存放已初始化的全局变量和静态变量。
.data
段:包含初始化的全局和静态变量。.rodata
段:包含只读的初始化数据(如字符串常量)。
- 作用:存放已初始化的全局变量和静态变量。
- BSS段:
- 作用:存放未初始化的全局变量和静态变量。程序加载时,这部分内存会被自动初始化为0。
- 特点:在可执行文件中不占用实际空间,只有在程序运行时才分配内存。
- 堆(Heap):
- 作用:动态分配内存区域,通过函数如
malloc()
和free()
进行管理。用于存放动态分配的数据结构。 - 管理:需要程序员手动管理内存分配和释放,避免内存泄漏和悬挂指针。
- 作用:动态分配内存区域,通过函数如
- 栈(Stack):
- 作用:存放局部变量、函数参数和返回地址。每个函数调用时会在栈上分配一个栈帧。
- 特点:栈内存的分配和释放由系统自动管理。栈大小通常有限,如果超出栈空间会导致栈溢出。
强符号/弱符号
对于全局变量来说,如果初始化了不为0的值,那么该全局变量则被保存在data段,如果初始化的值为0,那么将其保存在bss段,如果没有初始化,则将其保存在common段,等到链接时再将其放入到bss段。关于第三点不同编译器行为会不同,有的编译器会把没有初始化的全局变量直接放到bss段,也就是gcc的-fcommon与-fno-common属性的差异。
绝大多数情况下,函数和已初始化的变量是强符号,而未初始化的变量是弱符号。对于它们,下列三条规则适用:
- 同名的强符号只能存在一个。
- 一个强符号可以和多个同名的弱符号共存,但调用时会选择强符号的值。
- 有多个弱符号时,链接器可以选择其中任意一个。
librdkafka的编译问题?
目前librdkafka的最新版本是2.5,最近需要升级这个库,于是我在十几个操作系统编译了一下这个库,有3个编译报错,其中一个是因为依赖的libssl没找到,于是我在lib64指定了一下libssl.so也就编译通过了,而官方最近刚修复了这个问题,也就是没找ssl依赖的情况下,也能编译成功,有点巧。
还有2个报了redefinition of typedef的错误,一个是suse sp4,一个是centos 6,github搜了一下,发现这是老问题,这个项目以前经常会遇到这个错误,看着是修复过了,现在还会有错误?这2个操作系统的gcc都是4.4,版本比较低,而其他编译过的操作系统,gcc有4.8、7.3、8.3、10.3、12.3,也就是说旧的gcc反而会报错。找了一个变量看下代码
1 |
|
在rdkafka_int.h和rdkafka_op.h都定义了这个变量,然后 rdkafka_op.c都包含了这个头文件,很明显是重复定义了。但是这个符合c语言规则的
ISO/IEC 9899:1999 6.7.3中是这么描述的
1 |
|
而c99中
1 |
|
其实就是以前的c标准不允许重复重复声明,c99开始允许了,因此代码这么写没有问题,只是老版本的gcc遵循老的c标准,编译会报错。但是librdkafka明确表示以后不支持centos 6和7,所以也没什么好说的。8的gcc版本一般为8.3,可以正常编译,后续其实要推动客户升级操作系统,毕竟后续会出现越来越多的兼容性问题。