GCC multiple definition of

两个cpp文件。

//main.cpp
int cat = 3;
int main()
{
    return 0;
}
//data.cpp
int cat = 10;

使用gcc分别编译成目标文件。
gcc -c main.cpp
gcc -c data.cpp
然后执行
gcc main.o data.o
结果报错:
data.o:(.data+0x0): multiple definition of `cat'
main.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status

问题来了,如果我将data.cpp编译成动态库。
gcc -fPIC -shared data.cpp -o libdata.so
然后执行
gcc main.o libdata.so
就不会报错。

如果将data.o打包成静态库
ar rcs libdata.a data.o
再执行
gcc main.o libdata.a
也不会报错。

这是为什么?
使用nm命令查看libdata.so和libdata.a,都包含了符号'cat',gcc却没报multiple definition错误。

阅读 4.6k
2 个回答

结论

  • 使用目标文件编译时,由于初始化的全局变量为强符号,编译器不允许该情况,报错multiple definition
  • 使用静态库/动态库编译时,对于没有使用到的目标批文件不会被链接器链接到最终的输出文件中。由于main.o并未使用data.o,链接器没有链接,所以不报错。

强符号/弱符号

多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误。比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错:

b.o:(.data+0x0): multiple definition of 'global'
a.o:(.data+0x0): first defined here

这种符号的定义可以被称为强符号(Strong Symbol)。有些符号的定义可以被称为弱符号(Weak Symbol)。对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。
针对强弱符号的概念,链接器就会按如下规则处理与选择被多次定义的全局符号:

  • 规则1:不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);如果有多个强符号定义,则链接器报符号重复定义错误。
  • 规则2:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。
  • 规则3:如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个。比如目标文件A定义全局变量global为int型,占4个字节;目标文件B定义global为double型,占8个字节,那么目标文件A和B链接后,符号global占8个字节(尽量不要使用多个不同类型的弱符号,否则容易导致很难发现的程序错误)。”

摘录来自《程序员的自我修养:链接、装载与库》 俞甲子 石凡 潘爱民

目标文件直接编译

main.o的符号表如下,可见cat为global类型

$ readelf -s main.o
Symbol table '.symtab' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.cpp
...
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 cat
    10: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 main

data.o符号表中cat也为global,强符号

$ readelf -s data.o

Symbol table '.symtab' contains 9 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS data.cpp
...
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 cat

初始化的全局变量是强符号,链接时如果有同名的强符号则会出现multiple definition的错误。

静态库

为了验证作为静态库时,data.o并未链接,现在我们在data.cpp中添加一个函数:

// data.cpp
int cat = 10;
int foo() {
    return 0;
}

可见符号表中有_Z3foov,即我们定义的foo函数

$ gcc -c data.cpp
$ ar rcs libdata.a data.o
$ readelf -s libdata.a 

File: libdata.a(data.o)

Symbol table '.symtab' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS data.cpp
...
     9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    2 cat
    10: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 _Z3foov

链接该静态库输出a.out,过滤foo函数无内容,证明该data.o并没有被链接到a.out中,也就不存在冲突问题了。

$ readelf -s a.out  | grep foo
$ 

PS: 可对比下修改data.cpp前后两次产生的a.out文件,两者的符号表是一样的,也证明了data.o并未链接。

推荐阅读

《程序员的自我修养:链接、装载与库》 俞甲子 石凡 潘爱民

你在两个编译单元里写了相同的符号实例,这是一个错误。而且从思路上就不对:在设计API的时候,你应当想好一个东西是由哪里定义,然后别人都去使用那个,通过头文件包含。

你应当写成:

// data.h
#pragma once
extern int cat; // 别人看到它,了解在某处有一个cat定义
// data.cpp
#include "data.h"
int cat = 10; // cat定义在此处
// main.cpp
#include "data.h"
int main()
{
    // code that use cat
}
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏