C 语言初识:程序运行机制与基本结构
- 1C 语言发展史
- 2C 语言开发环境的安装与配置
- 3C 语言初识:程序运行机制与基本结构本文
- 4C 语言基础:注释、关键字、变量与数据类型
【一】C 程序的运行机制
从源代码到程序运行,通常会经历编写、预处理、编译、汇编、链接和运行等阶段。开发者平时执行一条编译命令时,这些阶段大多由编译器工具链自动完成,因此中间文件不一定会被保留下来。

图中展示的是 Windows 下的 MSVC 工具链:
cl.exe负责处理和编译源文件,link.exe负责链接,目标文件扩展名为.obj。本文安装的是 MinGW-w64 GCC 工具链,目标文件通常使用.o扩展名。二者生成的文件名称和使用的命令有所不同,但整体处理过程基本一致。
1. 编写代码:使用编辑器编写 C 源代码,并保存为 xx.c 文件。
2. 预处理:处理 #include、#define、条件编译等预处理指令,并处理源代码中的注释,通常得到 xx.i 文件。
3. 编译:对预处理后的代码进行词法分析、语法分析、语义分析和优化,再转换为汇编代码,通常得到 xx.s 文件。
4. 汇编:汇编器将汇编代码转换成机器指令,生成可重定位目标文件;GCC 通常生成 xx.o,MSVC 通常生成 xx.obj。
5. 链接:链接器把一个或多个目标文件、启动代码和所需的库组合起来,解析函数与变量的引用,最终生成可执行文件。Windows 下通常为 xx.exe。
6. 运行:操作系统加载可执行文件及其依赖的动态库,完成运行环境初始化后开始执行程序,并得到运行结果。这里需要区分“编译”的两种常见含义:
- 广义的编译:泛指从源文件生成可执行文件的整个构建过程,包括预处理、狭义编译、汇编和链接。
- 狭义的编译:特指把预处理后的 C 代码转换为汇编代码的阶段。
目标文件虽然已经包含机器指令,但通常还存在尚未确定地址的符号引用,因此不能直接运行。例如,程序调用 printf 时,目标文件只记录了对该函数的引用;链接器还需要从相应的库中找到它,并建立正确的关联。
使用 MinGW-w64 GCC 时,可以通过下面的命令分别观察各个阶段:
# 1. 预处理:hello.c -> hello.igcc -E hello.c -o hello.i
# 2. 编译:hello.i -> hello.sgcc -S hello.i -o hello.s
# 3. 汇编:hello.s -> hello.ogcc -c hello.s -o hello.o
# 4. 链接:hello.o -> hello.exegcc hello.o -o hello.exe
# 5. 运行(PowerShell).\hello.exe实际开发时通常不需要手动执行每个阶段。一条命令即可让 GCC 依次完成预处理、编译、汇编和链接:
gcc hello.c -o hello.exe.\hello.exe
gcc不只是狭义上的编译器命令,它还充当工具链的驱动程序,会根据参数自动调用预处理器、编译器、汇编器和链接器。
【二】C 基础语法
C 语言是一种通用编程语言,广泛应用于系统编程、嵌入式开发和高性能计算等领域。
C 语言具有高效、灵活、可移植性强等特点,是许多其他编程语言(如 C++、Java、Go)的基础,至今仍是操作系统、驱动程序和底层库开发的主流选择。
在 C 语言中,令牌(Token)是程序的基本组成单位。编译器通过词法分析,将源代码识别为一个个具有特定含义的令牌。了解令牌的类别,有助于看懂程序的语法结构。
C 语言的令牌主要包括以下几种类型:
- 关键字(Keywords):语言预留的具有特殊含义的单词,如
int、if、return。 - 标识符(Identifiers):程序员自定义的名称,用于变量、函数、数组等。
- 常量(Constants):程序中固定不变的值,如整数
42、浮点数3.14。 - 字符串字面量(String Literals):由双引号括起来的字符序列,如
"Hello"。 - 运算符(Operators):用于执行运算的符号,如
+、==、&&。 - 分隔符(Separators):用于分隔语句和代码块的符号,如
;、{}。
阅读代码时,可以先找出标识符、字面量和运算符,再观察分号、括号与花括号如何组织语句。C 语言区分大小写,例如 count 和 Count 是两个不同的标识符。
【三】C 程序的基本结构
【1】简单的 C 语言程序
先从一个输出文字的 C 程序开始,观察头文件、main 函数与 return 语句的位置:
#include <stdio.h>
int main() { printf("Hello, World!\n"); return 0;}上面的代码可以从以下几个组成部分理解;变量声明和其他函数定义则会出现在后续示例中:
- 预处理器指令:如
#include和#define,在编译前由预处理器处理。 - 主函数:每个 C 程序都有且只有一个
main()函数,是程序的执行入口。 - 变量声明:声明程序中使用的变量,C89 要求声明必须在代码块最开头。
- 函数定义:定义程序中使用的函数,描述函数的具体实现逻辑。
【2】复杂一点的 C 程序结构
再看一个稍完整的 C 程序:它从标准输入读取两个整数,调用 add 函数计算结果,并输出求和结果。下面先认识整体结构,各知识点会在后续章节展开说明。
#include <stdio.h> // 头文件包含
#define PI 3.14159 // 宏定义
// 函数声明int add(int a, int b);
int main() { // 主函数 // 变量声明 int num1, num2, sum;
// 用户输入 printf("Enter two integers: "); if (scanf("%d %d", &num1, &num2) != 2) { printf("Invalid input.\n"); return 1; }
// 函数调用 sum = add(num1, num2);
// 输出结果 printf("Sum: %d\n", sum);
return 0; // 返回 0 表示程序成功执行}
// 函数定义int add(int a, int b) { return a + b;}scanf 会返回成功读取并转换的数据项数量。本例需要读取两个整数,因此返回值应为 2;如果返回值不等于 2,程序会提示输入无效并提前结束,从而避免继续使用未正确赋值的变量。
头文件包含
- 头文件通常在程序开头通过
#include指令引入。例如,<stdio.h>声明了标准输入输出函数,<stdlib.h>提供了常用的通用工具声明。包含相应头文件后,编译器才能获得所用函数和宏的相关信息。 - 标准库头文件通常使用尖括号
< >,项目自定义头文件通常使用双引号" ";两种写法的头文件搜索方式不同。 - 示例:
#include <stdio.h>(标准库头文件)、#include "myheader.h"(自定义头文件)。
宏定义
- 宏通过
#define指令定义,可表示符号常量或代码片段。预处理器会在编译前展开宏;宏常用于定义常量或简短的可复用表达式。 - 宏本身不进行类型检查,使用时需谨慎;需要类型约束时,可以考虑使用
const变量。 - 示例:
#define PI 3.14159、#define MAX(a, b) ((a) > (b) ? (a) : (b))
函数声明
- 函数声明(也称函数原型)提供返回类型、函数名和参数信息,使编译器能在调用处检查参数与返回类型。本例在调用
add之前先写出声明,再在main之后给出定义。 - 多个源文件需要调用同一个函数时,可以将其声明放在头文件中共享。
- 示例:
int add(int a, int b);
主函数
main()函数是 C 程序的入口点,每个 C 程序都必须包含一个main()函数。程序从main()开始执行,返回值通常为0表示程序成功执行,非零值表示出现了错误。main()函数也可以接收命令行参数:int main(int argc, char *argv[]),其中argc是参数个数,argv是参数字符串数组。- 示例:
int main() { ... }
变量声明
- 在 C 程序中,变量使用前需要声明其类型。变量既可以在函数内部声明为局部变量,也可以在函数外部声明为全局变量。
- 声明变量时可同时进行初始化,未初始化的局部变量包含不确定值(垃圾值),而全局变量和静态变量会被自动初始化为
0。 - 示例:
int count = 0;、float price = 9.99;、char grade = 'A';
语句和表达式
- 语句用于组织程序中的操作,例如函数调用、赋值或控制流。表达式则可以由变量、运算符和常量等构成,并参与求值。
- C 语言中,赋值本身也是一个表达式,具有值(即被赋的值),因此可以写出
a = b = c = 0;这样的链式赋值。 - 示例:
printf("Enter two integers: ");sum = add(num1, num2);控制流语句
- 控制流语句决定程序的执行顺序,包括条件判断(
if/else、switch)、循环(for、while、do-while),以及break、continue、goto和return等跳转语句。 - 合理使用控制流语句有助于让程序逻辑更清晰;为了保持代码可读性,应避免滥用
goto。 - 示例:
if (num1 > num2) { printf("num1 is greater than num2");}函数定义
- 函数定义包含完整的函数体,描述函数的具体实现。一个函数通常由返回类型、函数名、参数列表和函数体组成。设计函数时,可以尽量让每个函数专注于一项明确的任务。
- C 语言的函数不支持重载,每个函数名在同一作用域中必须唯一。函数可以递归调用自身。
- 示例:
int add(int a, int b) { return a + b;}返回语句
return语句用于终止函数的执行,并将控制权连同返回值一起交还给调用者。void类型的函数可以使用不带值的return;提前退出,也可以省略return语句。main()函数的返回值会传递给操作系统,0通常表示成功,可使用标准宏EXIT_SUCCESS和EXIT_FAILURE(定义在<stdlib.h>)。- 示例:
return 0;、return EXIT_SUCCESS;
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!














