Loading

GCC编译过程


返回 我的技术栈(Technology Stack)



GCC编译器介绍

编辑器(如vi、记事本)是指我们用它来写程序的(编辑代码)。
而我们写的代码语句,电脑是不懂的,我们需要把它转成电脑能懂的语句,编译器就是这样的转化工具。就是说,我们用编辑器编写程序,由编译器编译后才可以运行!【计算机运行的是机器码,高级语言必须编译成机器码才能运行】
编译器是将易于编写、阅读和维护的高级计算机语言翻译为计算机能解读、运行的低级机器语言的程序。

GCC(GNU Compiler Collection,GNU 编译器套件)是由 GNU 开发的编程语言编译器,GCC 是 GNU(GNU 是 GNU's Not Unix 的缩写)项 目开 发出来的众多有用工具之一 。GCC 原本作为GNU操作系统的官方编译器,现已被大多数类Unix操作系统(如Linux、BSD、Mac OS X等)采纳为标准的编译器,GCC 同样适用于微软的Windows。

GCC 最初用于编译C语言,随着项目的发展GCC已经成为了能够编译C、C++、Java、Ada、fortran、Object C、Object C++、Go语言的编译器大家族。

GNU 环境包括 EMACS 编辑器、GCC编译器、GDB 调试器、汇编器、链接器、处理二进制文件的工具以及其他一些部件。

编译命令格式:
gcc [-option1] ... <filename>
g++ [-option1] ... <filename>

  • 命令、选项和源文件之间使用空格分隔
  • 一行命令中可以有零个、一个或多个选项
  • 文件名可以包含文件的绝对路径,也可以使用相对路径
  • 如果命令中不包含输出可执行文件的文件名,可执行文件的文件名会自动生成一个默认名,Linux平台为a.out,Windows平台为a.exe

GCC的编译器参数(控制编译过程):

  • -E :只对C源程序进行预处理,不编译。即宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
  • -S :只编译到汇编文件。即检查语法,将预处理后文件编译生成汇编文件
  • -c :只编译生成目标文件,不进行链接。即将汇编文件生成目标文件(二进制文件)
  • -o :指定输出的可执行文件。因为C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去,最终生成一个可执行目标文件(或者简称为可执行文件),可以被加载到内存中,由系统执行。
  • -g :生成带有调试信息的debug文件
  • -O2 :代码编译优化等级,一般选择2
  • -W :在编译中开启警告(warming)信息
  • -I :大写的I,在编译时候指定头文件的路径
  • -l : 小写的L,指定程序使用的函数库
  • -L :大写的L,指定函数库的路径

C程序编译步骤

C代码编译成可执行程序经过4步:
1)预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
2)编译:检查语法,将预处理后文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去
执行这四个阶段的程序一起构成了编译系统(compilation system)

预处理

宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法

常见的预处理命令:

  • 头文件包含:#include,通过头文件包含可以实现模块化编程
  • 宏定义:#define,宏定义一个常量,提高程序的可读性
  • 条件编译:#if/#else/#endif,通过条件编译可以让代码兼容不同的处理器架构和平台,以最大限度复用代码
  • 编译控制:#pragma,可以设定编译器的状态,指示编译器完成一些特定的动作,如#pragma once 就表示防止头文件编译多次【这个在预处理阶段后是会保留的,指示编译器干活的】

gcc -E hello.c -o hello.i

进行预处理后,就得到了另一个 Preprocessed C/C++ Source 文件,通常是以 .i 作为文件扩展名。
image

头文件展开
预处理器 (cpp) 根据以字符#开头的命令【所有的#开头的行,都代表预编译指令】,修改原始的 C 程序。
比如 hello.c 中第 1 行的 #include <stdio. h> 命令告诉预处理器读取系统头文件 stdio.h 的内容,并把它直接插入程序文本中,这个过程叫做头文件展开。
image

不建议用 / * 和 * / 来注释一段代码,因为如果代码里面就本就存在由注释,那就会出问题,
更好的方法是使用 #if 指令,这是一个预处理指令

#if 0
	statements
#endif

宏定义展开
就是把代码中使用宏定义的地方都换成具体的数值。
image

不会检查语法
image

编译

检查语法,将预处理后文件编译生成汇编文件

gcc -S hello.i -o hello.s

编译器 (ccl) 将文本文件 hello.i 翻译成文本文件 hello.s, 它包含一个汇编语言程序
进行编译后,就得到了另一个 Assembler Source 文件,通常是以 .s 作为文件扩展名。
image

检查语法
image

编译生成汇编文件
image

汇编

将汇编文件生成目标文件(二进制文件) 【注意是:小c】

gcc -c hello.s -o hello.o

汇编器 (as)hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序 (relocatable object program) 的格式,并将结果保存在目标文件 hello.o 中。 hello.o 文件是一个二进制文件,它包含的 17 个字节是函数 main的指令编码。如果我们在文本编辑器中打开 hello.o 文件,将看到一堆乱码。
进行汇编后,就得到了另一个 O 文件,通常是以 .o 作为文件扩展名。
image

将汇编文件生成目标文件(二进制文件)
image

链接

C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去

gcc hello.o -o hello 或 gcc hello.o -o hello.exe

请注意,hello 程序调用了 printf 函数,它是每个 C 编译器都提供的标准 C 库中的一个函数。printf 函数存在于一个名为 printf.o 的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到我们的 hello.o 程序中 。 链接器 (Id) 就负责处理这种合并。结果就得到 hello.exe 文件,它是一个可执行目标文件(或者简称为可执行文件),可以被加载到内存中,由系统执行。

进行链接后,就得到了另一个 可执行 文件,通常是以 .exe 作为文件扩展名。

由此可以得出整个编译系统,如下图:
image


了解编译系统如何工作是大有益处的

对于像 hello.c 这样简单的程序,我们可以依靠编译系统生成正确有效的机器代码。但是,有一些重要的原因促使程序员必须知道编译系统是如何工作的。

  • 优化程序性能
    现代编译器都是成熟的工具,通常可以生成很好的代码。
    作为程序员,我们无须为了写出高效代码而去了解编译器的内部工作。但是,为了在 C 程序中做出好的编码选择,我们确实需要了解一些机器代码以及编译器将不同的 C 语句转化为机器代码的方式。
    比如,一个 switch 语句是否总是比一系列的 if-else 语句高效得多?一个函数调用的开销有多大? while 循环比 for 循环更有效吗?指针引用比数组索引更有效吗?为什么将循环求和的结果放到一个本地变量中,会比将其放到一个通过引用传递过来的参数中,运行起来快很多呢?为什么我们只是简单地重新排列一下算术表达式中的括号就能让函数运行得更快?

  • 理解链接时出现的错误
    根据我们的经验,一些最令人困扰的程序错误往往都与链接器操作有关,尤其是当你试图构建大型的软件系统时。
    比如,链接器报告说它无法解析一个引用,这是什么意思?静态变量和全局变量的区别是什么?如果你在不同的 C 文件中定义了名字相同的两个全局变量会发生什么?静态库和动态库的区别是什么?我们在命令行上排列库的顺序有什么影响?最严重的是,为什么有些链接错误直到运行时才会出现?

  • 避免安全漏洞
    学习安全编程的第一步就是理解数据和控制信息存储在程序栈上的方式会引起的后果。
    多年来,缓冲区溢出错误是造成大多数网络和 Internet 服务器上安全漏洞的主要原因。存在这些错误是因为很少有程序员能够理解需要限制从不受信任的源接收数据的数量和格式。


参考:
[1]深入理解计算机系统(原书第3版)/(美)兰德尔.E.布莱恩特(Randal E.Bryant)等著;龚奕利,贺莲译.—北京:机械工业出版社,2016.7
[2]嵌入式C语言自我修养:从芯片、编译器到操作系统/王利涛编著.——北京:电子工业出版社,2021.4
[3]C基础讲义2018修订版(黑马程序员)


posted @ 2021-07-08 20:31  言非  阅读(598)  评论(0编辑  收藏  举报