1缘由
最近开发中遇到一个“undefinedreference”的连接问题,花了半小时时间排查。决定将这个问题解决思路捋顺一下。
”undefinedreference“问题发生在连接阶段,连接阶段无法找到定义。解决这几个问题一般思路:
a. 链接时缺失了相关目标文件.o。
b. 链接时缺少相关的库文件(.a/.so)。
c. 链接的库文件中又使用了另一个库文件。
d. 多个库文件链接顺序问题。(依赖其他库的库一定要放到被依赖库的前面)
e. 在c++代码中链接c语言的库。(gcc和g++编译方式存在差别)。
f. 使用工具nm、ldd去查看相关信息。
2问题情境
gcc编译一个可执行文件app,连接一个g++编译的.so库。报错找不到相应的函数。
3问题原因
函数参数packet在不同的模块定义了两个,用g++编译后,函数会编译为
000605ce T_Z11GetTermTypePK7_Packet,函数名加参数,因C++函数重载机制,导致函数编译后的符号带有packet,有因为程序多个模块定义了不同的packet,当你头文件用错后,导致在.c中gcc编译时,无法找到对象的定义。
4静态库和动态库
编译器编译成最终可执行的文件需要好几步,基本可以分为:文本解析->语法解析->此法分析->预处理分析->编译->连接。
在各个系统平台上,库文件的格式和形式各不相同,Windows下就是如同xxx.dll或者xxx.lib,Linux下就是xxx.so或者xxx.a。两种分别对应的是静态库和动态库,静态库会连同编译器编译链接进入程序成为程序的一部分,好处是作为程序的一部分不用每次运行时都去load(弊端是可能很多进程都用到这个库但是每个进程中都有一份,动态库的话内存中只有一份,通过重定向来加载),而且不会导致因库的缺失而运行失败,坏处是会导致可执行文件偏大。动态库是程序运行时动态加载到进程里去的,而且可以多进程共,并且方便软件更新,直接替换老的库即可。
4.1静态库和动态库的生成
看如下代码,main.c,a.cpp,a.h,b.cpp,b.h
b.h:
#ifndef __B_H__
#define __B_H__
extern int print();
extern int print(int b);
#endif
b.cpp:
#include <stdio.h>
#include "b.h"
int print()
{
printf("b\n");
return 0;
}
int print(int b)
{
printf("%d\n", b);
return 0;
}
a.h:
#ifndef __A_H__
#define __A_H__
extern int display();
#endif
a.cpp:
#include <stdio.h>
#include "a.h"
#include "b.h"
int display()
{
print();
}
Main.cpp:
#include <stdio.h>
#include "a.h"
int main()
{
display();
return 0;
}
生成动态库liba.so:
g++a.cpp b.cpp -fPIC -shared -o liba.so
然后编译main.cpp:
gcc-o main main.c liba.so
报错:main.c:(.text+0x7):undefined reference to `display'
原因:c中调用.cpp中的函数,找不到对应的函数符号,因为gcc和g++编译成的函数描述符不同。
g++编译:00000540 T_Z7displayv
gcc编译:000003e0 Tdisplay
以上很容易看出,display函数的描述符不同,所以找不到display。
解决办法,C中调用c++中的函数时,让其按C的函数解释方式执行。在c++头文件中添加如下模块:
#ifndef __A_H__
#define __A_H__
#ifdef __cplusplus
extern "C"{
#endif
int display();
#ifdef __cplusplus
}
#endif
#endif
__cplusplus为使用c++编译选项的宏。
这样display函数不能重载。
4.2C/C++编写的程序互调
在c/c++语言混合编程中,活做接口程序时,需考虑的问题,下面以举例的形式来说明。
4.2.1在C++中调用C库的例子:
1做一个C动态库:
// hello.c:
#include <stdio.h>
void hello()
{
printf("hello\n");
}
编译并copy到系统库目录下(也可以自己定义库目录,man ldconfig):
[root@coredump test]# gcc --shared -o libhello.so hello.c
[root@coredump test]# cp libhello.so /lib/
2).写个C++程序去调用它:
// test.cpp
#include <iostream>
#ifdef __cplusplus
extern "C" { // 告诉编译器下列代码要以C链接约定的模式进行链接
#endif
void hello();
#ifdef __cplusplus
}
#endif
int main()
{
hello();
return 0;
}
编译并运行:
[root@coredump test]# g++ test.cpp -o test -lhello
[root@coredump test]# ./test
hello
4.2.2C调用C++库:
上面那个例子已经说明。
5常用协助工具
1、nm [options]file 列出file中的所有符号,有助于调试函数符号。
[option]
-c 将符号转化为用户级的名字
-s 当用于.a文件即静态库时,输出把符号名映射到定义该符号的模块或成员名的索引
-u 显示在file外定义的符号或没有定义的符号
-l 显示每个符号的行号,或为定义符号的重定义项
2、ar {dmpqrtx}[member] archive file 用于操作高度结构化的存档文件(.a)
[options]
-c 创建存档文件
-s 创建或升级从符号到定义他们的成员之间的交叉索引映射表
-r 替换archive中的同名文件或添加新文件
-q 不检查而直接添加文件到存档文件的末尾
ranlib [-v|-V] file 的作用跟ar -sfile相同
3、ldd[options] file 列出file运行所需的共享库,以及常引用路径,如果不存在会报not found
[options]
-d 执行重定位并报告所有丢失的函数
-r 执行对函数和对象的重定位并报告丢失的任何函数或对象
4、 ldconfig [options][libs] 决定位于目录/usr/lib和/lib下的共享库所需的运行的链接,这些链接由[libs]指定并被保存到/etc/ld.so.conf中
[options]
-p 打印文件/etc/ld.so.conf的内容
-v 更新/etc/ld.so.conf
5、 ld.so 动态链接/加载器
ld.so使用的两个环境变量
$LD_LIBRARY_PATH 告诉ld.so去哪里查找保存在非标准目录下的共享库,冒号分隔,对应文件/etc/ld.so.conf
$LD_PRELOAD告诉ld.so用户指定的在所有库加载之前加载的库所在的目录,空格分隔,对应文件/etc/ld.so.preload