C 语言编程教程

C 语言首页

C 语言基础

C 语言概述 C 语言特性 C 语言发展历史 C 语言下载安装和环境配置 C 语言程序结构 C 语言 Hello World C 编译过程 C 注释 C 标记 C 关键字 C 标识符 C 用户输入 C 基本语法 C 数据类型 C 变量 C 整数提升 C 类型转换 C 类型转换 C 布尔值

C 语言中的常量和文字

C 常量 C 字面量 C 转义序列 C 格式说明符

C 语言中的运算符

C 运算符 C 算术运算符 C 关系运算符 C 逻辑运算符 C 位运算符 C 赋值运算符 C 一元运算符 C 递增和递减运算符 C 三元运算符 C sizeof 运算符 C 运算符优先级 C 其他运算符

C 语言中的决策

C 决策 C if 语句 C if...else 语句 C 嵌套 if 语句 C switch 语句 C 嵌套 switch 语句

C 语言中的循环

C 循环 C While 循环 C For 循环 C Do...while 循环 C 嵌套循环 C 无限循环 C Break 语句 C Continue 语句 C goto 语句

C 语言中的函数

C 函数 C Main 函数 C 按值调用函数 C 按引用调用函数 C 嵌套函数 C 可变参数函数 C 用户定义函数 C 回调函数 C return 语句 C 递归

C 语言中的作用域规则

C 作用域规则 C 静态变量 C 全局变量

C 语言中的数组

C 数组 C 数组的属性 C 多维数组 C 将数组传递给函数 C 从函数返回数组 C 可变长度数组

C 语言中的指针

C 指针 C 指针和数组 C 指针的应用 C 指针运算 C 指针数组 C 指向指针的指针 C 将指针传递给函数 C 从函数返回指针 C 函数指针 C 指向数组的指针 C 指向结构体的指针 C 指针链 C 指针 vs 数组 C 字符指针和函数 C NULL 指针 C void 指针 C 悬垂指针 C 解引用指针 C Near、Far 和 Huge 指针 C 指针数组的初始化 C 指针与多维数组

C 语言中的字符串

C 字符串 C 字符串数组 C 特殊字符

C 语言的结构体和联合

C 结构体 C 结构体和函数 C 结构体数组 C 自引用结构 C 查找表 C 点 (.) 运算符 C 枚举(或 enum) C 结构填充和打包 C 嵌套结构 C 匿名结构和联合 C 联合 C Bit 位字段 C Typedef

C 语言中的文件处理

C 输入和输出 C 文件 I/O(文件处理)

C 语言中的预处理器

C 预处理器 C #pragma 编译指示 C 预处理器操作符 C 宏 C 头文件

C 语言中的内存管理

C 内存管理 C 内存地址 C 存储类

C 其他主题

C 错误处理 C 可变参数 C 命令执行 C 数学函数 C static 静态关键字 C 随机数生成 C 命令行参数

C 语言编程资源

C 语言问题与解答答案 C 语言快速指南 C 语言速查表 C 语言实用资源 C 语言讨论


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 语言。为了使用它,我们需要安装与目标计算机兼容的版本。

编译过程分为四个不同的步骤 -

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

下图展示了编译过程。

编译过程

示例

为了理解此过程,我们来看一下以下 C 语言源代码 (main.c) -

#include <stdio.h>

int main(){
   
   /* 我的第一个 C 语言程序 */
   
   printf("Hello World! 
");

   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

因此,如果您没有使用"-o""选项,那么现在我们可以输入"./hello.out"或者"./hello" 执行编译后的代码。输出将是"Hello World",随后会再次出现 shell 提示符。