编译过程

本文章将讲述对于c/c++,从源代码生成可执行文件的过程。

5个阶段

阶段名称 输入 输出 作用
编写代码 *.h * . cpp *.c * .cc 编写源文件头文件
预编译 *.h * . cpp *.c * .cc *.i ①展开头文件
在写有#include 或#include “filename”的文件中,将文件filename展开, 通俗来说就是将fiename文件中的代码写入到当前文件中;
②宏替换
③去掉注释
④条件编译
即对#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
2
3
4
5
6
#include <stdio.h>
int main()
{
printf("Hello world!\n");
return 0;
}

# 预处理

使用gcc 的 -E命令对源文件进行预处理生成.i文件,使用-o指定生成的文件名

gcc -E main.c -o main.i

得到

1
2
book@100ask:~/mydir/learn/compile/nolib$ ls
main.c main.i

编译

使用gcc -S命令来对源文件进行编译,得到汇编文件,gcc会补全前面的步骤。

或者对.i 文件进行编译,得到相同的汇编文件,都是一样的。

1
2
3
book@100ask:~/mydir/learn/compile/nolib$ gcc -S main.c -o main.s
book@100ask:~/mydir/learn/compile/nolib$ ls
main.c main.i main.s

汇编

使用gcc -c命令,对源文件进行汇编,得到二进制文件,在linux和macos等类unxi系统下是.o文件,在windows下是.obj文件

1
2
3
book@100ask:~/mydir/learn/compile/nolib$ gcc -c main.c -o main.o
book@100ask:~/mydir/learn/compile/nolib$ ls
main.c main.o

链接

使用不带参数的gcc可以进行链接,生成可执行文件

1
2
3
4
5
book@100ask:~/mydir/learn/compile/nolib$ gcc main.c -o main.out
book@100ask:~/mydir/learn/compile/nolib$ ls
main.c main.out
book@100ask:~/mydir/learn/compile/nolib$ ./main.out
Hello world!

代码举例(自制静态库)

原理说明

将使用到的函数封装到一个静态库文件中,这样用的时候就不用找到那些使用到的方法的实现代码了,只需要找到提供的库文件,这样非常方便,尤其是对于大型的工程。几乎是必须的,从使用库的开发者的角度来看,我为了用别人提供的方法,只需要有两样东西,一个是头文件,一个是库文件,在写代码的时候包含头文件,相当于得到了方法的使用说明啊,然后在链接的时候用到了库文件,组装成了完整的可执行程序。

对于linux来说,静态库必然是一个lib***.a的格式,***代表了这个库的名称,在链接的阶段需要使用到,在编写源代码的时候其实完全不需要用到这个名字的。

制作库文件

编写库的源码

在lib文件夹下创建库文件的源码,如下所示,提供一个打印helloworld的方法

printhelloworld.c

1
2
3
4
#include "printhelloworld.h"
void printhelloworld(void){
printf("Hello world!\n");
}

printhelloworld.h

1
2
3
4
5
#ifndef COMPILE_PRINTHELLOWORLD_H
#define COMPILE_PRINTHELLOWORLD_H
#include <stdio.h>
void printhelloworld(void);
#endif

生成目标文件

==需要从库的源码生成目标文件,然后才能通过ar工具打包成静态库文件==

1
2
3
book@100ask:~/mydir/learn/compile/staticlib/lib$ gcc -c *.c -o printhelloworld.o
book@100ask:~/mydir/learn/compile/staticlib/lib$ ls
printhelloworld.c printhelloworld.h printhelloworld.o

生成静态库文件

通过ar工具,将目标文件打包成静态库文件.a文件,注意:==名称必须是lib***.a的格式==,这样才能被gcc的链接命令的-l选项识别,符合linux的规定

1
2
3
4
book@100ask:~/mydir/learn/compile/staticlib/lib$ ar -crv libprinthelloworld.a *.o
a - printhelloworld.o
book@100ask:~/mydir/learn/compile/staticlib/lib$ ls
libprinthelloworld.a printhelloworld.c printhelloworld.h printhelloworld.o

使用库文件

创建main.c,在里面包含头文件

main.c

1
2
3
4
5
6
#include "lib/printhelloworld.h"
int main()
{
printhelloworld();
return 0;
}

生成可执行文件,这一步是在==链接的时候使用到了库文件==

1
2
3
4
5
book@100ask:~/mydir/learn/compile/staticlib$ gcc main.c -o main.out -L ./lib -l printhelloworld
book@100ask:~/mydir/learn/compile/staticlib$ ls
lib main.c main.out
book@100ask:~/mydir/learn/compile/staticlib$ ./main.out
Hello world!

最终的目录和文件结构:

1
2
3
4
5
6
7
8
9
book@100ask:~/mydir/learn/compile/staticlib$ tree
.
├── lib
│   ├── libprinthelloworld.a
│   ├── printhelloworld.c
│   ├── printhelloworld.h
│   └── printhelloworld.o
├── main.c
└── main.out

代码举例(自制动态库)

动态库的代码,不会在链接的阶段被放入可执行程序中,而是在程序运行的时候,会找到动态库,使用到里面的代码。这样就节省了空间,对于升级也很方便,如果是静态库的话,库函数更新了,还需要重新打包链接成可执行程序,但是动态库就只需要更新库文件。

制作库文件

代码也是和静态库一样,只是打包的方式不同,对于动态库,只需要通过gcc就可以完成全部的打包工作,-shared表示创建动态库,-fPIC表示创建于地址无关的编译程序

1
2
3
book@100ask:~/mydir/learn/compile/dynamiclib/lib$ gcc printhelloworld.c -o libprinthelloworld.so -shared -fPIC
book@100ask:~/mydir/learn/compile/dynamiclib/lib$ ls
libprinthelloworld.so printhelloworld.c printhelloworld.h

使用库文件

在创建main.out可执行文件时,需要能够找到libprinthelloworld.so这个库文件,因此需要指定路径,使用和静态库相同的语法:-L指定库文件的路径,-l指定库文件的名称。

但是,==在执行的时候,默认情况下linux会去/usr/lib这个路径下,找到动态库,==因此,我们把libprinthelloworld.so复制到这里面,在创建可执行文件的时候,就不需要指定动态库的路径了,因此在/usr/lib这个文件夹下了,只需要指定名称,如下所示

1
2
3
4
5
6
7
8
9
book@100ask:~/mydir/learn/compile/dynamiclib/lib$ sudo cp libprinthelloworld.so /usr/lib
book@100ask:~/mydir/learn/compile/dynamiclib/lib$ cd ..
book@100ask:~/mydir/learn/compile/dynamiclib$ ls
lib main.c
book@100ask:~/mydir/learn/compile/dynamiclib$ gcc main.c -o main.out -lprinthelloworld
book@100ask:~/mydir/learn/compile/dynamiclib$ ls
lib main.c main.out
book@100ask:~/mydir/learn/compile/dynamiclib$ ./main.out
Hello world!

参考

[]:https://blog.csdn.net/zhangxiao93/article/details/51344625