1.简述
嵌入式Linux软件开发过程中,需要开发者自己编写makefile脚本编译程序,在编译程序时,经常会遇到“undefined reference to xxx”的 编译报错,下面是简单分析。
2.常见错误
- 链接时缺少相关的目标文件
~/linux$ cat main.c
int main()
{
test();
}
~/linux$
~/linux$ cat test.c
#include<stdio.h>
void test()
{
printf("test undefined reference");
}
~/linux$
~/linux$ gcc -c test.c
~/linux$ gcc -c main.c
main.c: In function ‘main’:
main.c:3:5: warning: implicit declaration of function ‘test’ [-Wimplicit-function-declaration]
test();
^
~/linux$
~/linux$ gcc -o main main.o
main.o: In function `main':
main.c:(.text+0xa): undefined reference to `test'
collect2: error: ld returned 1 exit status
~/linux$ gcc -o main main.o test.o
~/linux$
比较常见的“undefined reference to”问题,首先编译main.o的时候已经出现告警,此告警一般由下面两种情况造成的:
- 没有把函数所在的c文件生成.o目标文件
- 在函数所在的c文件中定义了,但是没有在与之相关联的.h文件中声明
在大型的编译工程中,很容易忽视这种告警问题,到链接的时候,出现“undefined reference to”问题。因为,在链接的时候发现找不到某个函数的实现文件。
- 链接时缺少相关的库文件
~/linux$ cat test.h
void test();
~/linux$ gcc -c test.c
~/linux$ ar -rc test.a test.o
~/linux$ gcc -c main.c
main.c: In function ‘main’:
main.c:3:5: warning: implicit declaration of function ‘test’ [-Wimplicit-function-declaration]
test();
^
~/linux$ gcc -o main main.o
main.o: In function `main':
main.c:(.text+0xa): undefined reference to `test'
collect2: error: ld returned 1 exit status
~/linux$ gcc -o main main.o ./test.a
~/linux$
出现“undefined reference to”问题,主要也是因为找不到test函数的实现,而test函数是在库中的,因此链接的时候添加库就能编译通过
- 链接的库文件中又使用了另一个库文件
~/linux$ ls
func.c func.h main.c test.c test.h
~/linux$ cat func.c
#include <stdio.h>
int func()
{
printf("enter func");
return 0;
}
~/linux$ cat func.h
int func();
~/linux$ cat main.c
#include "test.h"
int main()
{
test();
}
~/linux$ cat test.c
#include<stdio.h>
#include"func.h"
void test()
{
printf("enter test");
func();
}
~/linux$
~/linux$ cat test.h
void test();
~/linux$
~/linux$ gcc -c func.c
~/linux$ gcc -c test.c
~/linux$ gcc -c main.c
~/linux$ ar -rc func.a func.o
~/linux$ ar -rc test.a test.o
~/linux$ gcc -o main main.o test.a
test.a(test.o): In function `test':
test.c:(.text+0x19): undefined reference to `func'
collect2: error: ld returned 1 exit status
~/linux$
~/linux$ gcc -o main main.o test.a func.a
比较隐蔽的一类问题,链接的时候,发现test.a调用了func()函数,但却找不到对应的实现文件。将func()的实现库文件添加后编译可以通过
4.多个库文件链接顺序问题
~/linux$ gcc -o main main.o func.a test.a
test.a(test.o): In function `test':
test.c:(.text+0x19): undefined reference to `func'
collect2: error: ld returned 1 exit status
链接的时候需要注意库之间的依赖顺序:依赖其它库的库,一定要放在被依赖库的前面
- 在C++代码中连接c语言的库
~/linux$ cat test.c
#include<stdio.h>
void test()
{
printf("enter test");
}
~/linux$ cat test.h
void test();
~/linux$ cat main.cpp
#include "test.h"
int main()
{
test();
return 1;
}
~/linux$ gcc -c test.c
~/linux$ ar -rc test.a test.o
~/linux$ g++ -o main main.cpp test.a
/tmp/cced7bc4.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `test()'
collect2: error: ld returned 1 exit status
~/linux$
~/linux$ cat main.cpp
extern "C"
{
#include "test.h"
}
int main()
{
test();
return 1;
}
~/linux$ g++ -o main main.cpp test.a
库文件由c编译器生成的,在c++代码中链接时,产生“undefined reference to xxx”的问题。解决方法是在c相关的库文件的头文件添加extern “C”的声明
3.分析
一般而言,在Linux下编译程序分为以下4个阶段:
- 预处理。处理宏定义等宏命令(如#define等)——生成后缀为“.i”的文件;
- 编译。将预处理后的文件转换成汇编语言——生成后缀为“.s”的文件;
- 汇编。由汇编生成的文件翻译为二进制目标文件——生成后缀为“.o”的文件;
- 链接。多个目标文件(二进制)结合库函数等综合成的能直接独立执行的执行文件——生成后缀为“.out”的文件。
链接的查找顺序:
- -L 指定的路径, 从左到右依次查找
- 由环境变量LIBRARY_PATH指定的路径,使用":"分割从左到右依次查找
- /etc/ld.so.conf 指定的路径顺序
- /lib 和 /usr/lib (64位下是/lib64和/usr/lib64)
动态库调用的查找顺序:
- ld的-rpath参数指定的路径, 写死在代码中
- ld脚本指定的路径
- LD_LIBRARY_PATH 指定的路径
- /etc/ld.so.conf 指定的路径
- /lib和/usr/lib(64位下是/lib64和/usr/lib64)
对于动态链接库,实际的符号定位是在运行期进行的。在编译.so的时候,如果没有把它需要的库和他一起进行联编,比如libx.so 需要使用uldict, 但是忘记在编译libx.so的时候加上-luldict的话,在编译libx.so的时候不会报错。因为这个时候libx.so被认为是一个库,它里面存在一些不知道具体实现的符号是合法的,是可以在运行期指定或者编译另外的二进制程序的时候指定。
如果是采用g++ -L path -lx 的方式进行编译,链接器会发现所需要的uldict的符号表找不到而报错;但是如果是程序采用dlopen的方式载入,由于是运行期,这个程序在这个地方就直接运行报错了;另外还有一种情况就是一个对外的接口在动态库中已经声明定义了,但是忘记实现了,这个时候也会产生类似的错误。如果在运行期报出这样的错误,就要注意是否是由于某些库没有链接进来或者某些接口没有实现的原因产生。
因此,undefined reference error错误的原因是:
- 没有指定对应的库(.o/.a/.so)。 使用了链接库中定义的实体,但没有链接库(-lXXX)或者没有指定库路径(-LYYY);
- 链接库的参数顺序不对。越是基础的库,越要写在后面,无论是静态还动态;
- gcc/ld 版本不匹配。gcc/ld的版本的兼容性问题,当在高版本机器上使用低版本的机器编译的库,就会导致这样的错误。这个问题比较常见在32位的环境上,使用了64位的库,或者反过来64位环境使用了32位的库;
- C/C++相互依赖。后缀为.c的,gcc把它当作是C程序(cc/cpp两个编译器均判定为c++程序),而g++当作是c++程序。使用extern "C"保证g++编译的C++代码能够调用gcc编译的c代码 ;而在我们的64位环境中gcc链接g++的库还需要加上 -lstdc++;
- 运行期报错。由于程序使用dlopen的方式载入.so库, 但.so没有把所有需要的库都链接上。
参考
https://segmentfault.com/a/1190000006049907?utm_source=tuicool&utm_medium=referral