动态链接中的名字冲突问题

3820阅读 0评论2016-11-15 lvyilong316
分类:C/C++

动态链接中的名字冲突问题

——lvyilong316

我们知道当使用静态链接时,如果参与链接的不同文件中有相同名称的全局变量或者函数的话,在连接的时候就会报错,因为名字冲突。那么动态连接的情况又是怎么样呢?我们先看一个例子。

1. 使用动态连接器装载

sotest1.c

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int count = 0;
  5. void funtest1(void){
  6.      int i = 5;
  7.      while (i > 0 ) {
  8.         printf("in funtest1, count=%d, addr: %x\n", count, &count);
  9.         i--;
  10.         count++;
  11.         sleep(1);
  12.     }
  13. }

文件sotest1.c中定义了一个全局变量count,同时定义了函数functest1每隔一秒打印一次count的值和地址,同时自增count。下面将这个文件编译成动态库:

gcc -shared -fPIC sotest1.c -o sotest1.so

说明: -shared 表示产生共享对象,-fPIC知道生成地址无关代码(采用全局偏移表GOT实现)。不使用-fPIC参数不影响共享对象的装载,但是代码就不能被多个进程共享(每个进程的代码段都要有一份共享对象代码段的拷贝),于是也就失去了节省内存的优点。

    下面看main函数。

demo.c

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int count = 0;
  5. int main(int argc, char *argv[])
  6. {
  7.     int i = 5;
  8.     while (i > 0) {
  9.         printf("count = %d, addr: %x\n", count, &count);
  10.         i--;
  11.         count++;
  12.         sleep(1);
  13.     }
  14.     funtest1();
  15.     i = 5;
  16.     while (i > 0) {
  17.         printf("count = %d, addr: %x\n", count, &count);
  18.         i--;
  19.         count++;
  20.         sleep(1);
  21.     }
  22. }

    demo.c文件中同样定义了一个全局变量count,同时在main函数中调用sotest.c文件中的funtest1函数,并在调用前后分别打印5count的值和地址。下面编译连接demo.c

    gcc demo.c sotest1.so -o demo -Xlinker -rpath ./

    说明: -Xlinker -rpath ./ 表示连接器在当前路径下寻找共享对象,否则连接器回报无法找到sotest1.so的错误。(也可以将so拷贝到/lib /usr/lib

下面运行下demo程序,运行结果如下:

从结果可以看到,funtest1函数中使用的count变量并不是sotest.so共享对象中的count,而是demo.c中的count。这是什么原因呢?

首先引入一个概念:全局符号介入——指一个共享对象中的全局符号被另一个共享对象的同名全局符号覆盖的现象。关于全局符号介入,实际上linux下的动态链接器是这样处理的:它定义了一个规则,那就是当一个符号需要被加入全局符号表时,如果相同的符号名称已经存在,则后加入的符号被忽略

由于存在这种同名符号被直接忽略的问题,当程序大量使用共享对象时应该非常小心符号重名问题,如果两个符号重名又执行不同的功能,那么程序运行时可能会将所有该符号的引用解析到第一个被加入全局符号表的使用该符号名的符号,从而导致程序莫名其妙的错误。所以尽量少的使用全局变量,添加static关键字。

2. 显示运行时装载

之前我们使用的linux中的动态链接器进行链接,而动态连接器生成的可执行文件,会在执行的开始阶段将由动态连接器装载所引用的共享对象(.so)。我们也可以不使用动态连接器而在程序运行过程中显示的装载,这是通过动态链接器提供的一组API(dlopen、dlclose、dlerror、dlsym)进行的。如果显示装载的情况,名字冲突又会是什么现象呢?下面在看一个例子:

demo.c

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <dlfcn.h>
  5. int count = 0;
  6. int main(int argc, char *argv[])
  7. {
  8.     int i = 5;
  9.     void *handle;
  10.     char *error;
  11.     void (*fun)(void);
  12.     while (i > 0) {
  13.         printf("count = %d, addr: %x\n", count, &count);
  14.         i--;
  15.         count++;
  16.         sleep(1);
  17.     }
  18.     handle = dlopen(argv[1], RTLD_NOW);
  19.     if (handle == NULL) {
  20.         printf("Open library %s error:%s\n", argv[1], dlerror());
  21.         return -1;
  22.     }
  23.     fun = dlsym(handle, "funtest1");
  24.     fun();
  25.     i = 5;
  26.     while (i > 0) {
  27.         printf("count = %d, addr: %x\n", count, &count);
  28.         i--;
  29.         count++;
  30.         sleep(1);
  31.     }
  32.     dlclose(handle);
  33. }

和之前的demo.c不同的是这里使用dlopen函数在运行时打开sotest1.so,并调用其中的funtest1函数。下面编译demo.c:

gcc -o demo ./demo.c -ldl

-ldl表示使用DL库,它位于/lib/libdl.so.2.

     我们运行demo程序,结果如下图:

我们发现,显示使用dlopen打开时,共享对象sotest1.so中的count并没有被覆盖,而是和demo.c中的count变量各自保持独立。所以我们得到一个结论:采用显示装载时不会有全局符号介入问题

上一篇:内核页表和进程页表
下一篇:Linux系统中RPS/RFS介绍