Linux编译过程以及生成中间文件--链接
1、编译系统
C源文件到可执行文件共经历了4个过程。在使用GCC编译程序时,编译过程可以被细分为四个阶段,包括预处理、编译、汇编、链接
1、预处理
在预处理阶段,编译器主要作加载头文件、宏替换、条件编译的作用。一般处理带“#”的语句。
我们可以通过gcc 的 -E 选项进行查看,如下所示:
gcc -E main.c > main.i
编译器将main.c预处理结果输出 main.i 文件。
2、编译
在编译过程中,编译器主要作语法检查和词法分析。在确认所有指令都符合语法规则之后,将其翻译成等价的中间代码或者是汇编代码。
gcc -S main.i -o main.s
编译器将预处理结果文件main.i翻译成汇编代码main.s
3、汇编
汇编阶段是把编译阶段生成的”.s”文件转成二进制目标代码。
gcc -c main.s -o main.o
编译器将main.s文件转化为main.o 文件。
4、链接
在成功编译之后,就进入了链接阶段。链接就是将目标文件、启动代码、库文件链接成可执行文件的过程,这个文件可被加载或拷贝到存储器执行。
gcc main.o -o main.exe
编译器将main.o链接成最终可执行文件main.exe
gcc -c与gcc -o以及不加参数的区别:
-c 编译和汇编,但不要链接。
-o <file> 将输出放入<文件>。
'无参数' 表示恢复为基于文件扩展名猜测语言的默认行为
1、通过gcc 不加参数可以一步直接编译生成可执行文件
gcc main.c
这里生成的是可执行文件a.out,当然可以通过-o选项更改生成文件的名字,比如将生成的可执行文件命名为hello.exe
gcc main.c -o main.exe //当然也可以通过-o选项更改生成的执行文件的名字
2、gcc -c 编译生成main.o
gcc -c main.c #生成main.o
gcc main.o #不加参数,gcc自动链接上一步生成的main.o来生成最终可执行文件a.out
2、静态链接
静态连接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
-
符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
-
重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
3、目标文件
-
可执行目标文件:可以直接在内存中执行;
-
可重定向目标文件:可与其它可重定向目标文件在链接阶段合并,创建一个可执行目标文件;
-
共享目标文件:这是一种特殊的可重定向目标文件,可以在运行时被动态加载进内存并链接;
4、动态链接
静态库有以下两个问题:
-
当静态库更新时那么整个程序都要重新进行链接;
-
对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。
共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具有以下特点:
-
在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
-
在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。
5、静态库编译与使用
库是预编译的目标文件(.o)的集合,它们可以被链接进程序。
标准系统库可在目录/usr/lib与/lib中找到。比如,在类Unix系统中C语言的数序库一般存储为文件/usr/lib/libm.a。该库中函数的原型声明在头文件/usr/include/math.h中。
C标准库本身存储为/usr/lib/libc.a,它包含ANS1/ISO标准指定的函数,比如printf。对每一个C程序来说,libc.a都默认被链接。
假设我们自己编译一个加法程序,和打印程序。
//mylib.h
int myadd(int a, int b);
void myprint( int s);
//mylib.cpp #include <iostream> #include "mylib.h" int myadd(int a, int b) { return a + b; } void myprint( int s) { std::cout << s << std::endl; }
以通过下面两个命令将这两个函数编译成静态库。
gcc -c mylib.cpp -o mylib.o //产生目标文件
ar rcv libmylib.a mylib.o //将目标文件打包成静态库
使用C++编译的静态库
//main.cpp
#include <iostream>
#include "mylib.h"
int main()
{
myprint(myadd(1,2));
return 0;
}
通过下面的命令(任选一种)将main.cpp与静态库进行编译。
g++ -o main main.cpp -L. -lmylib
g++ -o main main.cpp libmylib.a