前面在编译DuckDB插件过程中,遇到链接DuckDB动态库和静态库的问题。
为了试验c动态库和静态库的性质,假设有一个程序需要计算整数数组的元素连加和连乘,把底层功能用不同的c文件编译成.o文件,分别是add.o mul.o arr_add.o arr_mul.o,把所有功能编成一个all.so文件,然后把所有连乘和连加打包成一个arr.a文件。写一个test.c,只用到arr_add功能,链接时采用2个版本,动态链接all.so的版本,和静态链接arr.a的版本,查看它们各自依赖的文件。编写所有源代码和编译命令、执行命令。
他的回答如下
我来为您创建一个完整的实验项目,演示动态库和静态库的性质。
项目结构
.
├── add.c # 加法函数
├── mul.c # 乘法函数
├── arr_add.c # 数组求和
├── arr_mul.c # 数组求积
├── test.c # 测试程序
└── Makefile # 编译脚本
源代码文件
add.c
int add(int a, int b) {
return a + b;
}
mul.c
int mul(int a, int b) {
return a * b;
}
arr_add.c
extern int add(int, int);
int arr_add(int *array, int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum = add(sum, array[i]);
}
return sum;
}
arr_mul.c
extern int mul(int, int);
int arr_mul(int *array, int size) {
int product = 1;
for (int i = 0; i < size; i++) {
product = mul(product, array[i]);
}
return product;
}
test.c
#include <stdio.h>
extern int arr_add(int *array, int size);
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
int sum = arr_add(arr, size);
printf("数组元素和: %d\n", sum);
return 0;
}
编译命令和执行命令
Makefile
CC=gcc
CFLAGS=-Wall -fPIC
TARGET=test
TEST_ARR=test_arr
# 编译所有目标文件
add.o: add.c
$(CC) $(CFLAGS) -c add.c
mul.o: mul.c
$(CC) $(CFLAGS) -c mul.c
arr_add.o: arr_add.c
$(CC) $(CFLAGS) -c arr_add.c
arr_mul.o: arr_mul.c
$(CC) $(CFLAGS) -c arr_mul.c
# 创建动态库 liball.so
all.so: add.o mul.o arr_add.o arr_mul.o
$(CC) -shared -o liball.so add.o mul.o arr_add.o arr_mul.o
# 创建静态库 libarr.a
arr.a: arr_add.o arr_mul.o
ar rcs libarr.a arr_add.o arr_mul.o
# 动态链接版本
test_dynamic: test.c all.so
$(CC) -o $(TARGET)_dynamic test.c -L. -lall
@echo "=== 动态链接版本依赖 ==="
ldd $(TARGET)_dynamic || true
# 静态链接版本
test_static: test.c arr.a
$(CC) -o $(TARGET)_static test.c -L. -larr -static
@echo "=== 静态链接版本依赖 ==="
ldd $(TARGET)_static 2>/dev/null || echo "静态链接版本没有动态库依赖"
# 测试数组功能的版本(包含乘法和加法)
test_arr: test_arr.c arr.a
$(CC) -o $(TEST_ARR) test_arr.c -L. -larr -static
# 清理
clean:
rm -f *.o *.so *.a test_dynamic test_static $(TEST_ARR)
# 全部构建
all: test_dynamic test_static
.PHONY: all clean
额外的测试文件 test_arr.c(用于测试完整功能)
#include <stdio.h>
extern int arr_add(int *array, int size);
extern int arr_mul(int *array, int size);
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
int sum = arr_add(arr, size);
int product = arr_mul(arr, size);
printf("数组元素和: %d\n", sum);
printf("数组元素积: %d\n", product);
return 0;
}
执行步骤
1. 编译所有文件
make all
2. 查看文件依赖关系
# 查看动态链接版本
echo "=== 动态链接版本 ==="
file test_dynamic
ldd test_dynamic
echo -e "\n=== 静态链接版本 ==="
file test_static
ldd test_static 2>/dev/null || echo "静态版本没有动态库依赖"
echo -e "\n=== 查看静态库内容 ==="
nm libarr.a
echo -e "\n=== 查看动态库内容 ==="
nm liball.so
3. 运行程序
# 运行动态链接版本
echo "运行动态链接版本:"
LD_LIBRARY_PATH=. ./test_dynamic
echo -e "\n运行静态链接版本:"
./test_static
4. 查看文件大小对比
echo "=== 文件大小对比 ==="
ls -lh test_dynamic test_static *.so *.a
预期结果分析
依赖关系
- 动态链接版本 (
test_dynamic):依赖all.so,使用ldd命令可以看到依赖关系 - 静态链接版本 (
test_static):不依赖外部库,是独立的可执行文件
文件特点
-
动态库版本:
- 文件较小
- 运行时需要
all.so在库路径中 - 可以动态更新库而不重新编译主程序
-
静态库版本:
- 文件较大(包含了所有需要的代码)
- 可以独立运行
- 链接时只包含实际使用的代码(
arr_mul不会被包含)
验证静态链接的优化
使用 nm 命令检查可执行文件中的符号:
nm test_static | grep -E "(add|mul|arr_)"
会发现只有 arr_add 和它依赖的 add 函数被链接,而 arr_mul 和 mul 没有被包含。
这个实验很好地展示了动态库和静态库在依赖管理、文件大小和部署方式上的区别。
实际执行结果如下:因为库文件名没有遵守规则,前面未加lib前缀,所以第一次链接不成功,在makefile中改正后可以了(上面代码已改), 还要注意脚本文件采用Unix换行符LF,而不是CR+LF。
root@6ae32a5ffcde:/par/test_ld# make clean
rm -f *.o *.so *.a test_dynamic test_static test_arr
root@6ae32a5ffcde:/par/test_ld# make all
gcc -Wall -fPIC -c add.c
gcc -Wall -fPIC -c mul.c
gcc -Wall -fPIC -c arr_add.c
gcc -Wall -fPIC -c arr_mul.c
gcc -shared -o liball.so add.o mul.o arr_add.o arr_mul.o
gcc -o test_dynamic test.c -L. -lall
=== 动态链接版本依赖 ===
ldd test_dynamic || true
linux-vdso.so.1 (0x00007ffd45b6b000)
liball.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcd1a9c0000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcd1abaf000)
ar rcs libarr.a arr_add.o arr_mul.o
gcc -o test_static test.c -L. -larr -static
/usr/bin/ld: ./libarr.a(arr_add.o): in function `arr_add':
arr_add.c:(.text+0x3d): undefined reference to `add'
collect2: error: ld returned 1 exit status
make: *** [makefile:35: test_static] Error 1
静态链接版本链接报错,因为makefile未链接add函数所在的add.o文件,手工添加就链接成功了。
root@6ae32a5ffcde:/par/test_ld# gcc -o test_static test.c -L. -larr -static add.o
root@6ae32a5ffcde:/par/test_ld# bash check.sh
=== 动态链接版本 ===
test_dynamic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
linux-vdso.so.1 (0x00007fff8acd3000)
liball.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9014748000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9014937000)
=== 静态链接版本 ===
test_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, with debug_info, not stripped
静态版本没有动态库依赖
=== 查看静态库内容 ===
arr_add.o:
U add
0000000000000000 T arr_add
arr_mul.o:
0000000000000000 T arr_mul
U mul
=== 查看动态库内容 ===
0000000000003df8 d _DYNAMIC
0000000000003fe8 d _GLOBAL_OFFSET_TABLE_
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000002118 r __FRAME_END__
0000000000002000 r __GNU_EH_FRAME_HDR
0000000000004018 d __TMC_END__
w __cxa_finalize@GLIBC_2.2.5
00000000000010d0 t __do_global_dtors_aux
0000000000003df0 d __do_global_dtors_aux_fini_array_entry
0000000000004010 d __dso_handle
0000000000003de8 d __frame_dummy_init_array_entry
w __gmon_start__
00000000000011ec t _fini
0000000000001000 t _init
0000000000001119 T add
0000000000001140 T arr_add
0000000000001195 T arr_mul
0000000000004018 b completed.0
0000000000001060 t deregister_tm_clones
0000000000001110 t frame_dummy
000000000000112d T mul
0000000000001090 t register_tm_clones
=== 文件大小对比 ===
-rwxrwxrwx 1 root root 16K Oct 1 23:11 liball.so
-rwxrwxrwx 1 root root 2.8K Oct 1 23:11 libarr.a
-rwxrwxrwx 1 root root 16K Oct 1 23:11 test_dynamic
-rwxrwxrwx 1 root root 943K Oct 1 23:17 test_static
root@6ae32a5ffcde:/par/test_ld# ldd test_static
not a dynamic executable
root@6ae32a5ffcde:/par/test_ld# bash run.sh
运行动态链接版本:
数组元素和: 15
运行静态链接版本:
数组元素和: 15
以上静态链接不但链接了自定义函数,还链接了库文件中的函数,可称为完全静态链接,最终的文件大小较大,而且用nm。还可以部分静态链接,只链接了自定义函数,试验如下:
root@6ae32a5ffcde:/par/test_ld# gcc -o test_static2 test.c -L. libarr.a add.o
root@6ae32a5ffcde:/par/test_ld# echo "=== 文件大小对比 ==="
ls -lh test_dynamic test_static* *.so *.a
=== 文件大小对比 ===
-rwxrwxrwx 1 root root 16K Oct 1 23:11 liball.so
-rwxrwxrwx 1 root root 2.8K Oct 1 23:11 libarr.a
-rwxrwxrwx 1 root root 16K Oct 1 23:11 test_dynamic
-rwxrwxrwx 1 root root 943K Oct 1 23:17 test_static
-rwxrwxrwx 1 root root 16K Oct 1 23:41 test_static2
root@6ae32a5ffcde:/par/test_ld# file test_static2
test_static2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
root@6ae32a5ffcde:/par/test_ld# nm test_static2
0000000000403df8 d _DYNAMIC
0000000000403fe8 d _GLOBAL_OFFSET_TABLE_
0000000000402000 R _IO_stdin_used
0000000000402138 r __FRAME_END__
000000000040201c r __GNU_EH_FRAME_HDR
0000000000404018 D __TMC_END__
0000000000400358 r __abi_tag
0000000000404018 B __bss_start
0000000000404008 D __data_start
00000000004010f0 t __do_global_dtors_aux
0000000000403df0 d __do_global_dtors_aux_fini_array_entry
0000000000404010 D __dso_handle
0000000000403de8 d __frame_dummy_init_array_entry
w __gmon_start__
U __libc_start_main@GLIBC_2.34
0000000000401070 T _dl_relocate_static_pie
0000000000404018 D _edata
0000000000404020 B _end
00000000004011f0 T _fini
0000000000401000 T _init
0000000000401040 T _start
00000000004011dc T add
0000000000401187 T arr_add
0000000000404018 b completed.0
0000000000404008 W data_start
0000000000401080 t deregister_tm_clones
0000000000401120 t frame_dummy
0000000000401126 T main
U printf@GLIBC_2.2.5
00000000004010b0 t register_tm_clones
root@6ae32a5ffcde:/par/test_ld# ./test_dynamic
./test_dynamic: error while loading shared libraries: liball.so: cannot open shared object file: No such file or directory
root@6ae32a5ffcde:/par/test_ld# ./test_static2
数组元素和: 15
root@6ae32a5ffcde:/par/test_ld# nm test_static2 | grep -E "(add|mul|arr_)"
00000000004011dc T add
0000000000401187 T arr_add
可见,在不指定LD_LIBRARY_PATH的情况下,动态链接可执行文件无法执行,而部分静态链接可执行文件可以执行,当明确知道系统库文件存在时,可用此法减少可执行文件大小。
5736

被折叠的 条评论
为什么被折叠?



