Replacing text macros
预处理器支持文本宏替换和函数式文本宏替换。
| 目录 | 
语法
| 
          
           #define
          
         标识符
         
         
          替换列表
         
         
         
          (可选) | (1) | ||||||||
| 
          
           #define
          
         标识符
         
         
          
           (
          
         参数
          
           )
          
         替换列表 | (2) | ||||||||
| 
          
           #define
          
         标识符
         
         
          
           (
          
         参数
          
           , ... )
          
         替换列表 | (3) | (C99起) | |||||||
| 
          
           #define
          
         标识符
         
         
          
           ( ... )
          
         替换列表 | (4) | (C99起) | |||||||
| 
          
           #undef
          
         标识符 | (5) | ||||||||
说明
#define 指令
       
        #define
       
       指令将
       
        identifier
       
       定义为宏,即指示编译器将所有后续出现的
       
        identifier
       
       替换为
       
        replacement-list
       
       ,该替换列表可选择性地进行额外处理。若该标识符已定义为任何类型的宏,则程序非良构,除非定义完全一致。
      
对象式宏
       对象式宏会将所有出现的已定义
       
        标识符
       
       替换为
       
        替换列表
       
       。
       
        #define
       
       指令的版本(1)正是如此运作的。
      
函数式宏
类函数宏会将每个已定义的 标识符 出现处替换为 替换列表 ,同时接收若干参数,这些参数随后会替换 替换列表 中对应 形参 的所有出现位置。
函数式宏调用的语法与函数调用语法相似:每个宏名称实例后紧跟一个 ( 作为下一个预处理标记时,会引入被替换列表所替换的标记序列。该序列由匹配的 ) 标记终止,同时跳过中间成对匹配的左右括号。
参数数量必须与宏定义中的参数数量相同,否则程序格式错误。若标识符未采用函数表示法(即自身后未跟随圆括号),则完全不会被替换。
       版本(2)的
       
        #define
       
       指令定义了一个简单的函数式宏。
      
       版本 (3) 的
       
        #define
       
       指令定义了一个可变参数数量的函数式宏。额外的参数可通过
       
        
         __VA_ARGS__
        
       
       标识符访问,该标识符随后会被替换为与待替换标识符一同提供的参数。
      
       版本 (4) 的
       
        #define
       
       指令定义了一个类似函数的宏,该宏具有可变数量的参数,但不包含常规参数。这些参数只能通过
       
        
         __VA_ARGS__
        
       
       标识符访问,该标识符随后会被替换为与待替换标识符一同提供的参数。
      
| 
          对于版本(3,4),
          
           替换列表
          
          可包含词法序列
           #define F(...) f(0 __VA_OPT__(,) __VA_ARGS__) F(a, b, c) // replaced by f(0, a, b, c) F() // replaced by f(0) #define G(X, ...) f(0, X __VA_OPT__(,) __VA_ARGS__) G(a, b, c) // replaced by f(0, a, b, c) G(a, ) // replaced by f(0, a) G(a) // replaced by f(0, a) #define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ }) SDEF(foo); // replaced by S foo; SDEF(bar, 1, 2); // replaced by S bar = { 1, 2 }; | (自 C23 起) | 
       
       注意:如果函数式宏的参数包含未被匹配的左右括号保护的逗号(例如
       
        
         macro
         
          (
         
         array
         
          [
         
         x
         
          =
         
         y, x
         
          +
         
         
          1
         
         
          ]
         
         
          )
         
        
       
       或
       
        
         
          
           atomic_store
          
         
         
          (
         
         p,
         
          (
         
         
          struct
         
         S
         
          )
         
         
          {
         
         a, b
         
          }
         
         
          )
         
         
          ;
         
        
       
       ),该逗号会被解释为宏参数分隔符,导致因参数数量不匹配而引发编译失败。
      
# 与 ## 运算符
       在函数式宏中,
       
        #
       
       运算符置于
       
        替换列表
       
       中的标识符前时,会对该标识符进行参数替换并将结果用引号括起,从而有效创建字符串字面量。此外,预处理器会添加反斜杠来转义嵌入字符串字面量(如果存在)的引号,并视需要将字符串内的反斜杠加倍处理。所有首尾空白符会被移除,文本中间(但不包括嵌入字符串字面量内部)的连续空白符序列会被压缩为单个空格。此操作称为“字符串化”。若字符串化结果不是有效的字符串字面量,则行为未定义。
      
| 
          当
           #define showlist(...) puts(#__VA_ARGS__) showlist(); // expands to puts("") showlist(1, "x", int); // expands to puts("1, \"x\", int") | (C99起) | 
       在
       
        替换列表
       
       中任意两个相邻标识符之间的
       
        ##
       
       运算符会对这两个标识符执行参数替换,然后将结果连接起来。此操作称为“连接”或“标记粘贴”。只有能共同构成有效标记的符号方可被粘贴:组成更长标识符的标识符、组成数字的数码,或是组成
       
        
         +=
        
       
       的运算符
       
        
         +
        
       
       和
       
        
         =
        
       
       。无法通过粘贴
       
        
         /
        
       
       和
       
        
         *
        
       
       来创建注释,因为在宏替换处理前注释已从文本中移除。若连接结果不是有效标记,则行为未定义。
      
       注意:某些编译器提供了一个扩展,允许在逗号后和
       
        
         __VA_ARGS__
        
       
       前出现
       
        ##
       
       ,在这种情况下当
       
        
         __VA_ARGS__
        
       
       非空时
       
        ##
       
       不执行任何操作,但当
       
        
         __VA_ARGS__
        
       
       为空时会移除逗号:这使得定义诸如
       
        
         
          
           fprintf
          
         
         
          (
         
         
          
           stderr
          
         
         , format,
         
          ##__VA_ARGS__)
         
        
       
       之类的宏成为可能。
      
       
        #
       
       和
       
        ##
       
       运算符的求值顺序是未指定的。
      
#undef 指令
       
        #undef
       
       指令用于取消定义
       
        标识符
       
       ,即撤销之前通过
       
        #define
       
       指令对该
       
        标识符
       
       的定义。如果该标识符没有关联的宏定义,则该指令将被忽略。
      
预定义宏
以下宏名称在任何翻译单元中都是预定义的:
| 
           
            
             __STDC__
            
           
           | 展开为整型常量
         
          
           
            1
           
          
         
         。此宏用于指示符合标准的实现 (宏常量) | 
| 
           
            
             __STDC_VERSION__
            
           
           
           
            
             
              (C95)
             
            
           
           | 展开为
         
          
           long
          
         
         类型的整型常量,其值随C标准版本更新而递增: 
 | 
| 
           
            
             __STDC_HOSTED__
            
           
           
           
            
             
              (C99)
             
            
           
           | 若实现为托管式(在操作系统下运行)则展开为整型常量
         
          
           
            1
           
          
         
         ,若为独立式(无需操作系统运行)则展开为
         
          
           
           
            0
           
            (宏常量) | 
| 
           
            
             __FILE__
            
           
           | 展开为当前文件名(字符串字面量),可通过 
           #line
          指令修改(宏常量) | 
| 
           
            
             __LINE__
            
           
           | 展开为源文件行号(整型常量),可通过 
           #line
          指令修改(宏常量) | 
| 
           
            
             __DATE__
            
           
           | 展开为翻译日期(格式为“Mmm dd yyyy”的字符串字面量)。月份名称与
         
          
           asctime
          
         
         生成格式相同,若日期数字小于10则“dd”首字符为空格 (宏常量) | 
| 
           
            
             __TIME__
            
           
           | 展开为翻译时间(格式为“hh:mm:ss”的字符串字面量),与
         
          
           
            
             asctime
            
           
           
            (
           
           
            )
           
          
         
         生成的时间格式相同 (宏常量) | 
| 
           
            
             __STDC_UTF_16__
            
           
           
           
            
             
              (C23)
             
            
           
           | 展开为
         
          
           
            1
           
          
         
         以指示
         
          char16_t
         
         使用 UTF-16 编码 (宏常量) | 
| 
           
            
             __STDC_UTF_32__
            
           
           
           
            
             
              (C23)
             
            
           
           | 展开为
         
          
           
            1
           
          
         
         以指示
         
          char32_t
         
         使用 UTF-32 编码 (宏常量) | 
| 
           
            
             __STDC_EMBED_NOT_FOUND__
            
            
             __STDC_EMBED_FOUND__
            
            
             __STDC_EMBED_EMPTY__
            
           
           
           
            
             
              (C23)
             
            
           
           | 分别展开为
         
          
           
           
            0
           
           
          
         
         、
         
          
           
            1
           
          
         
         和
         
          
           
            2 (宏常量) | 
以下额外的宏名称可能由实现预定义:
| 
           
            
             __STDC_ISO_10646__
            
           
           
           
            
             
              (C99)
             
            
           
           | 若
         
          
           wchar_t
          
         
         使用 Unicode,则展开为 
          yyyymmL
         形式的整型常量;该日期表示所支持的最新 Unicode 修订版本(宏常量) | 
| 
           
            
             __STDC_IEC_559__
            
           
           
           
            
             
              (C99)
             
            
            
             
              (deprecated in C23)
             
            
           
           | 若支持 IEC 60559 则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_IEC_559_COMPLEX__
            
           
           
           
            
             
              (C99)
             
            
            
             
              (deprecated in C23)
             
            
           
           | 若支持 IEC 60559 复数运算则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_UTF_16__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若
         
          char16_t
         
         使用 UTF-16 编码则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_UTF_32__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若
         
          char32_t
         
         使用 UTF-32 编码则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_MB_MIGHT_NEQ_WC__
            
           
           
           
            
             
              (C99)
             
            
           
           | 若对于基础字符集的成员(例如在使用 Unicode 表示
         
          
           wchar_t
          
         
         的 EBCDIC 系统上)可能存在
         
          
           
            'x'
           
           
            ==
           
           L
           
            'x'
           
          
         
         为假的情况,则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_ANALYZABLE__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若支持
         
          可分析性
         
         则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_LIB_EXT1__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若支持
         
          边界检查接口
         
         则展开为整型常量
         
          
           
            201112L (宏常量) | 
| 
           
            
             __STDC_NO_ATOMICS__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若不支持
         
          原子
         
         类型和
         
          原子操作库
         
         则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_NO_COMPLEX__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若不支持
         
          复数类型
         
         和
         
          复数数学库
         
         则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_NO_THREADS__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若不支持
         
          多线程
         
         则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_NO_VLA__
            
           
           
           
            
             
              (C11)
             
            
           
           | 若不支持
         
          变长数组
         
         
          
           和可变修改类型
          
          
           
            (until C23)
           
          
         
         
          
           的自动存储期
          
          
           
            (since C23)
           
          
         
         则展开为
         
          
           
            1 (宏常量) | 
| 
           
            
             __STDC_IEC_60559_BFP__
            
           
           
           
            
             
              (C23)
             
            
           
           | 若支持 IEC 60559 二进制浮点运算则展开为
         
          
           
            202311L (宏常量) | 
| 
           
            
             __STDC_IEC_60559_DFP__
            
           
           
           
            
             
              (C23)
             
            
           
           | 若支持 IEC 60559 十进制浮点运算则展开为
         
          
           
            202311L (宏常量) | 
| 
           
            
             __STDC_IEC_60559_COMPLEX__
            
           
           
           
            
             
              (C23)
             
            
           
           | 若支持 IEC 60559 复数运算则展开为
         
          
           
            202311L (宏常量) | 
| 
           
            
             __STDC_IEC_60559_TYPES__
            
           
           
           
            
             
              (C23)
             
            
           
           | 若支持 IEC 60559 交换和扩展类型则展开为
         
          
           
            202311L (宏常量) | 
       这些宏的值(除了
       
        __FILE__
       
       和
       
        __LINE__
       
       )在翻译单元内保持恒定。尝试重新定义或取消定义这些宏会导致未定义行为。
      
| 
          预定义变量
          
           
            __func__
           
          
          (详见
          
           函数定义
          
          )并非预处理器宏,尽管它常与
           | (C99 起) | 
示例
#include <stdio.h> // 创建函数工厂并使用 #define FUNCTION(name, a) int fun_##name(int x) { return (a) * x; } FUNCTION(quadruple, 4) FUNCTION(double, 2) #undef FUNCTION #define FUNCTION 34 #define OUTPUT(a) puts( #a ) int main(void) { printf("quadruple(13): %d\n", fun_quadruple(13)); printf("double(21): %d\n", fun_double(21)); printf("%d\n", FUNCTION); OUTPUT(billion); // 注意没有引号 }
输出:
quadruple(13): 52 double(21): 42 34 billion
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C标准。
| 缺陷报告 | 适用标准 | 发布时的行为 | 正确行为 | 
|---|---|---|---|
| DR 321 | C99 | 未明确说明基本字符集中 L 'x' == 'x' 是否恒成立 | 为此新增 __STDC_MB_MIGHT_NEQ_WC__ 宏 | 
参考文献
- C23 标准 (ISO/IEC 9899:2024):
- 
         - 6.10.4 宏替换 (p: 187-184)
 
- 
         - 6.10.9 预定义宏名称 (p: 186-188)
 
- C17 标准 (ISO/IEC 9899:2018):
- 
         - 6.10.3 宏替换 (p: 121-126)
 
- 
         - 6.10.8 预定义宏名称 (p: 127-129)
 
- C11 标准 (ISO/IEC 9899:2011):
- 
         - 6.10.3 宏替换 (p: 166-173)
 
- 
         - 6.10.8 预定义宏名称 (p: 175-176)
 
- C99标准(ISO/IEC 9899:1999):
- 
         - 6.10.3 宏替换(页码:151-158)
 
- 
         - 6.10.8 预定义宏名(页码:160-161)
 
- C89/C90 标准 (ISO/IEC 9899:1990):
- 
         - 3.8.3 宏替换
 
- 
         - 3.8.8 预定义宏名称
 
参见
| 
          
           
            C++ 文档
           
          
          关于
          
           
            替换文本宏
           
          
          |