预处理器保留关键字
Preprocessor reserved keywords
对于宏,除了必须是标识符之外,是否有任何名称限制?例如,下面的内容是否有效?
#define assert getchar
#include <stdio.h>
int main(void)
{
assert();
}
代码 link: https://godbolt.org/z/ra63na.
main:
push rbp
mov rbp, rsp
mov eax, 0
call getchar
mov eax, 0
pop rbp
ret
预处理器是否了解C语言?或者它更像是一个查找和替换程序?
真的很蠢。它足以理解令牌替换,但仅此而已。
例如:#define test fail
将替换 test(...)
中的 test
,但不会替换 tested
或 "test"
。
因为 C 有一个非常基本的语法,所以编写一个可以处理和识别标记的解析器实际上并不难。让它理解 C 语法的全部超出了该工具的范围。
换句话说,对于像这样的输入程序:
#define test fail
int main() {
test(9, "test", tested());
return 0;
}
C 预处理器将其分解为标记,最终如下所示:
[ "#", "define", "test", "fail" ]
[ "int", "main", "(", ")", "{" ]
[ "test", "(", "9", "\"test\"", "tested", "(", ")", ")", ";" ]
...
其中每一个都使用简单的预处理器语法进行处理。
这稍微复杂一些,因为宏可以包含参数,但您明白了。使用的文法是整个C文法的一个简单子集。
是的,它是有效的。不,预处理器不支持语言。预处理器完全按照它所说的去做——包括内容,替换宏——如果这导致语法无效,编译器必须检测到。
除C符号命名规则外,没有C语言依赖项或保留字。所有预处理器指令都以 #
开头,这不是有效的 C 符号名称,因此不需要保留字。
预处理器可以是 运行 自己的 - 通过编译器驱动程序的命令行选项或在 GUN 工具链中它是一个独立的可执行文件 cpp
- 使其成为用于 C 和 C++ 源代码预处理以外的用途。
标准 (C11) 的第 7.1.2 节和第 7.1.3 节对此进行了介绍。以下是一些与宏有关的规则:
- 如果使用,header 应包含在任何外部声明或定义之外,并且应在第一次引用任何函数或它声明的 objects 之前首先包含它,或者它定义的任何类型或宏。
- 程序中不得有任何宏的名称在词法上与包含 header 之前定义的关键字相同,或者在扩展 header 中定义的任何宏时。
- 以下任何子条款中的每个宏名称(包括未来的库
directions) 如果包含任何相关的 headers 则保留用于指定用途;
除非另有明确说明。
- 具有以下任何子条款中列出的文件范围的每个标识符(包括
future library directions) 保留用作宏名称和标识符
如果包含任何关联的 header,则文件范围在同名 space 中。
所以您发布的确切程序是正确的,因为 <assert.h>
没有包括在内。但是,如果您确实包含 header.
,那将是未定义的行为
For macros, are there any name limitations other than it needs to be an identifier?
是的,它们受语言规范第 7.1.3 节(“保留标识符”)的规定约束,特别是:
- All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use
[including as macro names].
[...]
- Each macro name in any of the [standard library specification] subclauses (including the future library directions) is reserved for
use as specified if any of its associated headers is included; unless
explicitly stated otherwise
[...]
- Each identifier with file scope listed in any of the [standard library specification] subclauses (including the future library
directions) is reserved for use as a macro name and as an identifier
with file scope in the same name space if any of its associated
headers is included.
[...] If the program declares or defines an identifier in a context in
which it is reserved (other than as allowed by 7.1.4), or defines a
reserved identifier as a macro name, the behavior is undefined.
如果第二个要点还包含 assert.h
header,那么第二个要点将与您的示例代码相关。然后标识符 assert
将保留用作宏名称。您将其用作一个会触发未定义的行为。这不会对实现提出任何特定要求——事实上,这正是“未定义行为”的含义。它不需要实现来接受代码,也不需要拒绝它,也不需要在任何一种情况下发出任何类型的诊断。如果它确实接受了它,那么预处理器就不需要在 assert
上执行宏替换,也不会被禁止这样做,事实上,它也不需要以任何方式出现方式理性或可预测。
如果您将 getchar
定义为包含 stdio.h
的代码中的宏名称,则基于第三个要点的情况类似,如示例所示。然而,实际显示的代码是可以的。
你也问,
And does the preprocessor have any knowledge of the C language? Or is
it more like a find-and-replace program?
有一点。 C 预处理器不是一种 general-purpose 宏语言,并且尝试使用它时往往效果不佳。预处理器的输入是一系列标记,根据与 C 语法一致的规则确定,它使用与 C 相同的标识符语法。条件包含指令识别 C 的算术表达式的子集,并且它们根据宿主实现的整数数据类型之一工作。预处理器(或至少它之前的标记化阶段)理解 C 字符串文字和字符常量,因此宏替换不会影响这些内容。
对于宏,除了必须是标识符之外,是否有任何名称限制?例如,下面的内容是否有效?
#define assert getchar
#include <stdio.h>
int main(void)
{
assert();
}
代码 link: https://godbolt.org/z/ra63na.
main:
push rbp
mov rbp, rsp
mov eax, 0
call getchar
mov eax, 0
pop rbp
ret
预处理器是否了解C语言?或者它更像是一个查找和替换程序?
真的很蠢。它足以理解令牌替换,但仅此而已。
例如:#define test fail
将替换 test(...)
中的 test
,但不会替换 tested
或 "test"
。
因为 C 有一个非常基本的语法,所以编写一个可以处理和识别标记的解析器实际上并不难。让它理解 C 语法的全部超出了该工具的范围。
换句话说,对于像这样的输入程序:
#define test fail
int main() {
test(9, "test", tested());
return 0;
}
C 预处理器将其分解为标记,最终如下所示:
[ "#", "define", "test", "fail" ]
[ "int", "main", "(", ")", "{" ]
[ "test", "(", "9", "\"test\"", "tested", "(", ")", ")", ";" ]
...
其中每一个都使用简单的预处理器语法进行处理。
这稍微复杂一些,因为宏可以包含参数,但您明白了。使用的文法是整个C文法的一个简单子集。
是的,它是有效的。不,预处理器不支持语言。预处理器完全按照它所说的去做——包括内容,替换宏——如果这导致语法无效,编译器必须检测到。
除C符号命名规则外,没有C语言依赖项或保留字。所有预处理器指令都以 #
开头,这不是有效的 C 符号名称,因此不需要保留字。
预处理器可以是 运行 自己的 - 通过编译器驱动程序的命令行选项或在 GUN 工具链中它是一个独立的可执行文件 cpp
- 使其成为用于 C 和 C++ 源代码预处理以外的用途。
标准 (C11) 的第 7.1.2 节和第 7.1.3 节对此进行了介绍。以下是一些与宏有关的规则:
- 如果使用,header 应包含在任何外部声明或定义之外,并且应在第一次引用任何函数或它声明的 objects 之前首先包含它,或者它定义的任何类型或宏。
- 程序中不得有任何宏的名称在词法上与包含 header 之前定义的关键字相同,或者在扩展 header 中定义的任何宏时。
- 以下任何子条款中的每个宏名称(包括未来的库 directions) 如果包含任何相关的 headers 则保留用于指定用途; 除非另有明确说明。
- 具有以下任何子条款中列出的文件范围的每个标识符(包括 future library directions) 保留用作宏名称和标识符 如果包含任何关联的 header,则文件范围在同名 space 中。
所以您发布的确切程序是正确的,因为 <assert.h>
没有包括在内。但是,如果您确实包含 header.
For macros, are there any name limitations other than it needs to be an identifier?
是的,它们受语言规范第 7.1.3 节(“保留标识符”)的规定约束,特别是:
- All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use [including as macro names].
[...]
- Each macro name in any of the [standard library specification] subclauses (including the future library directions) is reserved for use as specified if any of its associated headers is included; unless explicitly stated otherwise
[...]
- Each identifier with file scope listed in any of the [standard library specification] subclauses (including the future library directions) is reserved for use as a macro name and as an identifier with file scope in the same name space if any of its associated headers is included.
[...] If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined.
如果第二个要点还包含 assert.h
header,那么第二个要点将与您的示例代码相关。然后标识符 assert
将保留用作宏名称。您将其用作一个会触发未定义的行为。这不会对实现提出任何特定要求——事实上,这正是“未定义行为”的含义。它不需要实现来接受代码,也不需要拒绝它,也不需要在任何一种情况下发出任何类型的诊断。如果它确实接受了它,那么预处理器就不需要在 assert
上执行宏替换,也不会被禁止这样做,事实上,它也不需要以任何方式出现方式理性或可预测。
如果您将 getchar
定义为包含 stdio.h
的代码中的宏名称,则基于第三个要点的情况类似,如示例所示。然而,实际显示的代码是可以的。
你也问,
And does the preprocessor have any knowledge of the C language? Or is it more like a find-and-replace program?
有一点。 C 预处理器不是一种 general-purpose 宏语言,并且尝试使用它时往往效果不佳。预处理器的输入是一系列标记,根据与 C 语法一致的规则确定,它使用与 C 相同的标识符语法。条件包含指令识别 C 的算术表达式的子集,并且它们根据宿主实现的整数数据类型之一工作。预处理器(或至少它之前的标记化阶段)理解 C 字符串文字和字符常量,因此宏替换不会影响这些内容。