您的位置:首页 > 其它

静态库和动态库编译方法和开发上的重要性

2016-04-16 10:47 423 查看

静态库和动态库的重要性

在整个产品研发过程中,主要涉及以下几点:

==》需求:立项阶段

==》功能:立项阶段

==》架构:设计阶段

----> 关键技术:技术报告

----> 概要:系统架构

----> 接口:规范,稳定,完成,可扩展,高效率

----> 模块:功能单一,低耦合

----> 组件:松耦合

----> 部署:简单,方便,不影响业务

==》开发&测试:开发阶段

----> 静态库:链接

----> 动态库:部署

----> 模块开发:编码&调试

----> 测试用例:自动化测试

----> 集成联调:技术攻关,内部测试

==》版本发布:发布阶段

----> 测试回归:质量标准,形成alpha,beta,product标尺

随着系统开发的规模化和规范化,从系统架构和资源成本角度考虑,越来越多的代码被集成到模块,这样不仅提高了可维可测性,便于快速集成和部署。

模块和组件,在开发上主要依赖的以下几种方式来对设计进行管控:

==》代码工程结构

==》静态库

==》动态库

==》可执行文件

静态库和动态库编译方法介绍

说了这么多,主要是想说明下静态库和动态库在开发设计上的位置,以及重要性。接下去,就介绍下gcc静态库和动态库编译方法。

示例代码源文件

为了说明这个问题,首先,创建A和B两个库源代码文件:

so_a_test_A.c

#include <stdio.h>

void test_printf_A(void)
{
printf("so_a_test print A!\n");
}

so_a_test_B.c

#include <stdio.h>

void test_printf_B(void)
{
printf("so_a_test print B!\n");
}


然后,创建1和2两个主程序源代码文件:
main_1.c

#include <stdio.h>

extern void test_printf_A(void);

void main(void)
{
printf("hello, world!\n");
test_printf_A();
printf("hello, world done!\n");
}


main_2.c

#include <stdio.h>

extern void test_printf_A(void);

void main(void)
{
printf("hello, world!\n");
test_printf_A();
printf("hello, world done!\n");
}


静态库举例

静态库规划方式

1.代码工程结构

==》so_a_test_A.c

==》so_a_test_B.c

==》main_1.c

==》main_2.c

2.静态库

==》libso_a_test.a

3.可执行文件

==》main_1,main_2

静态库编译执行

# gcc -c so_a_test_A.c
# gcc -c so_a_test_B.c
# ar cr libso_a_test.a so_a_test_A.o so_a_test_B.o
# gcc -o main_1 main_1.c -L. -lso_a_test
# gcc -o main_2 main_2.c -L. -lso_a_test


执行结果

# ./main_1
hello, world!
so_a_test print A!
so_a_test print B!
hello, world done!

# ./main_2
hello, world!
so_a_test print A!
hello, world done!


可执行文件依赖性分析

除了动态链接系统库就没有其他自定义的动态库。

# ldd main_1
linux-vdso.so.1 =>  (0x00007fff65dfe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa6b4148000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa6b451f000)


静态库链接分析

“+000000000040054c T test_printf_A”,so_a_test_B.c的代码没有被集成到main_2可执行文件里面,由于没有被main_2使用,被链接的时候剪裁掉了。

# nm -s main_1 > main_1.txt
# nm -s main_1 > main_1.txt
# diff -uN main_1.txt  main_2.txt
--- main_1.txt        2016-04-15 18:44:42.990572340 -0700
+++ main_2.txt        2016-04-15 18:44:49.726571995 -0700
@@ -9,28 +9,27 @@
0000000000600e28 d _DYNAMIC
0000000000601040 D _edata
0000000000601048 B _end
-00000000004005f4 T _fini
+00000000004005d4 T _fini
0000000000400500 t frame_dummy
0000000000600e10 t __frame_dummy_init_array_entry
-00000000004007c0 r __FRAME_END__
+0000000000400768 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000004003e0 T _init
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
-0000000000400600 R _IO_stdin_used
+00000000004005e0 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
w _Jv_RegisterClasses
-00000000004005f0 T __libc_csu_fini
-0000000000400580 T __libc_csu_init
+00000000004005d0 T __libc_csu_fini
+0000000000400560 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
000000000040052d T main
U puts@@GLIBC_2.2.5
00000000004004a0 t register_tm_clones
0000000000400440 T _start
-0000000000400551 T test_printf_A
-0000000000400561 T test_printf_B
+000000000040054c T test_printf_A
0000000000601040 D __TMC_END__


动态库举例

动态库规划方式

1.代码工程结构

==》so_a_test_A.c

==》so_a_test_B.c

==》main_1.c

==》main_2.c

2.动态库

==》libso_a_test.so

3.可执行文件

==》main_1,main_2

动态库编译执行

# gcc -c -fPIC so_a_test_A.c
# gcc -c -fPIC so_a_test_B.c
# gcc -shared -fPIC -o libso_a_test.so so_a_test_A.o so_a_test_B.o
# gcc -o main_1 main_1.c -L. -lso_a_test
# gcc -o main_2 main_2.c -L. -lso_a_test


执行结果

# export LD_LIBRARY_PATH=./
# ./main_1
hello, world!
so_a_test print A!
so_a_test print B!
hello, world done!
# ./main_2
hello, world!
so_a_test print A!
hello, world done!


动态库链接分析

“- U test_printf_B”,so_a_test_B.c的代码没有被链接到main_2可执行文件里面,由于没有被main_2使用,被链接的时候符号链接剪裁掉了。

# diff -uN main_1.txt  main_2.txt
--- main_1.txt       2016-04-15 18:53:12.266546288 -0700
+++ main_2.txt       2016-04-15 18:53:17.674546011 -0700
@@ -1,36 +1,35 @@
-0000000000601050 B __bss_start
-0000000000601050 b completed.6973
-0000000000601040 D __data_start
-0000000000601040 W data_start
-0000000000400680 t deregister_tm_clones
-00000000004006f0 t __do_global_dtors_aux
+0000000000601048 B __bss_start
+0000000000601048 b completed.6973
+0000000000601038 D __data_start
+0000000000601038 W data_start
+0000000000400630 t deregister_tm_clones
+00000000004006a0 t __do_global_dtors_aux
0000000000600e08 t __do_global_dtors_aux_fini_array_entry
-0000000000601048 D __dso_handle
+0000000000601040 D __dso_handle
0000000000600e18 d _DYNAMIC
-0000000000601050 D _edata
-0000000000601058 B _end
-00000000004007e4 T _fini
-0000000000400710 t frame_dummy
+0000000000601048 D _edata
+0000000000601050 B _end
+0000000000400784 T _fini
+00000000004006c0 t frame_dummy
0000000000600e00 t __frame_dummy_init_array_entry
-0000000000400940 r __FRAME_END__
+00000000004008e0 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
-00000000004005c8 T _init
+0000000000400588 T _init
0000000000600e08 t __init_array_end
0000000000600e00 t __init_array_start
-00000000004007f0 R _IO_stdin_used
+0000000000400790 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000600e10 d __JCR_END__
0000000000600e10 d __JCR_LIST__
w _Jv_RegisterClasses
-00000000004007e0 T __libc_csu_fini
-0000000000400770 T __libc_csu_init
+0000000000400780 T __libc_csu_fini
+0000000000400710 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
-000000000040073d T main
+00000000004006ed T main
U puts@@GLIBC_2.2.5
-00000000004006b0 t register_tm_clones
-0000000000400650 T _start
+0000000000400660 t register_tm_clones
+0000000000400600 T _start
U test_printf_A
-                 U test_printf_B
-0000000000601050 D __TMC_END__
+0000000000601048 D __TMC_END__


可执行文件依赖性分析

除了动态链接系统库,就链接了自己开发的libso_a_test.so文件。

# ldd main_1
linux-vdso.so.1 =>  (0x00007fffecad7000)
libso_a_test.so => ./libso_a_test.so (0x00007f6dccbc2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6dcc7ed000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6dccdc6000)


进一步分析libso_a_test.so文件,里面含有test_printf_A和test_printf_B代码段。

# nm -s libso_a_test.so
0000000000201038 B __bss_start
0000000000201038 b completed.6973
w __cxa_finalize@@GLIBC_2.2.5
0000000000000610 t deregister_tm_clones
0000000000000680 t __do_global_dtors_aux
0000000000200e08 t __do_global_dtors_aux_fini_array_entry
0000000000201030 d __dso_handle
0000000000200e18 d _DYNAMIC
0000000000201038 D _edata
0000000000201040 B _end
000000000000071c T _fini
00000000000006c0 t frame_dummy
0000000000200e00 t __frame_dummy_init_array_entry
00000000000007f0 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000000005a8 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000200e10 d __JCR_END__
0000000000200e10 d __JCR_LIST__
w _Jv_RegisterClasses
U puts@@GLIBC_2.2.5
0000000000000640 t register_tm_clones
00000000000006f5 T test_printf_A
0000000000000707 T test_printf_B
0000000000201038 d __TMC_END__


静态库和动态库规划对比点

可执行文件剪裁对比

==》静态链接采用的是"T",代码段嵌入

==》动态链接采用的是"U",符号嵌入

==》静态链接可执行文件由于代码段植入,比动态链接可执行文件要大

==》main_1和main_2可执行文件从剪裁角度来说,都剪裁了“test_printf_B”,只不过一个剪裁的是代码,一个是符号

==》静态链接可执行文件依赖系统so库,而动态链接可执行文件依赖libso_a_test.so

静态、动态库文件对比

$ nm -s libso_a_test.a

Archive index:
test_printf_A in so_a_test_A.o
test_printf_B in so_a_test_B.o

so_a_test_A.o:
U puts
0000000000000000 T test_printf_A

so_a_test_B.o:
U puts
0000000000000000 T test_printf_B


$ nm -s libso_a_test.so
0000000000201038 B __bss_start
0000000000201038 b completed.6973
w __cxa_finalize@@GLIBC_2.2.5
0000000000000610 t deregister_tm_clones
0000000000000680 t __do_global_dtors_aux
0000000000200e08 t __do_global_dtors_aux_fini_array_entry
0000000000201030 d __dso_handle
0000000000200e18 d _DYNAMIC
0000000000201038 D _edata
0000000000201040 B _end
000000000000071c T _fini
00000000000006c0 t frame_dummy
0000000000200e00 t __frame_dummy_init_array_entry
00000000000007f0 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000000005a8 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000200e10 d __JCR_END__
0000000000200e10 d __JCR_LIST__
w _Jv_RegisterClasses
U puts@@GLIBC_2.2.5
0000000000000640 t register_tm_clones
00000000000006f5 T test_printf_A
0000000000000707 T test_printf_B
0000000000201038 d __TMC_END__


$ ldd libso_a_test.so
linux-vdso.so.1 =>  (0x00007fffb13fe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa85b418000)
/lib64/ld-linux-x86-64.so.2 (0x00007fa85b9f1000)


$ ldd libso_a_test.a
not a dynamic executable


==》静态库给出了函数工程代码源文件出处,动态库没有

==》动态库有系统支持代码,静态库没有

==》动态库有系统支持代码,所以文件较静态库大

==》动态库有默认的系统初始化代码和去初始化代码,可以引入组件支持和对象模型机制

代码工程结构对静态库的剪裁影响

将so_a_test_A.c和so_a_test_B.c代码整合到一个文件so_a_test.c

#include <stdio.h>

void test_printf_A(void)
{
printf("so_a_test print A!\n");
}

void test_printf_B(void)
{
printf("so_a_test print B!\n");
}


编译,执行,分析,可以看出,工程整合后,静态链接的剪裁效果消失了,不管main_1,main_2是否都用到该函数,代码段都将被链接。这也是为什么需要模块化切分,可以减少可执行文件的大小。

# gcc -c so_a_test.c
# ar cr libso_a_test.a so_a_test.o
# nm -s libso_a_test.a

Archive index:
test_printf_A in so_a_test.o
test_printf_B in so_a_test.o

so_a_test.o:
U puts
0000000000000000 T test_printf_A
0000000000000010 T test_printf_B

# gcc -o main_1 main_1.c -L. -lso_a_test
# gcc -o main_2 main_2.c -L. -lso_a_test

# ./main_1
hello, world!
so_a_test print A!
so_a_test print B!
hello, world done!
# ./main_2
hello, world!
so_a_test print A!
hello, world done!

# nm -s main_1 > main_1.txt
# nm -s main_2 > main_2.txt
# diff -uN main_1.txt main_2.txt
--- main_1.txt        2016-04-15 19:37:50.206409296 -0700
+++ main_2.txt        2016-04-15 19:37:54.834409059 -0700
@@ -9,28 +9,28 @@
0000000000600e28 d _DYNAMIC
0000000000601040 D _edata
0000000000601048 B _end
-00000000004005f4 T _fini
+00000000004005e4 T _fini
0000000000400500 t frame_dummy
0000000000600e10 t __frame_dummy_init_array_entry
-00000000004007c0 r __FRAME_END__
+00000000004007b0 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000004003e0 T _init
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
-0000000000400600 R _IO_stdin_used
+00000000004005f0 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
w _Jv_RegisterClasses
-00000000004005f0 T __libc_csu_fini
-0000000000400580 T __libc_csu_init
+00000000004005e0 T __libc_csu_fini
+0000000000400570 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
000000000040052d T main
U puts@@GLIBC_2.2.5
00000000004004a0 t register_tm_clones
0000000000400440 T _start
-0000000000400551 T test_printf_A
-0000000000400561 T test_printf_B
+000000000040054c T test_printf_A
+000000000040055c T test_printf_B
0000000000601040 D __TMC_END__


补充知识点:nm常见的符号类型

A 该符号的值在今后的链接中将不再改变;

B 该符号放在BSS段中,通常是那些未初始化的全局变量;

D 该符号放在普通的数据段中,通常是那些已经初始化的全局变量;

T 该符号放在代码段中,通常是那些全局非静态函数;

U 该符号未定义过,需要自其他对象文件中链接进来;

W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。

参考资料

【1】gcc使用指南
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: