C语言编译过程



C 是一种编译型语言。与解释型语言相比,编译型语言执行速度更快。可以使用不同的编译器产品来编译 C 程序,例如 GCC、Clang、MSVC 等。本章将解释使用 GCC 编译器编译 C 程序时后台发生的情况。

编译C程序

一系列由 1 和 0 组成的二进制指令被称为机器码。C、C++、Java 等高级编程语言包含更接近人类语言(如英语)的关键字。因此,用 C(或任何其他高级语言)编写的程序需要转换为其等效的机器码。这个过程称为编译

请注意,机器码是特定于硬件架构和操作系统的。换句话说,在 Windows 操作系统的计算机上编译的特定 C 程序的机器码与使用 Linux 操作系统的另一台计算机不兼容。因此,我们必须使用适合目标操作系统的编译器。

Compilation

C语言编译过程步骤

在本教程中,我们将使用 gcc(代表 GNU 编译器集合)。GNU 项目是由 Richard Stallman 发起的自由软件项目,允许开发人员免费访问强大的工具。

gcc 编译器支持多种编程语言,包括 C 语言。为了使用它,我们应该安装与其目标计算机兼容的版本。

编译过程包含四个不同的步骤:

  • 预处理
  • 编译
  • 汇编
  • 链接

下图说明了编译过程。

Compilation Process

示例

为了理解这个过程,让我们考虑以下 C 语言源代码 (main.c):

#include <stdio.h>

int main(){
   
   /* my first program in C */
   
   printf("Hello World! \n");

   return 0;
}

输出

运行代码并检查其输出:

Hello World!

“.c” 是一个文件扩展名,通常表示该文件是用 C 编写的。第一行是预处理指令#include,它告诉编译器包含stdio.h头文件。/**/ 之间的文本是注释,这些注释对于文档目的很有用。

程序的入口点是main() 函数。这意味着程序将首先执行此函数块内的语句。在此给定的程序代码中,只有两条语句:一条语句将在终端打印句子“Hello World”,另一条语句告诉程序如果它正确退出或结束则“返回 0”。因此,一旦我们编译它,如果我们运行此程序,我们将只看到显示的短语“Hello World”。

C编译过程内部发生了什么?

为了使我们的“main.c”代码可执行,我们需要输入命令“gcc main.c”,编译过程将经历其包含的四个步骤。

步骤 1:预处理

预处理器执行以下操作:

  • 它删除源文件中的所有注释。
  • 它包含头文件(.h)的代码,头文件包含 C 函数声明和宏定义。
  • 它用其值替换所有宏(已命名的代码片段)。

此步骤的输出将存储在扩展名为“.i”的文件中,因此这里将存储在“main.i”中。

为了在此步骤之后停止编译,我们可以对源文件使用 gcc 命令的“-E”选项,然后按 Enter。

gcc -E main.c

步骤 2:编译

编译器从预处理文件生成 IR 代码(中间表示),因此这将生成一个“.s”文件。也就是说,其他编译器可能在此编译步骤中生成汇编代码。

我们可以使用 gcc 命令的“-S”选项在该步骤后停止,然后按 Enter。

gcc -S main.c

main.s 文件应如下所示:

.file	"helloworld.c"
   .text
   .def	__main;	.scl	2;	.type	32;	.endef
   .section .rdata,"dr"
.LC0:
   .ascii "Hello, World! \0"
   .text
   .globl	main
   .def	main;	.scl	2;	.type	32;	.endef
   .seh_proc	main
main:
   pushq	%rbp
   .seh_pushreg	%rbp
   movq	%rsp, %rbp
   .seh_setframe	%rbp, 0
   subq	$32, %rsp
   .seh_stackalloc	32
   .seh_endprologue
   call	__main
   leaq	.LC0(%rip), %rcx
   call	puts
   movl	$0, %eax
   addq	$32, %rsp
   popq	%rbp
   ret
   .seh_endproc
   .ident	"GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
   .def	puts;	.scl	2;	.type	32;	.endef

步骤 3:汇编

汇编器获取 IR 代码并将其转换为机器语言代码(即二进制代码)。这将生成一个以“.o”结尾的文件。

我们可以通过使用 gcc 命令的“-c”选项并在之后按 Enter 来在此步骤之后停止编译过程。

请注意,“main.o”文件不是文本文件,因此当您使用文本编辑器打开此文件时,其内容将不可读。

步骤 4:链接

链接器创建最终的可执行二进制文件。它将所有源文件的目标代码链接在一起。链接器知道在哪里查找静态库动态库中的函数定义。

静态库是链接器将所有使用的库函数的副本复制到可执行文件的结果。动态库中的代码不会被完全复制,只有库的名称会被放在二进制文件中。

默认情况下,在此第四个也是最后一步之后,也就是在您键入完整的“gcc main.c”命令没有任何选项时,编译器将创建一个名为main.out(或在 Windows 系统中为main.exe)的可执行程序,我们可以从命令行运行它。

我们还可以通过向 gcc 命令添加“-o”选项来创建我们想要名称的可执行程序,该选项放置在我们正在编译的文件或文件的名称之后。

gcc main.c -o hello.out

因此,现在我们可以键入“./hello.out”(如果您没有使用“-o”选项)或“./hello”来执行编译后的代码。输出将是“Hello World”,之后将再次出现 shell 提示符。

广告