动态库

1、编译隐藏的过程
假设有一个源文件:

#include 
#pragma pack(2)

#define N 666
#define PR_D(x) printf(#x" = %d\n", x )
#define CONNECT(a,b) (a##b)

#ifdef N
    #define VERSION "2.0"
#else
    #define VERSION "0.1"
#endif

int ijk;

int main(void)
{
    printf("(%s,%s,%s,%d)\n", __TIME__, __FILE__, __FUNCTION__,  __LINE__); /* print __LINE__ */
    printf("%s\n", VERSION); // print version
    PR_D(ijk);
    printf("%d\n", CONNECT(i,jk) );

    return 0;
}

通常用 gcc 完整的编译命令是 gcc hello.c -o hello
上面的完整编译可以分解为 预处理、编译、汇编、链接。

预处理可以用命令 cpp hello.c > hello.i 或者 gcc -E hello.c -o hello.i

$ gcc -E hello.c
...
# 2 "hello.c" 2
#pragma pack(2)
# 14 "hello.c"

# 14 "hello.c"
int ijk;

int main(void)
{
    printf("(%s,%s,%s,%d)\n", "07:03:49", "hello.c", __FUNCTION__, 18);
    printf("%s\n", "2.0");
    printf("ijk"" = %d\n", ijk );
    printf("%d\n", (ijk) );

    return 0;
}

可以看到:
#include 被展开了,头文件内容太多不放在这里了。
#define 被展开了,除了__FUNCTION__,其他都替换了。
#if#ifdef 等条件编译被展开了,VERSION 已经替换成 "2.0" 了。
注释已经被删除了。
#pragma 会保留,在后面编译阶段处理。
添加了行号、文件名等标识,以便下一个过程编译可以产生调试用的行号,以及如果编译报错时可以打印行号。

如果代码里面有太多条件编译,不知道哪个定义了,哪个没定义时,可以查看 gcc -E的信息,或者直接使用#pragma message

#ifdef N
    #pragma message("N defined!")
#else
    #pragma message("N undeclared!")
#endif

编译阶段的命令是 gcc -S hello.i -o hello.s
汇编段的命令是 as hello.s -o hello.o 或者 gcc -c hello.s -o hello.o

查看 .c 代码的汇编代码可以用 gcc -S -g -o hello.s hello.c 查看 .o文件的汇编代码可以用 objdump -S hello.o > hello.s

2、静态链接
如果一些函数很通用,其他项目也要用,可以抽出来,单独做成库,这样就不用写重复代码了。
生成静态库:

gcc -c xxx.c -o xxx.o
ar -rcs libxxx.a xxx.o

链接:

gcc main.o -L . -lxxx -o s.out

可以查看静态库包文件中含了哪些文件:

$ ar -t libxxx.a
xxx.o

ar tv /usr/lib/gcc/x86_64-linux-gnu/8/libgcc.a

ar -x 可以从 *.a 解压出 *.o文件:

ar -x libxxx.a

静态链接的缺点:
一是浪费空间,每个可执行程序中都有一份所有需要的目标文件的副本;
二是更新比较麻烦,每当库函数的代码修改了,不仅要重新编译静态库,还需要重新链接所有用到该库的项目,以形成新的可执行程序。
优点就是执行的时候速度快一些。

3、动态链接
可执行文件较小,在程序运行时才将它们链接在一起形成一个完整的程序,不会在内存中存在多份副本。

生成动态库的命令:

gcc -c xxx.c -o xxx.o
gcc -fPIC -shared xxx.o -o libxxx.so

链接的命令和静态是一样的,不过动态链接后,运行时可能会报错

error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory

因为找不到该动态库,需要把库文件所在目录添加到环境变量:

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

查询一个可执行文件依赖哪些动态库:

$ ldd a.out 
    linux-vdso.so.1 (0x00007ffd04588000)
    libxxx.so => ./libxxx.so (0x00007f3989050000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3988c5f000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f3989454000)

查看某个函数是否在某个库文件中

$ nm -D /lib/x86_64-linux-gnu/libc.so.6 |grep xxx_1
$ nm -D libxxx.so |grep xxx_1
000000000000065a T xxx_1

除了 nm 命令,还可以使用 readelfobjdumpstrings 命令,这些命令都属于 GNU binutils 工具集。:

$ readelf -a libxxx.so |grep xxx_1
     8: 000000000000065a    31 FUNC    GLOBAL DEFAULT   12 xxx_1
    55: 000000000000065a    31 FUNC    GLOBAL DEFAULT   12 xxx_1
$ objdump -tT libxxx.so |grep xxx_1
000000000000065a g     F .text  000000000000001f              xxx_1
000000000000065a g    DF .text  000000000000001f  Base        xxx_1
$ strings libxxx.so |grep xxx_1
xxx_1
xxx_1
xxx_1

比如我们在嵌入式编程,改了一个函数,想看看效果,又不想完整编译、重新升级,这时候替换板子上的 .so 文件会比较快,这时候就需要确认一下这个函数属于哪一个库文件。

/usr/lib//usr/local/lib//lib/ 目录有很多 *.a 和 *.so 文件,可以用这些命令来测试玩一玩。

你可能感兴趣的:(动态库)