Crane
Table_bottom

Search
Loading
Table_bottom

分类
Table_bottom

随机文章
Table_bottom

标签云
Table_bottom

最新评论
Table_bottom

链接
Table_bottom

功能
Table_bottom

Linux下用g++编译共享库的一个问题

最近在使用linux下的共享库so的时候遇到一个奇怪的问题,做个记录,方便备查。

一般来说,如果用gcc编译的时候加上-shared和-fPIC选项,可以把源文件编译成一个so文件,可以在其他源程序连接阶段把这个链接上去,从而可以调用so文件提供的函数接口,这样可以多文件共用一个so文件提供的函数,即节省内存空间,也便于更新,所有的接口只需要更新so文件就行。

其实除了上面的方法,还有一个方法,那就是在运行时由程序自己动态加载so文件,使用一系列系统调用如dlopen,dlsym,dlclose等来进行动态加载,获取函数地址从而进行函数调用,关闭加载的so文件等。

一般以第一种方法用得多,但是第二种方法更灵活,结合配置文件,更具一般意义上的服务扩展性。 但是就是在用第二种方法的时候出现了一点问题。

这里把问题抽象一下,假设有一个源文件是要编译为so文件的,假设这个源文件只提供一个简单的函数,add,取两个整数为参数,返回它们的和,源文件为add.c,代码如下:

#include <stdio.h> 
 
int add(int a, int b) 
{ 
    return a+b; 
} 

编译

gcc -shared -fPIC add.c -o libadd.so

 

然后写个脚手架程序来测试,test.c,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int main(int argc, char *argv[])
{
    void * handle;
    int (*func)(int, int);
    char *error;

    handle = dlopen("libadd.so", RTLD_LAZY);
    if(!handle)
    {
        fprintf(stderr, "%s\n", dlerror());
        exit(1);
    }

    func = (int (*)(int,int))dlsym(handle, "add");
    if((error = dlerror()) != NULL)
    {
        fprintf(stderr, "%s\n", error);
        exit(1);
    }

    func(3, 4);
    dlclose(handle);

    return 0;
}
 

编译

gcc test.c -o test -ldl

然后修改LD_LIBRARY_PATH变量,加上库所在的目录,这里是当前目录

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
 

好的,这样是没有问题的,这样的方法是标准的动态加载so库的方法,但是问题在什么地方呢,如果把gcc换成g++,问题就出现了 整个编译完成后,执行test,它会告诉你

./libadd.so: undefined symbol: add
 

找不到符号add 然后用nm看一下libadd.so的符号名

$ nm libadd.so |grep add

00000000000005ec T _Z3addii

它娘的,这是c++编译器的符号命名法,用c++filt看一下

$ c++filt _Z3addii

add(int, int)

看吗,这才是我们很显而易见的函数名 所以,解决方案出来,把库源文件的函数当C符号来处理,这样

extern "C" {
// the function code
...
}

然后编译运行,这就是好的,编译出来后用nm看一下

$ nm libadd.so |grep add

00000000000005dc T add

哈哈,这就好了,然后在dlsym中写符号名的时候就直接写add就行了。

这里例子虽然是用g++编译纯C文件,其实是真正的cpp文件也是可以的。