利用DeepSeek辅助试验C动态链接静态链接

前面在编译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):不依赖外部库,是独立的可执行文件

文件特点

  1. 动态库版本

    • 文件较小
    • 运行时需要 all.so 在库路径中
    • 可以动态更新库而不重新编译主程序
  2. 静态库版本

    • 文件较大(包含了所有需要的代码)
    • 可以独立运行
    • 链接时只包含实际使用的代码(arr_mul 不会被包含)

验证静态链接的优化

使用 nm 命令检查可执行文件中的符号:

nm test_static | grep -E "(add|mul|arr_)"

会发现只有 arr_add 和它依赖的 add 函数被链接,而 arr_mulmul 没有被包含。

这个实验很好地展示了动态库和静态库在依赖管理、文件大小和部署方式上的区别。

实际执行结果如下:因为库文件名没有遵守规则,前面未加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的情况下,动态链接可执行文件无法执行,而部分静态链接可执行文件可以执行,当明确知道系统库文件存在时,可用此法减少可执行文件大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值