C/C++ 中的宏 (macros) 与宏展开的可视化显示

C/C++ 中的宏 (macros) 与宏展开的可视化显示

1. Replacing text macros (替换文本宏)

https://en.cppreference.com/w/cpp/preprocessor/replace
https://www.codecademy.com/resources/docs/cpp/macros

A macro is a label defined in the source code that is replaced by its value by the preprocessor before compilation. Macros are initialized with the #define preprocessor command and can be undefined with the #undef command.
在预处理阶段,宏会被替换为真实所指代的代码片段。

The preprocessor supports two types of macros: object-like macros and function-like macros.

1.1 Syntax

#define identifier replacement-list(optional)                    (1)	
#define identifier( parameters ) replacement-list(optional)      (2)	
#define identifier( parameters, ... ) replacement-list(optional) (3) (since C++11)
#define identifier( ... ) replacement-list(optional)             (4) (since C++11)

Note: Macro definitions are not followed by a semicolon ;.
宏定义后不跟分号 ;

1.2 #define directives (#define 指令)

The #define directives define the identifier as macro, that is instruct the compiler to replace all successive occurrences of identifier with replacement-list, which can be optionally additionally processed. If the identifier is already defined as any type of macro, the program is ill-formed unless the definitions are identical.
#define 指令将 identifier 定义为宏,即指示编译器将所有连续出现的 identifier 替换为 replacement-list,也可以被进一步处理。如果 identifier 已经被定义为任何类型的宏,除非定义相同,否则程序是非良构的。

1.3 Object-like macros

These macros are replaced by their value in the source code before compilation. Their primary purpose is to define constants to be used in the code.
这些宏在编译前被替换为它们在源代码中的值。它们的主要目的是定义要在代码中使用的常量。

An object-like macro is defined as the simple identifier that is replaced by the code fragment. It looks like an object in code.

Object-like macros replace every occurrence of defined identifier with replacement-list. Version (1) of the #define directive behaves exactly like that.
object-like macros 将每次出现的已定义 identifier 替换为 replacement-list。Version (1) of the #define directive 与此完全相同。

Chain macros are defined as the macros inside the macros. In the chain macro, the parent macro is expanded first, and then its child macros.
宏展开的顺序并不是宏定义时的顺序,#define 只是告诉编译器定义了一个宏,而具体的求值,则是从使用宏的地方才开始的。

宏展开前:

#define VEHICLE CAR
#define CAR 5

VEHICLE;
CAR;

宏展开后

5;
5;

1.4 Function-like macros

Function-like macros work the same as the function call. It is used to replace the entire code instead of the function name.
function-like macros 的工作方式与函数调用相同,它用于替换整个代码而不是函数名。

These macros behave like functions, in that they take arguments that are used in the replaced code. Pair of parentheses immediately after the macro name is necessary. If we put a space between the macro name and the parentheses in the macro definition, then the macro will not work.
这些宏的行为类似于函数,因为它们采用被替换代码中使用的实数。必须在宏名称后紧跟一对括号。 如果我们在宏定义中的宏名和括号之间放置一个空格,那么宏将不起作用。

Note: When defining a function-like macro, there cannot be a space between the macro name and the opening parenthesis.
定义类函数宏时,宏名和左括号之间不能有空格。

Function-like macros replace each occurrence of defined identifier with replacement-list, additionally taking a number of arguments, which then replace corresponding occurrences of any of the parameters in the replacement-list.
function-like macros 将每次出现的已定义 identifier 替换为 replacement-list,可选地接受一定量的 arguments (实参),然后替换掉 replacement-list 中出现的任何对应的 parameters (形参)。

The syntax of a function-like macro invocation is similar to the syntax of a function call: each instance of the macro name followed by a ( as the next preprocessing token introduces the sequence of tokens that is replaced by the replacement-list. The sequence is terminated by the matching ) token, skipping intervening matched pairs of left and right parentheses.
function-like macros 调用的语法类似于函数调用的语法:宏名称的每个实例后跟一个 ( 作为下一个预处理标记,引入的标记序列将被替换为 replacement-list,该序列以匹配的 ) 标记终止,跳过中间的匹配左右括号对。

parenthesis [pə'renθəsɪs]:n. 圆括号,插入成分,插入语,插曲
intervene [ˌɪntə(r)ˈviːn]:v. 介入,干扰,出面,插嘴

For version (2), the number of arguments must be the same as the number of parameters in macro definition. For versions (3, 4), the number of arguments must not be less than the number of parameters (not (since C++20) counting ...). Otherwise the program is ill-formed. If the identifier is not in functional-notation, i.e. does not have parentheses after itself, it is not replaced at all.
对于版本 (2),实参数量必须与宏定义中的形参数量相同。对于版本 (3, 4),实参数量不得少于形参数量 (不计 ...),否则程序非良构。如果标识符未使用函数写法,即其自身之后没有括号,则根本不会被替换。

Version (2) of the #define directive defines a simple function-like macro.
Version (2) of the #define directive 定义简单 function-like macros

Version (3) of the #define directive defines a function-like macro with variable number of arguments. The additional arguments (called variable arguments) can be accessed using __VA_ARGS__ identifier, which is then replaced with arguments, supplied with the identifier to be replaced.
#define 指令的版本 (3) 定义有可变数量实参的 function-like macros。额外的实参 (称为可变实参) 可用 __VA_ARGS__ 标识符访问,然后用实参替换,并提供要替换的标识符。

Version (4) of the #define directive defines a function-like macro with variable number of arguments, but no regular arguments. The arguments (called variable arguments) can be accessed only with __VA_ARGS__ identifier, which is then replaced with arguments, supplied with the identifier to be replaced.
#define 指令的版本 (4) 定义有可变数量实参的 function-like macros,但无常规实参。额外的实参 (称为可变实参) 只能用 __VA_ARGS__ 标识符访问,然后用参数替换,并提供要替换的标识符。

For versions (3, 4), replacement-list may contain the token sequence __VA_OPT__ ( content ), which is replaced by content if __VA_ARGS__ is non-empty, and expands to nothing otherwise.
对于版本 (3, 4),replacement-list 可以含有记号序列 __VA_OPT__ ( content ),如果 __VA_ARGS__ 非空,那么它会被 content 替换,否则不展开成任何内容。

#define YQ(...) yq(0 __VA_OPT__(,) __VA_ARGS__)

YQ(a, b, c)  // replaced by yq(0 , a, b, c)
YQ()         // replaced by yq(0 )

#define FS(X, ...) fs(0, X __VA_OPT__(,) __VA_ARGS__)

FS(a, b, c)  // replaced by fs(0, a , b, c)
FS(a, )      // replaced by fs(0, a )
FS(a)        // replaced by fs(0, a )

#define QIANG(name, ...) S name __VA_OPT__(= { __VA_ARGS__ })

QIANG(yong);         // replaced by S yong ;
QIANG(qiang, 1, 2);  // replaced by S qiang = { 1, 2 };

Note: if an argument of a function-like macro includes commas that are not protected by matched pairs of left and right parentheses (most commonly found in template argument lists, as in assert(std::is_same_v<int, int>); or BOOST_FOREACH(std::pair<int,int> p, m)), the comma is interpreted as macro argument separator, causing a compilation failure due to argument count mismatch.
如果 function-like macro 的实参中包含不为匹配的左右括号对所保护的逗号 (最常出现于模板实参列表中,如 assert(std::is_same_v<int, int>); or BOOST_FOREACH(std::pair<int,int> p, m),那么逗号被解释成宏实参分隔符,并造成由于实参数量不匹配所致的编译失败。

comma [ˈkɒmə]:n. 逗号,(一段音乐中的) 短暂停顿或间歇

1.5 Reserved macro names

A translation unit that includes a standard library header may not #define or #undef names declared in any standard library header.
包含 standard library header 的翻译单元不可以 #define or #undef 已在任何 standard library header 中声明的名称。

A translation unit that uses any part of the standard library may not #define or #undef names lexically identical to:
使用标准库任何部分的翻译单元不可以 #define or #undef 词汇上等同于下列内容的名称:

  • keywords (关键字)
  • identifiers with special meaning (有特殊含义的标识符)
  • any standard attribute token, except that likely and unlikely may be defined as function-like macros (since C++20)
    任何标准属性记号,除了 likely and unlikely 可以定义为 function-like macros。(C++20 起)

Otherwise, the behavior is undefined.
否则,行为未定义。

lexical ['leksɪk(ə)l]:adj. 词汇的

1.6 # and ## operators

In function-like macros, a # operator before an identifier in the replacement-list runs the identifier through parameter replacement and encloses the result in quotes, effectively creating a string literal. In addition, the preprocessor adds backslashes to escape the quotes surrounding embedded string literals, if any, and doubles the backslashes within the string as necessary. All leading and trailing whitespace is removed, and any sequence of whitespace in the middle of the text (but not inside embedded string literals) is collapsed to a single space. This operation is called “stringification”. If the result of stringification is not a valid string literal, the behavior is undefined.
function-like macros 中,如果在 replacement-list 中的一个标识符前有 # 运算符,那么该标识符在运行形参替换的基础上以引号包围,实际上创建一个字符串字面量。另外,预处理器为内嵌的字符串字面量 (如果存在) 外围的引号添加反斜杠以进行转义,并按需要双写字符串中的反斜杠。移除所有前导和尾随空白符,并将文本中间 (除内嵌字符串字面量中间外) 的任何空白符序列缩减成单个空格。此操作被称为“字符串化”,如果字符串化的结果不是合法的字符串字面量,那么行为未定义。

When # appears before __VA_ARGS__, the entire expanded __VA_ARGS__ is enclosed in quotes:
# 出现于 __VA_ARGS__ 之前时,展开后的 __VA_ARGS__ 整体被包在引号中:

#define SHOW(...) puts(#__VA_ARGS__)

SHOW();             // expands to puts("");
SHOW(1, "x", int);  // expands to puts("1, \"x\", int");

宏展开前:

// Using a macro in the definition of a later macro
#define WORD "Hello "
#define OUTER(...) WORD #__VA_ARGS__

int main()
{
    std::cout << OUTER(World) << '\n';
    std::cout << OUTER(WORD World) << '\n';

    return 0;
}

宏展开后:

int main()
{
    std::cout << "Hello " "World" << '\n';
    std::cout << "Hello " "WORD World" << '\n';

    return 0;
}

A ## operator between any two successive identifiers in the replacement-list runs parameter replacement on the two identifiers (which are not macro-expanded first) and then concatenates the result. This operation is called “concatenation” or “token pasting”. Only tokens that form a valid token together may be pasted: identifiers that form a longer identifier, digits that form a number, or operators + and = that form a +=. A comment cannot be created by pasting / and * because comments are removed from text before macro substitution is considered. If the result begins with a sequence matching the syntax of universal character name, the behavior is undefined. This determination does not consider the replacement of universal character names in translation phase 3. (since C++23) If the result of concatenation is not a valid token, the behavior is undefined.
如果在 replacement-list 中任何两个连续标识符之间有 ## 运算符,那么这两个标识符 (首先不是宏扩展) 在运行形参替换的基础上将结果进行拼接。此操作被称为“拼接”或“标记粘贴”。Only tokens that form a valid token together may be pasted: identifiers that form a longer identifier, digits that form a number, or operators + and = that form a +=. 不能通过粘贴 /* 来创建注释,这是因为注释在考虑文本宏替换前就已经被移除了。如果结果以匹配通用字符名称语法的序列开头,则行为未定义。在翻译阶段 3 中的通用字符名替换不算在内。 (C++23 起) 如果连接的结果不是合法记号,那么行为未定义。

通过 ## 将两个宏展开成一个,即将两者进行了拼接,称为 concatenation or token pasting

#include <iostream>

// Using a macro in the definition of a later macro
#define WORD "Hello "
#define OUTER(...) WORD #__VA_ARGS__

int main()
{
    std::cout << OUTER(World) << '\n';
    std::cout << OUTER(WORD World) << '\n';

    return 0;
}

Output:

Hello World
Hello WORD World

Note: some compilers offer an extension that allows ## to appear after a comma and before __VA_ARGS__, in which case the ## does nothing when the variable arguments are present, but removes the comma when the variable arguments are not present: this makes it possible to define macros such as fprintf (stderr, format, ##__VA_ARGS__). This can also be achieved in a standard manner using __VA_OPT__, such as fprintf (stderr, format __VA_OPT__(, ) __VA_ARGS__). (since C++20)
一些编译器提供了一项扩展,允许 ## 出现于逗号后及 __VA_ARGS__ 前,此情况下 ## 在存在可变实参时不做任何事,但在不存在可变实参时移除逗号:这使得可以定义如 fprintf (stderr, format, ##__VA_ARGS__) 这样的宏。这也可以使用 __VA_OPT__ 标准方式实现,例如 fprintf (stderr, format __VA_OPT__(, ) __VA_ARGS__)。 (C++20 起)

宏展开前:

#define MACRO_PRINTF(fmt, ...) printf(fmt, __VA_ARGS__)

MACRO_PRINTF("cheng", "yong", "qiang");
MACRO_PRINTF("yong", "qiang");
MACRO_PRINTF("cheng");

__VA_ARGS__ 为空,printf 会被展开成 printf(fmt, ),多余一个逗号。
宏展开后:

printf("cheng", "yong", "qiang");
printf("yong", "qiang");
printf("cheng", );

宏展开前:

#define MACRO_PRINTF(fmt, ...) printf(fmt, ##__VA_ARGS__)

MACRO_PRINTF("cheng", "yong", "qiang");
MACRO_PRINTF("yong", "qiang");
MACRO_PRINTF("cheng");

__VA_ARGS__ 前面写上 ##,当变参为空时,逗号被删除。
宏展开后:

printf("cheng", "yong", "qiang");
printf("yong", "qiang");
printf("cheng");

1.7 #undef directive (#undef 指令)

The #undef directive undefines the identifier, that is cancels previous definition of the identifier by #define directive. If the identifier does not have associated macro, the directive is ignored.
#undef 指令取消定义 identifier,即取消之前由 #define 指令定义的 identifier。如果 identifier 没有关联的宏,则该指令将被忽略。

#ifdef preprocessor function is used to check if a macro is defined earlier or not. If it is defined, then the code executes normally.

#ifndef MACRO  
// code
#endif  

#include preprocessor function tells the system to include the current file specified in the argument.

If the header file is predefined, then write the header file name in angular brackets.

#include <iostream>

If a programmer has written their own header file, then write the header file name in quotes.

#include “yongqiang.h” 

1.8 Multi-line macros

An object-like macro could have a multi-line.
可通过反斜杠 \ 实现换行从而定义出多行的宏。

宏展开前:

#define table 2,  \
              4,  \
              6,  \
              8,  \
              10, \
              12 

int arr[] = { table };

宏展开后:

int arr[] = { 2, 4, 6, 8, 10, 12 };

1.9 Example

宏展开前:

// Make function factory
#define FUNCTION(name, val) int function_##name() { return val; }

FUNCTION(cheng, 3)
FUNCTION(yong, 6)
FUNCTION(qiang, 9)

#undef FUNCTION

#define FUNCTION (35)

#define OUTPUT(str) std::cout << "output: " #str << '\n'
 
int main()
{
    std::cout << "cheng: " << function_cheng() << '\n';
    std::cout << "yong : " << function_yong() << '\n';
    std::cout << "qiang: " << function_qiang() << '\n';
 
    std::cout << FUNCTION << '\n';

    OUTPUT(strong);
 
    return 0;
}

宏展开后:

int function_cheng() { return 3; }
int function_yong() { return 6; }
int function_qiang() { return 9; }

int main()
{
    std::cout << "cheng: " << function_cheng() << '\n';
    std::cout << "yong : " << function_yong() << '\n';
    std::cout << "qiang: " << function_qiang() << '\n';

    std::cout << (35) << '\n';

    std::cout << "output: " "strong" << '\n';

    return 0;
}
#include <iostream>

// Make function factory
#define FUNCTION(name, val) int function_##name() { return val; }

FUNCTION(cheng, 3)
FUNCTION(yong, 6)
FUNCTION(qiang, 9)

#undef FUNCTION

#define FUNCTION (35)

#define OUTPUT(str) std::cout << "output: " #str << '\n'
 
int main()
{
    std::cout << "cheng: " << function_cheng() << '\n';
    std::cout << "yong : " << function_yong() << '\n';
    std::cout << "qiang: " << function_qiang() << '\n';
 
    std::cout << FUNCTION << '\n';

    OUTPUT(strong);
 
    return 0;
}

Output:

cheng: 3
yong : 6
qiang: 9
35
output: strong

The disadvantage of it is here the entire code is substituted so the program becomes lengthy if a macro is called several times.
它的缺点是这里要替换整个代码,因此如果多次调用宏,程序会变得冗长。

2. Predefined macros (预定义宏)

The following macro names (macro constant) are predefined in every translation unit.

  • __cplusplus

denotes the version of C++ standard that is being used, expands to value 199711L (until C++11), 201103L (C++11), 201402L (C++14), 201703L (C++17), or 202002L (C++20).
代表所用的 C++ 标准版本。

denote [dɪˈnəʊt]:v. 表示,标志,预示,象征
  • __STDC_HOSTED__ (C++11)

expands to the integer constant 1 if the implementation is hosted (runs under an OS), ​0​ if freestanding (runs without an OS)
如果实现有宿主 (在操作系统下运行) 则展开成整数常量 1,如果实现自立 (不在操作系统下运行) 则展开成 ​0​。

  • __FILE__

expands to the name of the current file, as a character string literal, can be changed by the #line directive.
展开成当前文件名,作为字符串字面量,可用 #line 指令更改​。

  • __LINE__

expands to the source file line number, an integer constant, can be changed by the #line directive.
展开成源文件行号,整数常量,可用 #line 指令更改。

  • __DATE__

expands to the date of translation, a character string literal of the form "MMM DD YYYY". The first character of "DD" is a space if the day of the month is less than 10. The name of the month is as if generated by std::asctime().
展开成形式为 "MMM DD YYYY" 的日期字符串。如果月份中的第几天小于 10,则 "DD" 的第一个字符是一个空格。月份名如同以 std::asctime() 生成。

  • __TIME__

expands to the time of translation, a character string literal of the form "HH:MM:SS".
展开成形式为 "HH:MM:SS" 的时间字符串字面量。

  • __STDCPP_DEFAULT_NEW_ALIGNMENT__ (C++17)

expands to an std::size_t literal whose value is the alignment guaranteed by a call to alignment-unaware operator new (larger alignments will be passed to alignment-aware overload, such as operator new(std::size_t, std::align_val_t).
展开成 std::size_t 字面量,其值是通过调用 alignment-unaware operator new 所保证的对齐 (larger alignments will be passed to alignment-aware overload, such as operator new(std::size_t, std::align_val_t)

aware [əˈweə(r)]:adj. 意识到,知道,明白,发现
conformance [kən'fɔ:məns]:n. 遵从,恪守

The following additional macro names may be predefined by the implementations.

  • __STDC__

implementation-defined value, if present, typically used to indicate C conformance.
典型地用于指示 C 遵从性。

  • __STDC_VERSION__ (C++11)

implementation-defined value, if present.

  • __STDC_ISO_10646__ (C++11)

expands to an integer constant of the form yyyymmL, if wchar_t uses Unicode, the date indicates the latest revision of Unicode supported.
如果 wchar_t 使用 Unicode,则展开成 yyyymmL 形式的整数常量,日期指示所支持的 Unicode 的最近版本。

  • __STDC_MB_MIGHT_NEQ_WC__ (C++11)

expands to 1 if 'x' == L'x' might be false for a member of the basic character set, such as on EBCDIC-based systems that use Unicode for wchar_t.
如果对于基本字符集成员 'x' == L'x' 可能为假,则展开成 1,如在基于 EBCDIC 并且为 wchar_t 使用 Unicode 的系统上。

  • __STDCPP_THREADS__ (C++11)

expands to 1 if the program can have more than one thread of execution.
如果程序能拥有多于一个执行线程则展开成 1

  • __STDCPP_STRICT_POINTER_SAFETY__ (C++11) (removed in C++23)

expands to 1 if the implementation has strict std::pointer_safety.
如果实现支持严格 std::pointer_safety 则展开成 1

The values of these macros (except for __FILE__ and __LINE__) remain constant throughout the translation unit. Attempts to redefine or undefine these macros result in undefined behavior.
这些宏的值 (除了 __FILE__ and __LINE__) 在整个翻译单元保持为常量。试图重定义或取消定义这些宏会导致未定义行为。

Note: in the scope of every function body, there is a special function-local predefined variable named __func__, defined as a static character array holding the name of the function in implementation-defined format. It is not a preprocessor macro, but it is used together with __FILE__ and __LINE__, e.g. by assert. (since C++11)
在每个函数体的作用域内部都有一个名为 __func__ 的特殊的函数局域预定义变量,它被定义为一个持有具有实现定义格式的函数名的静态字符数组。它不是预处理器宏,但它与 __FILE__ and __LINE__ 一起使用,例如 assert。(C++11 起)

2.1 Language feature-testing macros (语言功能特性检测宏)

The standard defines a set of preprocessor macros corresponding to C++ language features introduced in C++11 or later. They are intended as a simple and portable way to detect the presence of said features. (since C++20)
标准定义一组对应于 C++11 或之后引入的 C++ 语言功能特性的预处理器宏。它们被用来以简单且可移植的方式检测所说的特性是否存在。(C++20 起)

presence [ˈprez(ə)ns]:n. 存在,出现,在场,出席

3. 预处理宏 (macros) 展开的可视化显示

Compiler Explorer
https://godbolt.org/
在这里插入图片描述
C/C++ 代码编译过程中,可通过相应参数来获取到各编译步骤中的产出,查看预处理之后的宏,使用 gcc 加上 -E 参数。

-E 参数
在这里插入图片描述

References

https://yongqiang.blog.csdn.net/
https://www.codecademy.com/resources/docs/cpp

The C Preprocessor
https://gcc.gnu.org/onlinedocs/cpp/index.html

3 Macros
https://gcc.gnu.org/onlinedocs/cpp/Macros.html

猜你喜欢

转载自blog.csdn.net/chengyq116/article/details/128668069