链接、装载和库看完这个系列就够了(五)(符号同名问题续)

之前讲过了静态库和动态库同名的问题,现在来更加深入的了解一下。

两个动态库调用含同名符号的静态库

先说一下为什么会出现这种情况?比如有两个不同的开源的动态库,但是这两个动态库都同时调用了一个静态库,比如openssl,但是这两个动态库调用的openssl的版本是不同的,可能存在一定的兼容性问题,这样,当一个工程中同时调用这两个开源库的时候可能会出现问题。我们来测试一下,首先是两个不同的静态库:

//lib_fun1.c 
#include <stdio.h>
#include "lib_fun1.h"
int fun()
{
        printf("in lib_fun1\n");
        return 0;
}
//lib_fun2.c 
#include <stdio.h>
#include "lib_fun2.h"
int fun()
{
        printf("in lib_fun2\n");
        return 0;
}

然后是两个动态库,这两个动态库分别调用上面的静态库:

//lib_so1.c 
#include "lib_fun1.h"
int fun_so1()
{
        fun();
        return 0;
}
//lib_so2.c 
#include "lib_fun1.h"
int fun_so2()
{
        fun();
        return 0;
}

然后同时调用这两个动态库:

//main.c 
int fun_so1();
int fun_so2();
int main()
{
        fun_so1();
        fun_so2();
        return 0;
}
#Makefile 
all:main

lib_so1.so: lib_so1.o lib_a1.a
        gcc -shared -o $@ $< -l_a1 -L.
lib_so2.so: lib_so2.o
        gcc -shared -o $@ $< -l_a2 -L.
lib_a1.a: lib_fun1.o lib_a2.a
        ar rcs $@ $<
lib_a2.a: lib_fun2.o
        ar rcs $@ $<

main: main.o lib_so1.so lib_so2.so
        gcc -o $@ main.o -l_so1 -l_so2 -L. -Wl,--rpath=.

%.o : %.c
        gcc -fPIC -c $< -o $@

clean:
        rm -rf *.o *.a *.so main

编译执行:

# make
gcc -fPIC -c main.c -o main.o
gcc -fPIC -c lib_so1.c -o lib_so1.o
gcc -fPIC -c lib_fun1.c -o lib_fun1.o
gcc -fPIC -c lib_fun2.c -o lib_fun2.o
ar rcs lib_a2.a lib_fun2.o
ar rcs lib_a1.a lib_fun1.o
gcc -shared -o lib_so1.so lib_so1.o -l_a1 -L.
gcc -fPIC -c lib_so2.c -o lib_so2.o
gcc -shared -o lib_so2.so lib_so2.o -l_a2 -L.
gcc -o main main.o -l_so1 -l_so2 -L. -Wl,--rpath=.
# ./main 
in lib_fun1
in lib_fun1

我们发现两个库输出相同的信息,第二个库并不是调用自身的函数。我们交换一下链接的顺序,会发现输出改变了:

# gcc -o main main.o -l_so2 -l_so1 -L. -Wl,--rpath=.
# ./main 
in lib_fun2
in lib_fun2

我们看一下fun符号的属性,以便后面做对比:

readelf -s lib_so1.so |grep fun
    11: 000000000000066f    23 FUNC    GLOBAL DEFAULT   12 fun
    12: 000000000000065a    21 FUNC    GLOBAL DEFAULT   12 fun_so1
    34: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS lib_fun1.c
    47: 000000000000066f    23 FUNC    GLOBAL DEFAULT   12 fun
    48: 000000000000065a    21 FUNC    GLOBAL DEFAULT   12 fun_so1

我们发现fun符号的属性时GLOBAL,也就是全局可见的。如何解决这个问题?

怎么解决这个问题?

显然不能在函数前面加上static,因为他们是跨文件引用的。此时可以使用__attribute__((visibility(“hidden”))),这个修饰的意思是使符号对库外不可见。
我们修改一下代码:

//lib_fun1.c 
#include <stdio.h>
#include "lib_fun1.h"
__attribute__((visibility("hidden"))) int fun()
{
        printf("in lib_fun1\n");
        return 0;
}
//lib_fun2.c 
#include <stdio.h>
#include "lib_fun2.h"
__attribute__((visibility("hidden"))) int fun()
{
        printf("in lib_fun2\n");
        return 0;
}

编译执行:

# ./main 
in lib_fun1
in lib_fun2

我们发现程序正常输出了。
我们再看一下符号的属性:

readelf -s lib_so1.so |grep fun
    11: 000000000000060a    21 FUNC    GLOBAL DEFAULT   12 fun_so1
    34: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS lib_fun1.c
    38: 000000000000061f    23 FUNC    LOCAL  DEFAULT   12 fun
    48: 000000000000060a    21 FUNC    GLOBAL DEFAULT   12 fun_so1

我们发现fun符号的属性不再是GLOBAL,而是LOCAL,也就是本地的,外部不能调用,类似static
注:

  1. attribute((visibility(“hidden”)))是声明,并不一定需要加在在函数定义的时候,也可以在头文件中声明
  2. 也可以在编译中添加参数**-fvisibility=hidden达到相同的效果,该参数加在编译**.c的时候,而不是链接
  3. 如果我们看一下生成的.o的话可能有一些区别:
readelf -s lib_so1.o |grep fun
     8: 0000000000000000    21 FUNC    GLOBAL DEFAULT    1 fun_so1
     9: 0000000000000000     0 NOTYPE  GLOBAL HIDDEN   UND fun

fun符号的属性是HIDDEN

《程序员的自我修养:链接,装载》是一本由林锐、郭晓东、郑蕾等人合著的计算机技术书籍,在该书,作者从程序员的视角出发,对链接装载等概念进行了深入的阐述和解析。 在计算机编程链接是指将各个源文件的代码模块组合成一个可执行的程序的过程。链接可以分为静态链接和动态链接两种方式。静态链接是在编译时将所有代码模块合并成一个独立的可执行文件,而动态链接是在运行时根据需要加载相应的代码模块。 装载是指将一个程序从磁盘上加载到内存准备执行的过程。在装载过程,操作系统会为程序分配内存空间,并将程序的各个模块加载到相应的内存地址上。装载过程还包括解析模块之间的引用关系,以及进行地址重定位等操作。 是指一组可重用的代码模块,通过链接装载的方式被程序调用。可以分为静态库动态库静态库是在编译时将的代码链接到程序,使程序与的代码合并为一个可执行文件。动态库则是在运行时通过动态链接的方式加载并调用。 《程序员的自我修养:链接,装载》对于理解链接装载的原理和机制具有极大的帮助。通过学习这些概念,程序员可以更好地优化代码结构和组织,提高程序的性能和可维护性。同时,了解链接装载的工作原理也对于进行调试和故障排除具有重要意义。 总之,链接装载是计算机编程的重要概念,对于程序员来说掌握这些知识是非常必要的。《程序员的自我修养:链接,装载》这本书提供了深入浅出的解释和实例,对于想要学习和掌握这些知识的程序员来说是一本非常有价值的参考书籍。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值