本文通过几个简单的程序实例,学习gcc命令生成静态库.a 和动态库.so的方法和库的使用。以及了解gcc命令背后的编译文章,分析了解学习ELF文件格式。
文章目录
一:预备知识:
(1)gcc命令的使用
语法:
gcc [options] file...
选项:
-c :编译和汇编,但不链接。
-o <file> :指定输出文件。
-S :只编译(不汇编或链接)。
-E :仅执行预处理(不要编译、汇编或链接)。
(2)静态库.a 和动态库.so
函数库,什么是库?库就是一段已经编译好了的代码,在编程的时候加上特定的头文件,就可以使用对应的库函数。
并且使用库,在编译的时候也不会再去编译库,可以节省时间成本。库的分类分为静态库和动态库两种。
静态库:静态链接库( .a)。之所以叫做静态,是因为静态库在编译的时候会被直接拷贝一份,复制到目标程序,
代码在目标程序里就不会再改变。程序运行则不需要静态库的存在。
动态库:动态链接库(.so)。与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向
动态库的引用。等程序运行时,动态库才会被加载进来。
(3)ar 命令的使用
描述:ar命令是建立或修改备存文件,或是从备存文件中抽取文件。ar可以集合许多文件,成为单一的备存文件。
语法:ar [options] [mod] 归档文件 [成员文件…]
option必选选项:
-d 删除备存文件中的成员文件。
-m 变更成员文件在备存文件中的次序。
-p 显示备存文件中的成员文件内容。
-q 将文件附加在备存文件末端。
-r 将文件插入备存文件中。
-t 显示备存文件中所包含的文件。
-x 自备存文件中取出成员文件。
mod可选选项:
a<成员文件> 将文件插入备存文件中指定的成员文件之后。
b<成员文件> 将文件插入备存文件中指定的成员文件之前。
c 建立备存文件。
f 避免过长的文件名不兼容于其他系统的ar指令指令,因此可利用此参数,截掉要放入备存文件中
过长的成员文件名称。
i<成员文件> 将文件插入备存文件中指定的成员文件之前。
o 保留备存文件中文件的日期。
s 若备存文件中包含了对象模式,可利用此参数建立备存文件的符号表。
S 不产生符号表。
u 只将日期较新文件插入备存文件中。
v 程序执行时显示详细的信息。
V 显示版本信息。
(4)ELF文件格式
ELF(Executable and Linkable Format,可执行与可链接格式)文件格式,我们经gcc编译之后生成的可执行文件
属于ELF文件。
文件大致分为下面几个段:
text:已编译程序指令代码
rodata:只读数据
data:初始化C程序的全局变量与静态局部变量
bss:未初始化的C程序的全局变量与静态局部变量
debug:调试符号表
二、hello实例使用库
(1)使用Vim命令编写hello.h,hello.c,main.c文件
(2)
①hello.c
②hello.h
③main.c
(3)<$ gcc -c hello.c> 将hello.c编译成hello.o文件
(4)< $ ar -crv libmyhello.a hello.o>由.o文件创建静态库
(5)< gcc main.c -L. -myhello -o hello>使用静态库
(6)< gcc -c main.c>
< gcc -o hello main.o libmyhello.c > 先生成main.o再使用库生成可执行文件
(7)删除静态库.a文件后执行可执行文件
(8)<gcc -shared -fPIC -o libmyhello.so hello.o>生成动态库文件
shared:表示指定生成动态链接库,不可省略
-fPIC:表示编译为位置独立的代码,不可省略
命令中的-o 不能够被省略
在执行./hello文件时,会报错
解决方式:< sudo mv libmyhello.so /usr/lib>
将libmyhello.so复制到目录/usr/lib中。由于运行时,是在/usr/lib中找库文件的。
sudo:增加权限
三、库使用实例练习
(1)使用vim编写.c文件和.h文件
① sub1.c
②sub2.c
③sub1.h
④main.c
(2)< gcc -c sub1.c sub2.c>将 .c文件,用gcc分别编译为.o 目标文件
(3)< ar cry libsub.a sub1.o sub2.o > 用 ar工具生成1个 .a 静态库文件
(4)< gcc -o main main1.c libsub.a>将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序
(5)< stat 文件名 >记录库文件和可执行程序大小
(6)动态库的生成和使用
< gcc -shared -fPIC -o libsub.so sub1.o sub2.o> :生成动态库文件
< gcc -o main main1.c libsub.so >: 链接动态库生成可执行文件
(7)动态库文件大小
(8)大小比较结论,静态库文件比动态库文件小,生成可执行文件大小差距不大。
四、gcc编译器背后的故事
(1)gcc 编译过程
①:预处理
命令:gcc -E hello.c -o hello.i
过程:1.将所有#define删除,展开宏定义。处理所有条件预编译指令
2.处理所有#inlcude预编译指令
3.删除注释//,/* */
4.添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
5.保留所有的#pragma 编译器指令
编写程序代码
预处理
cat查看预处理后生成的hello.i文件
②:编译
命令: gcc -S hello.i -o hello.s
// 将预处理生成的 hello.i 文件编译生成汇编程序 hello.s
// GCC 的选项-S 使 GCC 在执行完编译后停止,生成汇编程序
cat查看hello.s文件
③:汇编
命令:gcc -c hello.s -o hello.o
// 将编译生成的 hello.s 文件汇编生成目标文件 hello.o
// GCC 的选项-c 使 GCC 在执行完汇编后停止,生成目标文件
④: 链接
前面我们已经联系了静态库和动态库的生成与使用。通过链接最终生成可执行程序。这里不再赘述。
使用动态库链接效果:
(2)ELF文件格式分析
①< readelf -S hello > 查看ELF文件各section的信息
②< objdump -D hello >反汇编方式查看ELF文件指令与数据
格式:PC地址: 指令编码 指令的汇编格式
③<gcc -o hello -g hello.c >
< objdump -S hello >将反汇编与C语言代码混合显示