【C语言学习笔记】初识GCC编译器

时间:2022-10-19 02:04:23

初识GCC编译器

在Linux中进行C/C++编程,用到的编译器一般是GCC。

编译过程

C程序的编译过程通常分为了4步,分别为预处理→编译→汇编→链接。与其他的环境比较,在GCC中我们可以更加方便直观的看到编译过程中每一步的结果。
预处理
预处理通常是对程序中的以#开头的语句进行处理,例如文件包含,宏定义等。
预处理主要做了下面几个工作
1. 文件包含语句"#include"
预处理时,文件包含语句将被删除,并将包含文件的内容全部拷贝到该位置。如果被包含的文件中还有文件包含语句,将继续对语句进行拷贝替换,一直递归进行下去,直到文件中不再有包含语句。
2. 宏定义"#define"
预处理时,宏定义语句将被删除,并将所有宏定义展开,所有用到宏的地方全部用目标字符替换。
3. 条件预编译指令"#if","#ifdef","#ifndef","#elif","#else","#endif"
条件预编译指令将按照实际情况,将符合条件部分的语句块保留,不符合条件的语句块删除。并且将条件预编译指令删除。
4. 注释"//""/**/"
所有注释符号及内容全部删除。
5. 添加行号和文件名标示
"#line 1 "d:\\program files (x86)\\microsoft visual studio 12.0\\vc\\include\\stdio.h""。如果打开预处理后的.i文件将发现很多类似于这样的信息,而自己写的代码通常在最后。这些行号和文件名在编译时会被编译器忽略。这些信息的作用是让编译器报错时能够准确找到错误和相关信息的位置。
6. "#pragma"
在所有的#开头的语句中"#pragma"要特殊对待,因为这条语句并不是在预处理时进行处理,而是在编译时处理的语句。因此,预处理时,该语句将会原样保留。

在VS环境下,可以在项目的属性中对预处理进行设置,可以实现自动添加宏定义,不删除注释,不添加行号等特殊要求。

从预处理的工作中不难发现,并不是所有以#开头的语句都是在预处理时处理,编译器也需要一些提示信息,这些信息也是#开头的,如上面提到的"#line ","#pragma"

在gcc中如果需要进行预处理,并得到预处理的结果文件,可以使用命令
gcc -E test.c -o test.i
文件test.i便是预处理后的结果。

编译
编译是将预处理过后的代码进行词法分析的过程。直观来看编译就是将c语言代码翻译成为汇编代码的过程。
C语言的编译过程并不是由C代码直接到机器语言的过程,而是需要汇编代码作为中介,从C代码到汇编代码然后再到机器语言的过程。
通过对编译结果的汇编代码进行分析,我们可以知道程序到底都做了哪些事情,每一个步骤到底是如何实现的。

在gcc中,如果我们想要得到编译的结果,可以使用命令
gcc -S test.i -o test.s
其中
test.i为预处理的结果,
test.s为编译的结果。
另外也可以用下面的命令,直接由C源代码一次性经过预处理和编译得到编译结果
gcc -S test.c -o test.s
其中test.c为程序源代码。

汇编
汇编是将编译得到的汇编代码翻译成机器语言的过程,即得到目标文件。
要得到目标文件,可以使用命令
gcc -c test.s -o test.o
同样也可以直接由C源代码得到汇编结果
gcc -c test.c -o test.o
其中“为目标文件。

链接
目标文件并不是可执行的文件,还需要将目标文件和所需要的附加的目标文件进行链接。
进行链接的命令如下
gcc test.o -o test
同样一步到位的命令如下
gcc test.c -o test
其中test为最终的可执行文件。
只需在相应的目录下执行命令
./test
便可执行程序。

对于多文件的项目,每个文件都对应了一个目标文件。要得到最后的可执行文件,不仅需要附加的目标文件,还需要将每个源文件对应的目标文件进行链接。
链接的命令如下
gcc -c test1.s test2.s ... -o test
或者一步到位
gcc -c test1.c test2.c ... -o test
同样test为最后的可执行文件

扩展名

对于Linux系统来说,文件并不需要扩展名。但是对于gcc来说文件的扩展名是必须的,gcc要通过它来确定文件类型,并进行对应的处理方案。
gcc中常用到的扩展名有

扩展名 类型 处理方式
.c C源文件 预处理、编译、汇编
.C C++源文件 预处理、编译、汇编
.cc C++源文件 预处理、编译、汇编
.cxx C++源文件 预处理、编译、汇编
.m Objective-C源文件 预处理、编译、汇编
.i C源文件的预处理结果 编译、汇编
.ii C++源文件的预处理结果 编译、汇编
.s 汇编语言源文件(编译结果) 汇编
.S 汇编语言源文件(人工编写) 预处理、汇编
.h 头文件 通常不在命令中出现

在gcc的编译过程中,每一步的编译结果都要使用对应的扩展名。对于最后的可执行文件,由于操作可执行文件的并不再是gcc,而是Linux系统进行执行。因此,编译最后得到的可执行文件通常不需要扩展名。
.s.S
在表格中看到.s.S都是汇编语言源文件,那两者有什么区别呢。
首先,在对C语言进行编译的过程中,我们用到的扩展名应该是.s。那么可以知道,编译的结果应该是.s文件。那什么时候才会出现.S文件呢。
表格中看到,对于两种文件的处理方式有所不同。
.s文件只需要进行汇编处理
.S文件则需要先进行预处理,然后再进行汇编处理
为什么要进行预处理呢?
对汇编语言有所了解的应该知道,使用汇编语言编程是可以进行宏定义的,类似于C语言中的#define。要对这样的汇编文件进行汇编,则必须先要对的宏进行处理。因此在汇编之前,需要先进行预处理操作。而在编译得到的汇编文件中则不存在这样的语句,因此不需要预处理命令。
简单的说
.s文件通常是高级语言编译得到的文件;
.S文件通常是人工进行汇编语言编程,即实模式编程时得到的文件。文件在汇编之前需要对文件中的一些语句进行预处理。

通过上面的讨论我们还可以了解到,gcc不但可以对C语言和C++语言进行编译,还可以对汇编语言进行编译。