- 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语言 - 主函数
- 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语言 - 指针与数组的区别
- C语言 - 字符指针和函数
- C语言 - 空指针
- C语言 - void 指针
- C语言 - 悬空指针
- C语言 - 解引用指针
- C语言 - 近指针、远指针和巨大指针
- C语言 - 指针数组的初始化
- C语言 - 指针与多维数组的区别
- C语言中的字符串
- C语言 - 字符串
- C语言 - 字符串数组
- C语言 - 特殊字符
- C语言中的结构体和联合体
- C语言 - 结构体
- C语言 - 结构体和函数
- C语言 - 结构体数组
- C语言 - 自引用结构体
- C语言 - 查找表
- C语言 - 点 (.) 运算符
- C语言 - 枚举 (enum)
- C语言 - 结构体填充和打包
- C语言 - 嵌套结构体
- C语言 - 匿名结构体和联合体
- C语言 - 联合体
- C语言 - 位域
- C语言 - typedef
- C语言中的文件处理
- C语言 - 输入与输出
- C语言 - 文件I/O (文件处理)
- C语言预处理器
- C语言 - 预处理器
- 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 操作系统的另一台计算机不兼容。因此,我们必须使用适合目标操作系统的编译器。
C语言编译过程步骤
在本教程中,我们将使用 gcc(代表 GNU 编译器集合)。GNU 项目是由 Richard Stallman 发起的自由软件项目,允许开发人员免费访问强大的工具。
gcc 编译器支持多种编程语言,包括 C 语言。为了使用它,我们应该安装与其目标计算机兼容的版本。
编译过程包含四个不同的步骤:
- 预处理
- 编译
- 汇编
- 链接
下图说明了编译过程。
示例
为了理解这个过程,让我们考虑以下 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 提示符。