一 概述
二 类型
三 语句
四 函数
五 数据
六 内存
七 代码
附录
3. 预处理
编译器会对每个源码文件进行预处理。预处理操作插入头文件内容,识别条件编译代码段,删除注释,并用定义替换宏。预处理指令用 #
开头,以换行结束。且在 #
之前只能有空白符。
可用
\
续行符换行。预处理器会删除换行符和续行符,使其合并成一行。所以反斜线后面不能有任何内容,包括注释。
宏定义
所谓宏(macro)就是为文本定义名字,随后被预处理器直接展开并替换。
宏名称通常全部大写,以区分变量。如果宏内容为表达式,应该用括号包含起来,避免展开后因优先级导致错误。
宏直接文本替换,不能用来创建新的宏定义,预处理器不会递归处理。
#define A #define B print("abc") int main (void) { A return 0; }
$ gcc -E main.c int main (void) { #define B print("abc") return 0; }
也可仅作为条件,没有文本内容。
#define X #ifdef X printf("aa"); #endif
宏定义作用于整个翻译单元。不能重复定义,除非先用 #undef
取消。
宏函数
可为宏指定参数,使其看上去像函数。预处理器先替换参数,随后在引用位置替换宏文本。
参数标识符以逗号分隔,没有类型,因为直接文本替换。注意宏名字和参数列表左括号之间没有空格,否则就成了无参数宏了。如果调用参数也是宏,则参数宏先被替换。
参数列表使用 ...
表示可选参数 __VA_ARGS__
。
#define PRINT(fmt, name, ...) \ printf(fmt, name, __VA_ARGS__) int main (void) { PRINT("%s, %d, %d\n", "test", 1, 2); return 0; }
$ gcc -E main.c int main (void) { printf("%s, %d, %d\n", "test", 1, 2); return 0; }
如作为独立计算单元,那么需有独立作用域,避免和展开处冲突。用 ({...})
包含起来,其最后一个表达式作为返回值。
区别于纯逗号运算符。它从左到右处理,并返回最后一个。
(1,2,3)-> 3
#define ADD(a, b) \ ({ \ int z = a + b; \ z; \ }) int main (void) { int x = ADD(1, 2); printf("%d\n", x); return 0; }
$ gcc -E main.c int main (void) { int x = ({ int z = 1 + 2; z; }); printf("%d\n", x); return 0; }
如没有返回值,直接用单次循环语句包含即可。
#define TEST(a, b) \ do { \ int z = a + b; \ printf("%d\n", z); \ } while(0) int main (void) { TEST(1, 2); return 0; }
$ gcc -E main.c int main (void) { do { int z = 1 + 2; printf("%d\n", z); } while(0); return 0; }
操作符
基本上是将参数转换为字符串。
- 单个
#
将参数转换为引号包含的字符串字面量。 - 双
##
将左右标识符连接起来。
自动对相关符号做转义处理,使其符合语法,并原样输出。
struct user { char *name; void(*action)(void); } #define USER(name) { #name, name ## _command } int main (void) { struct user users[] = { USER(a), USER(b), }; return 0; }
$ gcc -E main.c int main (void) { struct user users[] = { { "a", a_command }, { "b", b_command }, }; return 0; }
泛型选择
以 _Generic
,根据条件表达式类型返回特定结果。
#define DEFAULT(x) \ _Generic((x), \ int : 1, \ float : 1.2, \ default: 0 \ ) int main (void) { float x = DEFAULT(x); printf("%f\n", x); return 0; }
$ gcc -E main.c int main (void) { float x = _Generic((x), int : 1, float : 1.2, default: 0 );; printf("%f\n", x); return 0; }
表达式不能直接提供 int、double 这样的类型关键字。
实现 typename 宏函数。
#define typename(x) _Generic((x), /* Get the name of a type */ \ \ _Bool: "_Bool", unsigned char: "unsigned char", \ char: "char", signed char: "signed char", \ short int: "short int", unsigned short int: "unsigned short int", \ int: "int", unsigned int: "unsigned int", \ long int: "long int", unsigned long int: "unsigned long int", \ long long int: "long long int", unsigned long long int: "unsigned long long int", \ float: "float", double: "double", \ long double: "long double", char *: "pointer to char", \ void *: "pointer to void", int *: "pointer to int", \ default: "other") int main (void) { char *s = typename(1); return 0; }
可返回函数,包括提供调用参数。
#define ADD(x, y) \ _Generic((x), \ int : int_add, \ double : float_add, \ default: str_add \ )(x, y) int main (void) { ADD(1, 2); return 0; }
$ gcc -E main.c int main (void) { _Generic((1), int: int_add, double: float_add, default: str_add )(1, 2); return 0; }
如只获取参数类型,可使用 typeof( -std=gnu11
)。
#define X(x, y) \ do { \ typeof(x) z = x + y; \ printf("%d\n", z); \ } while(0) int main (void) { X(1, 2); return 0; }
预定义宏
ISO C 支持的几个预定义宏。
__DATE__
: 当前日期。__TIME__
: 当前时间。__FILE__
: 源码文件名(不含路径)。__LINE__
: 行号。__func__
: 函数名。__STDC__
: 编译器是否符合 ISO C 规范(1)。__STDC_VERSION__
: ISO C 版本号。
int main (void) { printf("%s, %s, %s, %d, %s\n", __DATE__, __TIME__, __FILE__, __LINE__, __func__); return 0; }
#if __STDC_VERSION__ < 201710L #error "C17" #endif
调试器支持
使用 -g3
编译参数,使调试信息包含宏定义。
#define X(a) \ do { \ int z = a; \ z++; \ printf("%d\n", z); \ } while(0) int main (void) { X(100); return 0; }
$ gcc -g3 -O0 -o test main.c $ gdb test (gdb) info macro X Defined at /root/c/test/main.c:4 #define X(a) do { int z = a; z++; printf("%d\n", z); } while(0) (gdb) macro expand X(10) expands to: do { int z = 10; z++; printf("%d\n", z); } while(0)
条件编译
使用 #if...#elif...#else...#endif
按条件选择源码。
#if
、#elif
条件表达式必须是整形常量,不能使用转换语句。或用defined
判断某个宏是否定义。但可能已被#undef
取消。可直接使用#ifdef
、#ifndef
,等价于#if defined(...)
、#if !define(...)
。
可结合 -D<macro>
编译参数使用。
#ifdef RELEASE puts("release version"); #else puts("debug version"); #endif
$ gcc -DRELEASE -E main.c
编译器指令
#error
: 让预处理器发出错误信息。#pragma
: 向编译器提供附加信息。
#ifndef __STDC__ #error "This compiler does not conform to the ANSI C standard." #endif
#pragma pack(8) struct data { char a; long double b; }; #pragma pack()
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论