gcc详细编译过程

1. 程序编译4步骤

1.预处理

2.编译

3.汇编

4.链接

我们经常使用“编译”泛指上面的4个步骤之一,甚至有时候会囊括这四个步骤。

对于嵌入式linux开发来说,用到的编程语言有C,C++,汇编。将一个大型程序(包含有操作系统)编译成可执行程序都会经历这四个步骤:

  • 预处理器将所有的.c 、.cpp文件处理为.i文件
  • 编译器再将所有的.i文件编译为.S文件
  • 汇编器再将所有的.S文件汇编为.o文件
  • 链接器再将所有的.o文件链接为一个可执行文件

注意:.S文件本质上就是二级制文件(机器码),.o文件也是机器码

2. gcc使用示例

2.1 准备工作

2.1.1 git工具

git工具大家自行准备。

2.1.2 Mingw

Mingw相当于是window版本的gcc,大家自行下载,但是不要忘了添加环境变量。

添加完成后可以使用: gcc -v 命令查看是否环境变量中已经存在

如果出现上图所示的信息就说明已经添加成功了。

2.2 gcc使用语法

gcc [选项] 文件名

gcc hello.c                   // 输出一个名为a.out的可执行程序,然后可以执行./a.out
gcc -o hello hello.c          // 输出名为hello的可执行程序,然后可以执行./hello
gcc -o hello hello.c -static  // 静态链接
gcc -c -o hello.o hello.c  // 先编译(不链接)
gcc -o hello hello.o       // 再链接

2.3 gcc常用选项

2.3.1 手工控制编译过程

选项 功能
-v 查看gcc编译器的版本,显示gcc执行时的详细过程
-o 指定输出文件名为file,这个名称不能跟源文件名同名
-E 只预处理,不会编译、汇编、链接t
-S 只编译,不会汇编、链接
-c 编译和汇编,不会链接

一个c/c++文件要经过预处理、编译、汇编和链接才能变成可执行文件。

(1)预处理

C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。

预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。

预处理不处理语法错误,只是会执行预处理指令(包含头文件,宏定义,预处理条件开关)

(2)编译

编译就是把C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码。

编译的时候编译器会检查语法错误。

(3)汇编

汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)。“反汇编”是指将机器代码转换为汇编代码,这在调试程序时常常用到。

(4)链接

链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件

2.3.2 光说不练假把式

hello.c(预处理)->hello.i(编译)->hello.s(汇编)->hello.o(链接)->hello.exe(windows下)
详细的每一步命令如下:

gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o

2.3.2.1 对hello.c文件只进行预处理操作

给出hello.c的源代码,如下:

#include <stdio.h>

#define MAX 20
#define MIN 10
#define _DEBUG
#define SetBit(x)  (1<<x)

int main(int argc , char* argv[])
{

	printf("hello,world\n");
	printf("MAX = %d , MIN = %d , MAX+MIN = %d\n",MAX,MIN,MAX+MIN);
	    
	#ifdef _DEBUG
		printf("SetBit(5) = %d , SetBit(6) = %d\n",SetBit(5),SetBit(6));
		printf("SetBit(SetBit(2)) = %d\n",SetBit(SetBit(2)));
		#endif
   return 0;
}

在git里面用 cat 命令查看一下。

现在我们已经得到hello.c文件了,接下来对该文件只进行预处理命令。

gcc -E -o hello.i hello.c

现在我们来看下hello.i文件的内容。

截取hello.i中内容的一部分,我们发现,宏定义已经被替换了,开关表达语句也消失了,包含头文件命令也被替换了,这就是预编译器将源程序进行了预处理操作。

注意:预编译器是不会对语法进行报错的,仅仅起了一个替换作用。不信,你可以在源文件中随意地修改代码,都不会报错。

2.3.2.2 对hello.i文件只进行编译操作

gcc -S -o hello.s hello.i

现在已经得到了汇编文件。

由于该程序是运行在X86架构上,所以你看到的汇编程序和在ARM架构中的语法,寄存器有很大的不同。如果想了解X86和ARM架构的区别,可以去看下我之前写的一篇文章。

2.3.2.3 对hello.S文件只进行汇编操作

gcc -c -o hello.o hello.s

我们再看下hello.o文件里面的内容

这里面的内容你当然看不懂了,看的懂你就是神人。.o文件里面的内容是机器码,显示出来就会出现稀奇古怪的内容,不用在意。

2.3.2.4 对hello.o文件只进行链接操作

gcc -o hello hello.o

执行完这个命令之后就出现exe文件了。

2.3.2.5 执行hello.exe文件

看看,结果是不是和自己预想的一样。

2.3.3 改善

上面一连串命令比较麻烦,gcc会对.c文件默认进行预处理操作,使用-c再来指明了编译、汇编,从而得到.o文件,
再将.o文件进行链接,得到可执行应用程序。

简化如下:

gcc -c -o hello.o hello.c
gcc -o hello hello.o

注意:工程(包含很多个文件)在处理的过程中都是以源文件为单位。

这种编译方式有个非常好的一点就是,当你有个文件修改了,你只需要对那个文件进行编译即可,而不需要对整个文件进行编译。

3.后缀名决定编译过程

  • 总结
    • 输入文件的后缀名和选项共同决定gcc到底执行那些操作
    • 在编译过程中,最后的步骤都是链接
      • 除非使用了-E、-S、-c选项
      • 或者编译出错阻止了完整的编译过程

3.1 指定头文件目录

头文件在哪里?

  • 系统目录

    • 系统目录在哪?

      工具链里的某个include目录。

      我们下载一个工具链的时候,下载的不仅仅是可执行文件,还有很多的库文件和头文件。

      这就是mingw工具链目录,可以看到在这个目录里面有系统include文件,系统库文件,可执行文件等等。

      当然,我们也可以不使用库文件,编译时指定参数-nostdinc

  • 可以自己指定头文件目录

-I <头文件目录>

3.2 指定库文件

库文件在哪里?

  • 系统目录
    • 系统目录在哪?工具链里的某个lib目录

    • 当然,我们也可以不使用库文件,编译时指定参数-nostdlib

  • 可以自己指定库文件目录
-L <库文件目录>
  • 指定库文件
-l  <abc>   // 链接 libabc.so 或 lib.a

3.3 补充

在Linux中 ,.o是目标文件,相当于windows中的.obj文件 .so 为共享库,是shared object,用于动态连接的,相当于windows下的dll .a为静态库,是好多个.o合在一起,用于静态连接 。


版权声明:本文为qq_46359697原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>