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 提示符。