Linux/Uinx 静态与动态链接以及示例代码

静态与动态链接以及GCC代码示例

静态与动态链接

静态库和动态库的区别如下:

静态库

在链接时,链接器将所有必要的库函数代码和数据全部融入到.out文件中,这样会使得.out文件完整,独立,但是通常非常庞大。

让我们以一个简单的C程序为例:

#include 

int main() {
    printf("Hello, World!\n");
    return 0;
}

在编译和链接这个程序时,链接器会将 printf 函数的代码和数据从C标准库中取出,然后将它们融入到生成的.out文件中。这就是为什么你可以在没有C标准库的系统上运行这个程序,因为所有必要的代码和数据都已经包含在.out文件中了。

然而,这种方式有一个缺点,那就是.out文件可能会变得非常庞大。因为即使你的程序只使用了库中的一个小功能,链接器也会将整个库都包含进来。例如,如果你的程序只使用了printf,但是C标准库还包含了许多其他函数,那么链接器仍然会将整个C标准库都包含到.out文件中,从而使得.out文件变得非常庞大。

动态库

动态库linux系统中通常叫做共享库(.so文件),共享库是怎么工作的呢?这种库在程序运行时才会被加载到内存中,而不是在编译链接时就被包含到可执行文件中,这样做有以下几个好处:

  • 可以减小每个 a.out 文件的大小。
  • 许多执行程序可在内存中共享相同的库函数
  • 修改库函数不需要重新编译源文件

在共享库中,对一类函数的调用以指令性是记录在.out中

下面是动态库的例子:

同样的,该程序使用了printf函数:

#include 

int main() {
    printf("Hello, World!\n");
    return 0;
}

在编译和链接这个程序时,如果我们使用动态链接(也就是链接到一个共享库),那么printf函数的代码和数据不会被包含到生成的.out文件中。相反,这些代码和数据会保留在C标准库(一个共享库)中。

当然,如果将动态链接生成的.out文件搬运到没有C标准库的电脑上,这个程序就不能运行了。

GCC静态库和共享库生成示例

GCC静态库生成

假设我们有两个C源文件add.csubtract.c,它们的内容如下:

add.c:

int add(int a, int b) {
    return a + b;
}

subtract.c:

int subtract(int a, int b) {
    return a - b;
}

我们还有一个main.c文件,它使用了addsubtract函数:

main.c:

#include 

int add(int a, int b);
int subtract(int a, int b);

int main() {
    printf("%d\n", add(3, 4));
    printf("%d\n", subtract(3, 4));
    return 0;
}

我们可以按照以下步骤将add.csubtract.c编译为静态库,并将main.c链接到这个库:

  1. 首先将add.csubtract.c编译为目标文件

    gcc -c add.c
    gcc -c subtract.c
    

    这将生成add.osubtract.o文件。

  2. 然后将这两个目标文件打包为一个静态库

    ar rcs libcalc.a add.o subtract.o
    

    这将生成libcalc.a静态库。

  3. 最后将main.c编译并链接到我们刚刚创建的库

    gcc main.c -L. -lcalc -o main
    

    这将生成main可执行文件。-L.选项告诉GCC在当前目录下查找库,-lcalc选项告诉GCC链接名为libcalc.a的库。

  4. 现在,我们可以运行main程序了:

    ./main
    

    这将输出7-1,这分别是add(3, 4)subtract(3, 4)的结果。

GCC共享库生成

假设我们有两个C源文件add.csubtract.c,它们的内容如下:

add.c:

int add(int a, int b) {
    return a + b;
}

subtract.c:

int subtract(int a, int b) {
    return a - b;
}

我们还有一个main.c文件,它使用了addsubtract函数:

main.c:

#include 

int add(int a, int b);
int subtract(int a, int b);

int main() {
    printf("%d\n", add(3, 4));
    printf("%d\n", subtract(3, 4));
    return 0;
}

按照以下步骤将add.csubtract.c编译为动态库,并将main.c链接到这个库:

  1. 首先,将add.csubtract.c编译为位置无关代码(PIC):

    gcc -c -fPIC add.c
    gcc -c -fPIC subtract.c
    

    这将生成add.osubtract.o文件。

  2. 然后,将这两个目标文件链接为一个动态库:

    gcc -shared -o libcalc.so add.o subtract.o
    

    这将生成libcalc.so动态库。

  3. 最后,将main.c编译并链接到我们刚刚创建的库:

    gcc main.c -L. -lcalc -o main
    

    这样做将会生成main可执行文件。-L.选项告诉GCC在当前目录下查找库,-lcalc选项告诉GCC链接名为libcalc.so的库。

  4. 在运行main程序之前,我们需要告诉操作系统在哪里可以找到我们的动态库。我们可以通过设置LD_LIBRARY_PATH环境变量来实现这一点:

    export LD_LIBRARY_PATH=.
    
  5. 运行main程序了:

    ./main
    

    这将输出7-1,这分别是add(3, 4)subtract(3, 4)的结果。

关于位置无关代码PIC

位置无关代码(Position Independent Code,简称PIC)是指代码无论被加载到内存的哪个地址,都能正常运行。这是因为代码里没有使用绝对地址,都是相对地址。在编译链接时,符号地址的确定可以在编译链接时确定,这种技术对应静态链接,而大部分静态语言之所以叫做静态也是因为变量地址在编译时就已经确定了²。当然符号地址也可以在装载(load)时确定,动态链接就用到了这种技术。

例如,我们有一个动态链接库,这个库的代码可以在任意位置执行,这样的代码就是位置无关代码。这种代码放在什么位置都能正常运行,所以地址肯定是动态的不能固定的。这样的代码在数据段开始为每一个全局符号保留了一个条目(GOT global offset table),每一个条目中保存了全局符号的绝对地址。每次对动态链接中全局符号的引用,首先找到GOT中的条目,然后获得全局符号的地址,这样就实现了位置无关代码。





创作不易,点个赞和关注再走吧!!

你可能感兴趣的:(Linux/Uinx系统编程,linux,运维,c语言)