编译的几个阶段和库的制作与使用
编译过程
本文章将讲述对于c/c++,从源代码生成可执行文件的过程。
5个阶段
阶段名称 | 输入 | 输出 | 作用 |
---|---|---|---|
编写代码 | *.h * . cpp *.c * .cc | 编写源文件头文件 | |
预编译 | *.h * . cpp *.c * .cc | *.i | ①展开头文件 在写有#include ②宏替换 ③去掉注释 ④条件编译 即对#ifndef #define #endif进行判断检查,也正是在这一步,#ifndef #define #endif的作用体现出来,即防止头文件被多次重复引用 |
编译 | *.i | *.s | ①编译器在每个文件中保存一个函数地址符表,该表中存储着当前文件内包含的各个函数的地址; ②因为这步要生成汇编代码,即一条一条的指令,而调用函数的代码会被编译成一条call指令,call指令后面跟的是jmp指令的汇编代码地址,而jmp指令后面跟的才是“被调用的函数编译成汇编代码后的第一条指令”的地址,但是给call指令后面补充上地址的工作是在链接的时候做的事情。 |
汇编 | *.s | *.o *.obj | 将汇编代码转成机器码 |
链接 | .o 库文件(*.a *.lib *.so *.dll) |
可执行文件:*.out *.exe | 对于静态库:在链接成可执行程序的时候,需要把目标文件和静态库文件都装进可执行程序中 对于动态库: 其实不在链接的阶段时候,而是在实际运行的时候,程序需要找到动态库,才能使用里面的方法 |
代码举例(没有自制的库)
源代码
main.c
1 |
|
# 预处理
使用gcc 的 -E命令对源文件进行预处理生成.i文件,使用-o指定生成的文件名
gcc -E main.c -o main.i
得到
1 | book@100ask:~/mydir/learn/compile/nolib$ ls |
编译
使用gcc -S命令来对源文件进行编译,得到汇编文件,gcc会补全前面的步骤。
或者对.i 文件进行编译,得到相同的汇编文件,都是一样的。
1 | book@100ask:~/mydir/learn/compile/nolib$ gcc -S main.c -o main.s |
汇编
使用gcc -c命令,对源文件进行汇编,得到二进制文件,在linux和macos等类unxi系统下是.o文件,在windows下是.obj文件
1 | book@100ask:~/mydir/learn/compile/nolib$ gcc -c main.c -o main.o |
链接
使用不带参数的gcc可以进行链接,生成可执行文件
1 | book@100ask:~/mydir/learn/compile/nolib$ gcc main.c -o main.out |
代码举例(自制静态库)
原理说明
将使用到的函数封装到一个静态库文件中,这样用的时候就不用找到那些使用到的方法的实现代码了,只需要找到提供的库文件,这样非常方便,尤其是对于大型的工程。几乎是必须的,从使用库的开发者的角度来看,我为了用别人提供的方法,只需要有两样东西,一个是头文件,一个是库文件,在写代码的时候包含头文件,相当于得到了方法的使用说明啊,然后在链接的时候用到了库文件,组装成了完整的可执行程序。
对于linux来说,静态库必然是一个lib***.a的格式,***代表了这个库的名称,在链接的阶段需要使用到,在编写源代码的时候其实完全不需要用到这个名字的。
制作库文件
编写库的源码
在lib文件夹下创建库文件的源码,如下所示,提供一个打印helloworld的方法
printhelloworld.c
1 |
|
printhelloworld.h
1 |
|
生成目标文件
==需要从库的源码生成目标文件,然后才能通过ar工具打包成静态库文件==
1 | book@100ask:~/mydir/learn/compile/staticlib/lib$ gcc -c *.c -o printhelloworld.o |
生成静态库文件
通过ar工具,将目标文件打包成静态库文件.a文件,注意:==名称必须是lib***.a的格式==,这样才能被gcc的链接命令的-l选项识别,符合linux的规定
1 | book@100ask:~/mydir/learn/compile/staticlib/lib$ ar -crv libprinthelloworld.a *.o |
使用库文件
创建main.c,在里面包含头文件
main.c
1 |
|
生成可执行文件,这一步是在==链接的时候使用到了库文件==
1 | book@100ask:~/mydir/learn/compile/staticlib$ gcc main.c -o main.out -L ./lib -l printhelloworld |
最终的目录和文件结构:
1 | book@100ask:~/mydir/learn/compile/staticlib$ tree |
代码举例(自制动态库)
动态库的代码,不会在链接的阶段被放入可执行程序中,而是在程序运行的时候,会找到动态库,使用到里面的代码。这样就节省了空间,对于升级也很方便,如果是静态库的话,库函数更新了,还需要重新打包链接成可执行程序,但是动态库就只需要更新库文件。
制作库文件
代码也是和静态库一样,只是打包的方式不同,对于动态库,只需要通过gcc就可以完成全部的打包工作,-shared表示创建动态库,-fPIC表示创建于地址无关的编译程序
1 | book@100ask:~/mydir/learn/compile/dynamiclib/lib$ gcc printhelloworld.c -o libprinthelloworld.so -shared -fPIC |
使用库文件
在创建main.out可执行文件时,需要能够找到libprinthelloworld.so这个库文件,因此需要指定路径,使用和静态库相同的语法:-L指定库文件的路径,-l指定库文件的名称。
但是,==在执行的时候,默认情况下linux会去/usr/lib这个路径下,找到动态库,==因此,我们把libprinthelloworld.so复制到这里面,在创建可执行文件的时候,就不需要指定动态库的路径了,因此在/usr/lib这个文件夹下了,只需要指定名称,如下所示
1 | book@100ask:~/mydir/learn/compile/dynamiclib/lib$ sudo cp libprinthelloworld.so /usr/lib |
参考
[]:https://blog.csdn.net/zhangxiao93/article/details/51344625