编译中的各种undefined reference解决方式和坑

 

最近在Linux下编程发现一个诡异的现象,就是在链接一个静态/动态库的时候总是报错,类似下面这样的错误:

(.text+0x13): undefined reference to `func'

    关于undefined reference这样的问题,大家其实经常会遇到,在此,详细地示例给出常见错误的各种原因以及解决方法。

1.  链接时缺失了相关目标文件(.o

    测试代码如下:

test.c 中:

int test(){return 0;}

Main.c中:

Int main(){return test();}

    然后编译。

gcc -c test.c

gcc –c main.c

    得到两个 .o 文件,一个是 main.o,一个是 test.o ,然后我们链接 .o 得到可执行程序:

gcc -o main main.o

  这时,你会发现,报错了:

main.o: In function `main':

main.c:(.text+0x7): undefined reference to `test'

collect2: ld returned 1 exit status

    这就是最典型的undefined reference错误,因为在链接时发现找不到某个函数的实现文件,本例中test.o文件中包含了test()函数的实现,所以如果按下面这种方式链接就没事了。

gcc -o main main.o test.o

   【扩展】:其实上面为了让大家更加清楚底层原因,把编译链接分开了,下面这样编译也会报undefined reference错,其实底层原因与上面是一样的。

gcc -o main main.c //缺少test()的实现文件

需要改成如下形式才能成功,将test()函数的实现文件一起编译。

gcc -o main main.c test.c //ok,没问题了

2.    链接时缺少相关的库文件(.a/.so

    在此,只举个静态库的例子,假设源码如下。

test.c 中:

int test(){return 0;}

    先把test.c编译成静态库(.a)文件

gcc -c test.c

ar -rc test.a test.o

    至此,我们得到了test.a文件。我们开始编译main.c

gcc -c main.c

    这时,则生成了main.o文件,然后我们再通过如下命令进行链接希望得到可执行程序。

gcc -o main main.o

    你会发现,编译器报错了:

/tmp/ccCPA13l.o: In function `main':

main.c:(.text+0x7): undefined reference to `test'

collect2: ld returned 1 exit status

    其根本原因也是找不到test()函数的实现文件,由于该test()函数的实现在test.a这个静态库中的,故在链接的时候需要在其后加入test.a这个库,链接命令修改为如下形式即可。

gcc -o main main.o ./test.a //注:./ 是给出了test.a的路径

     【扩展】:同样,为了把问题说清楚,上面我们把代码的编译链接分开了,如果希望一次性生成可执行程序,则可以对main.ctest.a执行如下命令。

gcc -o main main.c ./test.a //同样,如果不加test.a也会报错

3.    链接的库文件中又使用了另一个库文件

    这种问题比较隐蔽,也是我最近遇到的与网上大家讨论的不同的问题,举例说明如下,首先,还是看看测试代码。

Fun.c中:

Int fun(){return 0;}

Test.c

Int test(){return fun();}

Main.c中:

Int main(){return test();}

    从上可以看出,main.c调用了test.c的函数,test.c中又调用了fun.c的函数。    

 首先,我们先对fun.ctest.cmain.c进行编译,生成 .o文件。

gcc -c func.c

gcc -c test.c

gcc -c main.c

    然后,将test.cfunc.c各自打包成为静态库文件。

ar –rc func.a func.o

ar –rc test.a test.o

    这时,我们准备将main.o链接为可执行程序,由于我们的main.c中包含了对test()的调用,因此,应该在链接时将test.a作为我们的库文件,链接命令如下。

gcc -o main main.o test.a

    这时,编译器仍然会报错,如下:

test.a(test.o): In function `test':

test.c:(.text+0x13): undefined reference to `func'

collect2: ld returned 1 exit status

    就是说,链接的时候,发现我们的test.a调用了func()函数,找不到对应的实现。由此我们发现,原来我们还需要将test.a所引用到的库文件也加进来才能成功链接,因此命令如下。

gcc -o main main.o test.a func.a

    ok,这样就可以成功得到最终的程序了。同样,如果我们的库或者程序中引用了第三方库(如pthread.a)则同样在链接的时候需要给出第三方库的路径和库文件,否则就会得到undefined reference的错误。

4 多个库文件链接顺序问题

    这种问题也非常的隐蔽,不仔细研究你可能会感到非常地莫名其妙。我们依然回到第3小节所讨论的问题中,在最后,如果我们把链接的库的顺序换一下,看看会发生什么结果?

gcc -o main main.o func.a test.a

    我们会得到如下报错.

test.a(test.o): In function `test':

test.c:(.text+0x13): undefined reference to `func'

collect2: ld returned 1 exit status

    因此,我们需要注意,在链接命令中给出所依赖的库时,需要注意库之间的依赖顺序,依赖其他库的库一定要放到被依赖库的前面,这样才能真正避免undefined reference的错误,完成编译链接。

5. c++代码中链接c语言的库

    如果你的库文件由c代码生成的,则在c++代码中链接库中的函数时,也会碰到undefined reference的问题。下面举例说明。

    首先,编写c语言版库文件:

Test.c

Int test(){return 0;}

    编译,打包为静态库:test.a

gcc -c test.c

ar -rc test.a test.o

    至此,我们得到了test.a文件。下面我们开始编写c++文件main.cpp

    Main.cc中:

Int main(){return test();}

    然后编译main.cpp生成可执行程序:

g++ -o main main.cpp test.a

    会发现报错:

/tmp/ccJjiCoS.o: In function `main':

main.cpp:(.text+0x7): undefined reference to `test()'

collect2: ld returned 1 exit status

    原因就是main.cppc++代码,调用了c语言库的函数,因此链接的时候找不到,解决方法:即在main.cpp中,把与c语言库test.a相关的头文件包含添加一个extern "C"的声明即可。例如,修改后的main.cpp如下:

extern “C”

{

#include “test.h”

}    

Int main(){

return test();

}

g++ -o main main.cpp test.a

    再编译会发现,问题已经成功解决。

或者直接在test.h中加入:

ifdef __cplusplus

extern "C"

{

#endif

int cadd(int x, int y);

#ifdef __cplusplus

}

#endif

一样也可以解决

6. 编译参数加入-Wl--as-needed的好处和注意事项

--as-needed标志可使链接程序避免以二进制形式链接额外的库。这不仅缩短了启动时间(因为加载器不必每一步都加载所有库)更重要的是,使用--as-needed避免将依赖项添加到二进制文件中,这是其直接或间接依赖项之一的先决条件。

6.1 最终链接失败,未定义符号

这是使用时发生的最常见错误--as-needed。它发生在可执行文件的最后链接阶段(库不会造成问题,因为允许它们具有未定义的符号)。可执行链接阶段之所以消失,是因为在馈送到命令行的库中存在未定义的符号。但是,可执行文件本身未使用该库,因此该库将被删除--as-needed。这通常意味着一个库没有链接到另一个库,而是在使用它,然后依靠最终的可执行文件将它们链接在一起。对于使用该库的开发人员来说,这种行为也是一种额外的负担,因为他们必须检查需求。

解决这类问题的方法通常很简单:只需找到哪个库提供了符号,哪个库就需要它们(来自链接器的错误消息应包含后者的名称)。然后确保从源文件链接库时,它也链接到第一个库。

6.2 执行失败,未定义符号

有时,未定义的符号错误不会在链接时发生,而是在使用--as-need生成的应用程序执行时发生。但是,原因与链接中未定义符号的原因相同:直接链接的库未链接其依赖项之一。它还具有相同的解决方案:查找哪个库包含未定义的符号,并确保将其链接到提供它们的库。

6.3 链接顺序的重要性

尽管所有库都出现在链接行中,但它们只是被忽略而不是完全链接。这导致了与上述相同的问题;在最终链接或执行期间缺少符号。这是因为强制实施了GNU链接程序的行为--as-needed导致的

 

基本上,链接器所做的是仅在紧随其后的文件中查找给定文件(目标文件,静态归档或库)中缺少的符号。当使用普通链接时,如果不使用--as-needed,则这不是问题,尽管链接阶段可能存在一些内部缺陷,但是文件链接在一起却没有考虑顺序。但是使用该标志时,不用于解析符号的库将被丢弃,因此不会链接。

 

6.4错误和正确的链接顺序的编码示例

(这种情况下,libm在对象文件之前被考虑,并且独立于两者的内容而被丢弃,即不会被编译到pro

$ gcc -Wl--as-needed -lm someunit1.o someunit2.o -o pro

 

(这是仅在需要时才能链接libm的正确链接顺序。)

$ gcc -Wl--as-needed someunit1.o someunit2.o -lm -o程序

通常这种情况下,解决方法是简单地修复链接顺序,以使提供给链接器的库都位于目标文件和静态档案之后。

6.5 实例用法

1.  linux下查看一个可执行文件或动态库依赖哪些动态库的办法

readelf -d PyGalaxy.so

ldd   PyGalaxy.so

load 动态库过程基本的说就是符号重定位,然后合并到全局符号表。

  1. 在编译动态库时:关键的看as-needed,意思是说:只给用到的动态库设置DT_NEEDED。比如:

g++ -shared  PyGalaxy.o -lGalaxyParser -lxxx  -lrt  -o PyGalaxy.so

像这样链接一个PyGalaxy.so的时候,假设PyGalaxy.so里面用到了libGalaxyParser.so但是没 有用到libxxx.so。查看依赖关系如下:(不加不管什么指定了就加进来)

ocaml@ocaml:~$ readelf -d PyGalaxy.so

 0x0000000000000001 (NEEDED)             Shared library: [libGalaxyParser.so]

 0x0000000000000001 (NEEDED)             Shared library: [libxxx.so]

 

当开启–as-needed的时候,像

g++ -shared  -Wl,--as-needed PyGalaxy.o -lGalaxyParser -lxxx  -lrt  -o PyGalaxy.so

这样链接PyGalaxy.so的时候,查看依赖关系如下:

ocaml@ocaml:~$ readelf -d PyGalaxy.so

 0x0000000000000001 (NEEDED)             Shared library: [libGalaxyParser.so]

as-needed就是忽略链接时没有用到的动态库,只将用到的动态库set NEEDED

 

3 开启as-needed的一些常见的问题:

一)链接主程序模块(可执行程序bin)或者是静态库的时的‘undefined reference to: xxx’

:

g++ -Wl,--as-needed -lGalaxyRT -lc -lm -ldl -lpthread   -L/home/ocaml/lib/  -lrt -o mutex mutex.o

假设可执行程序mutex依赖libGalaxyRT.so中的东西。因为gcc对库的顺序要求和–as-needed(因为libGalaxyRT.somutex.o的左边,所以gcc认为没有用到它,–as-needed将其忽略),ld忽略libGalaxyRT.so,定位mutex.o的符号的时候当然会找不到符号的定义,所以‘undefined reference to’这个错误是正常地!

 

正确的链接方式是:

g++ -Wl,--as-needed mutex.o -lGalaxyRT -lc -lm -ldl -lpthread   -L/home/ocaml/lib/  -lrt -o mutex

二) 编译动态库(shared library)的时候会导致一个比较隐晦的错误

编译出来的动态库的时候没有问题,但是加载(link)的时候有“undefined symbol: xxx”这样的错误。

假如像这也链接PyGalaxy.so

g++ -shared  -Wl,--as-needed -lGalaxyParser -lc -lm -ldl -lpthread   -L/home/ocaml/lib/  -lrt  -o PyGalaxy.so PyGalaxy.o

load PyGalaxy.so的时候会有上面的运行时错误!

简单分析原因:因为libGalaxyParser.soPyGalaxy.o的左边,所以gcc认为没有用到–as-needed将其忽略。但是前面说的动态库符号解析的特点导致ld认为某些符号是加载(link)的时候才去地址重定位的。但是 libGalaxyParser.so已经被忽略了。所以就算你写上了依赖的库,load的时候也会找不到符号,因为编译库时已经被忽略啦,一般链接PyGalaxy.so库时会报未定义错误。但是为什么没有-Wl–as-needed的时候是正确的呢?没有的话,ldset NEEDED libGalaxyParser.so(用前面提到的查看动态库 依赖关系的办法可以验证)。load的时候还是可以找到符号的,所以正确因为没有会将所以的库都编译进去,不管是否需要,只要被列出

正确的链接方式是:

g++ -shared  -Wl,--as-needed PyGalaxy.o -lGalaxyParser -lc -lm -ldl -lpthread   -L/home/ocaml/lib/  -lrt  -o PyGalaxy.so

三) 对链接顺序导致问题的解决方案

在项目开发过层中尽量让lib是垂直关系,避免循环依赖;越是底层的库,越是往后面写!

例如:

g++ ...  obj($?) -l(上层逻辑lib) -l(中间封装lib) -l(基础lib) -l(系统lib)  -o $@

这样写可以避免很多问题,这个是在搭建项目的构建环境的过程中需要考虑 清楚地,在编译和链接上浪费太多的生命不值得!

四)通过-(-)强制repeat

-(-),它能够强制"The specified archives are searched repeatedly", 这就是我们要找的啦。比如:

g++ -shared  -Wl,--as-needed PyGalaxy.o Xlinker "-("-lGalaxyParser -lxxx  -lrt"-)"  -o PyGalaxy.so

简单解释一下,Xlinker是将后面的一个参数传给ld(这里就是 "-("-lGalaxyParser -lxxx -lrt"-)"),然后-(-)强制repeat当然就可以找到了可以没有顺序。但是这样的repeat需要浪费一些时间。

7. 编译指定库路径

在链接时语句后面添加如下命令:

-Wl,-rpath=my_thirdparty_lib_path

  对比一下添加前后的Makefile语句。not found时的语句:

 

 

 

  更改之后的语句:

 

 

 

  来看看更改之后的编译结果:

 

 

 

  可以看到,我的libpaho-mqtt3cs.so.1从我在文章开头时的【not found】变成了有来源了,而绿色部分的路径就是我刚刚Makefile中的-Wl,-rpath=之后的路径。

通常设置库的方式有四种:

第一种方法:找到缺少的动态库(由于编译和链接时候的使用到了这个动态库,所以很容易找得到),将其加到/lib,/usr/lib中的一个文件夹下,这几个文件夹是系统默认的搜索路径。将库文件放置在其中,运行时就可以搜索到了。

第二种方法:设置临时增加链接动态库的路径;使用

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:your_lib_path

比如我的libpaho-mqtt3cs.so.1/home/mqtt/MQTT-c/lib目录下,那我使用的是:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/mqtt/MQTT-c/lib

这种方法设置的是临时的,系统重启之后就没了。当然也可以设置为持久的,这里就不过多讲述。

还有一种方法是不常用的,更改配置文件:

   第三种方法:/etc/ld.so.cache中缓存了动态库路径,可以通过修改配置文件/etc/ld.so.conf中指定的动态库搜索路径,然后执行ldconfig命令来改变。

第四种就是-Wl,-rpath=my_thirdparty_lib_path

这四种方法的优先顺序:四->->->

posted @ 2021-01-14 20:29  白伟碧一些小心得  阅读(33495)  评论(0编辑  收藏  举报