标签 gcc 下的文章

参考:extern "C"语句在C++中的作用

一、问题描述

在编译C++程序时,遇到以下问题:

/tmp/cccZeFer.o: In function `main':
main.cpp:(.text+0xf): undefined reference to `****'
collect2: error: ld returned 1 exit status

看到错误的第一直觉是共享库出问题了,因为以前出现这个问题都是因为库没有加进来,但是反复确认过后发现共享库并没有问题。

第一:编译的时候使用-l选项包含了库文件,并且库里面的函数也存在。

第二:库确实存在,不然也不会报上面的错误了,报的错误应该是:

/usr/bin/x86_64-linux-gnu-ld: cannot find -l***
collect2: error: ld returned 1 exit status

试了各种方法都无效,百思不得其解,最后无意间发现竟然是c和c符号表不兼容导致的。

因为库是c编译的,代码是c编译的,c和c的符号表规则不一致,导致编译c时找不到符号,因此编译报错。

二、重现

准备一个库libadd.so和一个源文件main.cpp

> tree
.
├── libadd
│   ├── add.c
│   ├── add.h
│   └── Makefile
├── main.cpp
└── Makefile

1 directory, 5 files

add.hadd.c的内容:

> cat libadd/add.h
int add(int i, int j);

> cat libadd/add.c
#include "add.h"

> cat libadd/Makefile
app:
    gcc add.c -fPIC -shared -o libadd.so

编译libadd.so能够正常编译,然后编译main:

> cat main.cpp 
#include "libadd/add.h"
#include <iostream>

int main() {
    std::cout << add(1, 2) << std::endl;
    return 0;
}

> cat Makefile 
app:
    g++ main.cpp -Llibadd -ladd

此时编译就报错:

/tmp/cccZeFer.o: In function `main':
main.cpp:(.text+0xf): undefined reference to `add'
collect2: error: ld returned 1 exit status

解决方案

在add.h中使用宏定义把函数声明为c导出的函数:

#ifdef __cplusplus
extern "C" {
#endif

int add(int i, int j);

#ifdef __cplusplus
}
#endif

再编译就能通过了。

一、 问题描述

最近工作中遇到了一个问题:项目需要合入其他部门的模块,但是其中的一个共用共享库被更新了。因为项目很大,如果直接在我们的环境中替换更新这个库,很有可能会影响到其他模块。祖传的代码流传了差不多20年,涉及的模块也十分之多,贸然升级的风险很难评估。但是不替换这个库第三方模块又跑不起来,一度头痛。

刚开始是想到了以下几个方法:

  1. 设置LD\_LIBRARY\_PATH环境变量,修改查找路径的优先级。
  2. 修改so库名

对于第一种方法是有效的:在程序目录下加个lib目录,然后 export LD_LIBRARY_PATH=pwd/lib:$LD_LIBRARY_PATH 把当前路径放到第一搜索顺序,能解决这个问题。但是,环境变量时当前所有程序共享的,其他程序的搜索目录也是这里第一,所以这里的结果就和直接升级库没有区别。同样,修改/etc/ld.so.conf的原理也是一样。

第二种方法本来是认为比较靠谱的,但是测试发现共享库并不是根据文件名来的,修改库名无效。so库内部还有个真实的名字,可以通过 readelf -d lib*.so 来查看:

> readelf /usr/lib/libau.so -d

Dynamic section at offset 0x2c88 contains 29 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so.2]
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libau.so.2]

所以上面两种方法就被派出了,后面查了很久也没有找到合适的办法,崩溃。。。

最后去请教大佬就被告知了可以使用 -Wl,-rpath 选项来解决这个问题,试了一下确实可以。

二、-Wl,-rpath和-Wl,-rpath-link选项

2.1 -Wl,-rpath

加上 -Wl,-rpath 选项的的作用就是指定“程序运行时”的库搜索目录,是一个链接选项,生效于设置环境变量之前。

我们已经知道,共享库的查找顺序为:

  1. LD_LIBRARY_PATH 环境变量的目录
  2. ld.so.conf 高速缓冲文件中的目录
  3. 系统的默认库目录如 /lib, /lib64

-Wl,-rpath 可以让程序在第一步搜索之前先搜索它所指定的目录,通过一个例子来说明:

// add.h
int add(int i, int j);

// add.c
#include "add.h"

int add(int i, int j) {
    return i + j;
}

// main.c
#include <stdio.h>
#include "add.h"

int main() {
    printf("1 + 2 = %d\n", add(1, 2));
    return 0;
}

add.h和add.c用于生成一个so库,实现了一个简单的加法,main.c中引用共享库计算1 + 2:

# 编译共享库
gcc add.c -fPIC -shared -o libadd.so
# 编译主程序
gcc main.o -L. -ladd -o app

编好后运行依赖库:

> ldd app 
    linux-vdso.so.1 (0x00007ffeb23ab000)
    libadd.so => not found
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007febb7dd0000)
    /lib64/ld-linux-x86-64.so.2 (0x00007febb83d0000
> ./app 
./app: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory

可以看到,libadd.so这个库是没有找到的,程序也无法运行,要运行它必须要把当前目录加到环境变量或者库搜索路径中去。

但是如果在链接的时候加上 -Wl,-rpath 选项之后:


> gcc -Wl,-rpath=`pwd` main.o -L. -ladd -o app
> ldd app 
    linux-vdso.so.1 (0x00007fff8f4e3000)
    libadd.so => /data/code/c/1-sys/solib/libadd.so (0x00007faef8428000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faef8030000)
    /lib64/ld-linux-x86-64.so.2 (0x00007faef8838000)
> ./app 
1 + 2 = 3

依赖库的查找路径就找到了,程序能正常运行。

2.2 -Wl,rpath-link

-Wl,rpath-link 是设置编译链接时候的顺序,例如app运行依赖libadd.so,但是libadd.so又依赖libadd\_ex.so,rpath-link 就是指定libadd\_ex.so的路径。和 -Wl,rpath 相比工作的时间不同,一个在链接期间,一个在运行期间。

三、其他相关

程序从编译和链接的过程

linux静态库和动态库的使用方法

‘xxx’: error while loading shared libraries的解决方案

  • -l:表明需要一起编译的库文件。
  • -L:添加库文件所在的目录,只针对静态库。
  • -c:生成目标文件*.o
  • -o:指定输出文件。

静态库的使用方法

库文件名字为libxxx.a或者libxxx.so,使用时通过-l链接:

gcc -lxxx main.c

如果库文件不在/lib, /usr/lib/usr/local/lib三者中的任一个文件夹的话,要通过-L选项指定库所在的目录,否则会报错:/usr/bin/ld: cannot find -lxxx

> make
gcc main.c -lfunc1 -o debug/app # 没有制定库所在路径报错
/usr/bin/ld: cannot find -lfunc1
collect2: error: ld returned 1 exit status
Makefile:9: recipe for target ''app'' failed
make: *** [app] Error 1
> vi Makefile # 修改Makefile添加-L选项
> make
gcc main.c -L. -lfunc1 -o debug/app # 编译成功

升级GCC后运行程序出现错误:

/usr/lib64/libstdc++.so.6: version "CXXABI_1.3.9" not found

问题的原因是因为升级GCC后相应的动态库没有更新,程序找不到新版本GCC库中的符号,运行不成功。

使用ls 命令查看该文件可以看到动态库是一个软链接:

> ls -l /usr/lib64/libstdc++.so.6
lrwxrwxrwx 1 root root 19 Aug 24 12:28 /usr/lib64/libstdc++.so.6 -> libstdc++.so.6.0.13

- 阅读剩余部分 -