C语言 程序环境和预处理
程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境。
- 翻译环境: 在此环境中源代码被转换为可执行的机器指令;
- 执行环境: 它用于运行程序.
程序的编译
- 预处理: gcc -E hello.c -o hello.i 本质是文本操作
- 头文件的插入
- 宏定义的替换
- 条件编译的处理
- 删除注释
- 编译: gcc -S hello.i -o hello.s 将预处理后的源代码转化成汇编代码
- 词法分析
- 语法分析
- 语义分析
- 符号汇总
- ...
- 汇编: gcc -c hello.s -o hello.o
- 生成符号表
- 将汇编代码转换成机器指令(目标文件)
- 目标文件为 elf 格式, 可使用 readelf -a hello.o 查看
- 链接: 将多个目标文件和链接库链接在一起生成一个可执行文件 gcc main.o sum.o -o app.out
- 合并段表
- 符号表的合并和重定位
预处理
预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__FUNCTION__ //进行编译的函数
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#include <stdio.h>
int main() {
printf("__FILE__: %s\n", __FILE__);
printf("__LINE__: %d\n", __LINE__);
printf("__DATE__: %s\n", __DATE__);
printf("__TIME__: %s\n", __TIME__);
printf("__FUNCTION__: %s\n", __FUNCTION__);
printf("__STDC__: %d\n", __STDC__);
return 0;
}
#define
概述
语法:
#define identifier token-stringtoken-string可选#define identifier(parament-list) token-string
注意:
- parament-list 是一个由逗号隔开的符号表, 他们可能出现在 token-string 中.
- 参数列表的左括号必须与 identifier 相邻. 如果两者之间有任何空白存在, 参数列表就会被解释为 token-string 的一部分.
#include <stdio.h>
#define SQUARE(X) ((X)*(X))
#define DOUBLE(X) ((X)+(X))
int main() {
printf("SQUARE(4)=%d\n", SQUARE(4));
printf("2*DOUBLE(4)=%d\n", 2*DOUBLE(4));
return 0;
}

#define替换规则
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名,被他们的值替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define定义中可以出现其它#define定义的常量。但是对于宏,不能出现递归。
- 当与处理器搜索#define定义的符号时候,字符串常量的内容并不被搜索。
# 和 ##
#include <stdio.h>
#define PRINT(X) printf("the value of " #X " is %d\n", X)
#define STRCAT(X,Y,Z) X##Y##Z
int main() {
printf("Hello" "World" "\n");
int a = 10;
// print: the value of a is 10
PRINT(a);
int class_101 = 250;
printf("class_101 = %d\n", STRCAT(class, _, 101));
return 0;
}

带有副作用的宏
#include <stdio.h>
#define MAX(X, Y) ((X)>(Y) ? (X): (Y))
int main() {
int a = 5, b = 8;
int m = MAX(a++, b++); // ((a++)>(b++) ? (a++): (b++))
printf("MAX(a, b) = %d\n", m);
printf("a = %d, b = %d\n", a, b);
return 0;
}

#undef
移除一个宏定义
语法 #undef name
#include <stdio.h>
#define N 100
int main() {
printf("N = %d\n", N);
#undef N
// printf("N = %d\n", N);
return 0;
}
命令行定义
新建 cmd.c 文件, 内容为:
#include <stdio.h>
int main() {
int arr[N] = {0};
for (int i = 0; i < N; ++i) {
arr[i] = i;
}
for (int i = 0; i < N; ++i) {
printf("%d ", i);
}
printf("\n");
return 0;
}
输入 gcc -D N=5 cmd.c -o cmd.out(-D N=100 指定N的值), 然后运行得出结果如下所示:

条件编译
示例1
#include <stdio.h>
#define DEFINE
int main() {
// 如果已定义 DEFINE, 参与下面的编译.
#ifdef DEFINE // 等价于 #if defined(DEFINE)
printf("DEFINE\n");
#endif
// 如果未定义 NON_DEFINE, 参与下面的编译.
#ifndef NON_DEFINE // 等价于 #if !defined(NON_DEFINE)
printf("NON_DEFINE\n");
#endif
return 0;
}
示例2
#include <stdio.h>
int main() {
#if FLAG == 100
printf("100\n");
#elif FLAG == 200
printf("200\n");
#else
printf("FLAG is %d\n", FLAG);
#endif
return 0;
}

文件包含
语法:
- 尖括号形式 #include
- 使用<>包含的头文件一般会先搜索-I选项后的路径(即用gcc编译时的-I选项),之后就是标准的系统头文件路径。
- 带引号的形式 #include "XXX"
- 用""号包含的头文件会首先搜索当前的工作目录,之后的搜索路径才是和<>号包含的头文件所搜索的路径一样的路径。

浙公网安备 33010602011771号