最近在做一个项目,包含有C++和C的代码,
在Makefile 里指定所有的cpp文件使用C++编译器(g++)编译,所有的c文件使用C编译器(gcc),
然而在编译过程中(链接阶段)出现了 xxx undefined,函数未定义的错误提示。
后来经过一番搜索,发现是linkage的问题,我也不知道这个词怎么翻译,简单的说就是要加 extern “C”
为啥要extern “C”
举个栗子好了,假如有3个文件,分别叫做
zero.h
zero.c
one.cpp
one.cpp:
#include "zero.h" int add(int a,int b) { return a+b; } int main() { add(1,2); add(3.0,4.0); return 0; }
zero.h:
double add(double a,double b);
zero.c:
double add(double a,double b) { return a+b; }
我们先用gcc编译zero.c文件
gcc -o zero.o -c zero.c
得到zero.o文件,然后再使用g++来编译one.cpp文件
g++ -o one.o -c one.cpp
得到one.o文件,最后组装
g++ -o one zero.o one.o
发现报错了,看看
ryan@UNIX-10:~$ g++ -o one zero.o one.o one.o: In function `main': one.cpp:(.text+0x52): undefined reference to `add(double, double)' collect2: error: ld returned 1 exit status
为啥?我们接着来看,分别看看两个.o文件的符号表
ryan@UNIX-10:~$ readelf -s one.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 one.cpp 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 6 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 8: 0000000000000000 20 FUNC GLOBAL DEFAULT 1 _Z3addii 9: 0000000000000014 73 FUNC GLOBAL DEFAULT 1 main 10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z3adddd ryan@UNIX-10:~$ readelf -s zero.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 zero.c 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 4: 0000000000000000 0 SECTION LOCAL DEFAULT 3 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 6: 0000000000000000 0 SECTION LOCAL DEFAULT 6 7: 0000000000000000 0 SECTION LOCAL DEFAULT 4 8: 0000000000000000 44 FUNC GLOBAL DEFAULT 1 add
在one.o里面,我们看序号为10的那一条,表示
文件有对名称为_Z3adddd的项目持有引用,
但此项目并没有在此文件中(好绕口,(╯‵□′)╯︵┻━┻)
意思就是我要找_Z3adddd,但是我这个文件里没有,也许稍后我可以从其他.o文件里找。
再来看看zero.o里面,序号8,有一个名字为add的项目,但是好像真的没看到_Z3adddd。
好了,问题来了,总共就尼玛2个.o文件,无论哪里都找不到_Z3adddd的函数定义。
然后编译器就只好提示你 ‘add(double, double)’ 是个未定义的引用了。。
说好的add函数呢?
明明定义了add函数,我们在zero.h里声明,在zero.c里实现,再标准不过了,
可是为毛编译器就找不到了,还有他娘的 _Z3adddd 又是什么鬼 (生气脸
这事吧,说(lan)来(de)话(da)长(zi)。。
我们知道C编译器啊,只需要通过函数名就可以定位一个函数,
因为她不支持重载(function overloading) <–英文是不是怎么写,但愿装逼成功了。= ̄ω ̄=
然而西加加编译器啊不是,C++编译器啊,是支持函数重载的,所以她不能仅仅通过函数名来定位一个函数。
然而有一天,C++编译器看到这个
double add(double a,double b); int add(int a,int b);
的时候,只能想想:
· 到底哪个悟空(划掉) add 函数才是真的?
· 我他妈到底要调用哪个?
· 黑人问号.jpg
所以啊,为了支持函数重载,C++还需要通过参数信息来定位一个函数,
我猜她在编译过程中,会修改函数名称来达到不可告人的(划掉)目的。 <—-这条是我瞎胡诌的,我也不知道是不是真的
这样,同样是add函数,你用C编译器编译出来的就是add,用C++编译器编译出来的就可能不是add了,而是其他的什么鬼。
道理我都懂,那怎么解决
简单说就是加extern “C” , 咱把zero.c 还有 zero.h 改改,就可以了,改成这样
zero.h:
#if __cplusplus extern "C" { #endif double add(double a,double b); #if __cplusplus } #endif
zero.c:
#include "zero.h" #if __cplusplus extern "C" { #endif double add(double a,double b) { return a+b; } #if __cplusplus } #endif
然后再编译一下one.o ,再来看看readelf返回的结果
ryan@UNIX-10:~$ g++ -o one.o -c one.cpp ryan@UNIX-10:~$ readelf -s one.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 one.cpp 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 0 SECTION LOCAL DEFAULT 6 6: 0000000000000000 0 SECTION LOCAL DEFAULT 7 7: 0000000000000000 0 SECTION LOCAL DEFAULT 5 8: 0000000000000000 20 FUNC GLOBAL DEFAULT 1 _Z3addii 9: 0000000000000014 73 FUNC GLOBAL DEFAULT 1 main 10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND add
看到了么,序号为10的条目那,名字变成了add。。
就是这样,有什么说的不对的地方,你倒是来打我啊不是欢迎大家批评指正~