静态库和动态库的区别如下:
在链接时,链接器将所有必要的库函数代码和数据全部融入到.out
文件中,这样会使得.out
文件完整,独立,但是通常非常庞大。
让我们以一个简单的C程序为例:
#include <stdio.h>
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 <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
在编译和链接这个程序时,如果我们使用动态链接(也就是链接到一个共享库),那么printf函数的代码和数据不会被包含到生成的.out
文件中。相反,这些代码和数据会保留在C标准库(一个共享库)中。
当然,如果将动态链接生成的.out
文件搬运到没有C标准库的电脑上,这个程序就不能运行了。
假设我们有两个C源文件add.c
和subtract.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
文件,它使用了add
和subtract
函数:
main.c
:
#include <stdio.h>
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.c
和subtract.c
编译为静态库,并将main.c
链接到这个库:
首先将add.c
和subtract.c
编译为目标文件:
gcc -c add.c
gcc -c subtract.c
这将生成add.o
和subtract.o
文件。
然后将这两个目标文件打包为一个静态库:
ar rcs libcalc.a add.o subtract.o
这将生成libcalc.a
静态库。
最后将main.c
编译并链接到我们刚刚创建的库:
gcc main.c -L. -lcalc -o main
这将生成main
可执行文件。-L.
选项告诉GCC在当前目录下查找库,-lcalc
选项告诉GCC链接名为libcalc.a
的库。
现在,我们可以运行main
程序了:
./main
这将输出7
和-1
,这分别是add(3, 4)
和subtract(3, 4)
的结果。
假设我们有两个C源文件add.c
和subtract.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
文件,它使用了add
和subtract
函数:
main.c
:
#include <stdio.h>
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.c
和subtract.c
编译为动态库,并将main.c
链接到这个库:
首先,将add.c
和subtract.c
编译为位置无关代码(PIC):
gcc -c -fPIC add.c
gcc -c -fPIC subtract.c
这将生成add.o
和subtract.o
文件。
然后,将这两个目标文件链接为一个动态库:
gcc -shared -o libcalc.so add.o subtract.o
这将生成libcalc.so
动态库。
最后,将main.c
编译并链接到我们刚刚创建的库:
gcc main.c -L. -lcalc -o main
这样做将会生成main
可执行文件。-L.
选项告诉GCC在当前目录下查找库,-lcalc
选项告诉GCC链接名为libcalc.so
的库。
在运行main
程序之前,我们需要告诉操作系统在哪里可以找到我们的动态库。我们可以通过设置LD_LIBRARY_PATH
环境变量来实现这一点:
export LD_LIBRARY_PATH=.
运行main
程序了:
./main
这将输出7
和-1
,这分别是add(3, 4)
和subtract(3, 4)
的结果。
位置无关代码(Position Independent Code,简称PIC)是指代码无论被加载到内存的哪个地址,都能正常运行。这是因为代码里没有使用绝对地址,都是相对地址。在编译链接时,符号地址的确定可以在编译链接时确定,这种技术对应静态链接,而大部分静态语言之所以叫做静态也是因为变量地址在编译时就已经确定了2。当然符号地址也可以在装载(load)时确定,动态链接就用到了这种技术。
例如,我们有一个动态链接库,这个库的代码可以在任意位置执行,这样的代码就是位置无关代码。这种代码放在什么位置都能正常运行,所以地址肯定是动态的不能固定的。这样的代码在数据段开始为每一个全局符号保留了一个条目(GOT global offset table),每一个条目中保存了全局符号的绝对地址。每次对动态链接中全局符号的引用,首先找到GOT中的条目,然后获得全局符号的地址,这样就实现了位置无关代码。
创作不易,点个赞和关注再走吧!!