我使用 C 语言已经有一年多了,我想知道它到底是如何#define
工作的。我知道你可以将它用作宏,例如#define MUL(x, y) (x*y)
,但是当你定义某些东西而不给出定义时会发生什么?例如
#ifndef _SYNNOVESLIB_H
#define _SYNNOVESLIB_H
#endif
它是否只是被 GCC 或正在使用的任何编译器标记为“已定义”或为真(如 1)?
14
最佳答案
3
#define _SYNNOVESLIB_H
告诉编译器用空字符替换。对于检查宏是否已定义(例如、和)_SYNNOVESLIB_H
的情况,该值将为真。defined
#ifdef
#ifndef
它还可以方便地有条件地定义某些功能以实现兼容性。。
6.10 预处理指令提供了的语法#define
。
control-line:
# define identifier replacement-list new-line
replacement-list:
pp-tokens(opt)
pp-tokens:
preprocessing-token
pp-tokens preprocessing-token
replacement-list
是预处理器标记的可选列表,因此它可以为空。
从6.10.1 条件包含
- 控制条件包含的表达式应为整数常量表达式,但以下情况除外:标识符(包括词汇上与关键字相同的标识符)按如下所述进行解释;169)并且它可能包含以下形式的一元运算符表达式
defined identifier
或者
defined ( identifier )
如果标识符当前被定义为宏名(即,如果它是预定义的,或者如果它是 #define 预处理指令的主题,而中间没有具有相同主题标识符的 #undef 指令),则计算结果为 1;如果不是,则计算结果为 0。
请注意,值并不重要,只是它已被定义。
- 表单的预处理指令
# ifdef identifier new-line groupopt
或者
# ifndef identifier new-line groupopt
检查标识符当前是否定义为宏名。它们的条件分别相当于
#if defined identifier
和#if !defined identifier
。
编译器内部如何处理这个问题取决于编译器,但是一个简单的哈希表就可以了。#define _SYNNOVESLIB_H
会添加一个_SYNNOVESLIB_H
值为 null 或空的键。#ifndef _SYNNOVESLIB_H
会检查哈希表中是否存在该键,忽略该值。#undef _SYNNOVESLIB_H
从表中删除该键。
6
-
这个答案可以理解为,使用空替换列表定义的宏的唯一用途是测试其定义性。事实并非如此。事实上,能够测试宏定义性与宏替换列表的内容几乎是正交的。
– -
@JohnBollinger 这不是暗示,这只是问题的范围。
– -
在我看来,这不是问题的范围。这只是其中给出的特定示例的性质。“当您定义某些东西而不给出定义时会发生什么?[…] 它只是被标记为‘已定义’[…]? ”——不。它被定义为具有指定替换列表的宏,而该列表恰好为空。
– -
@JohnBollinger 您似乎想要讨论“空列表”和“无值”之间的细微区别。从技术上讲您是正确的,这是最好的正确,但这不是问题所在。我现在明白了,这意味着定义性是唯一的用例,我会软化这一点。
– -
不是。我不在乎您说的是“定义为无”还是“定义为空列表”。我关心的是您错误地说“无定义”。最终,重要的一点是,具有空/无替换列表的宏定义不是特殊情况,这与 OP 的想法相反。
–
|
精确的编译器源代码就很棒了。
该#define
宏在 libcpp/directives.cc 中处理,位于附近。该定义保存在中。内部宏存储在哈希表中,但尚未发现它发生在哪里。
|
当你定义某事物但没有给出定义时会发生什么?
每个有效#define
指令都将宏名称与相应的替换列表(用您的术语来说,是“定义”)相关联。允许使用空的替换列表,就预处理器而言,这没有什么特别的。您不应将这种情况视为“没有给出定义”,因为这会误导您——例如,让您认为存在某种特殊行为,或者可能认为您无法像扩展其他类型的替换列表那样扩展此类宏。此外,因为将特殊类别的宏区分为没有定义会使您对该语言的心理模型变得比它需要的更复杂。
它是否只是被 GCC 或正在使用的任何编译器标记为“已定义”或为真(如 1)?
它被定义为一个空的替换列表,这是完全有效的,没有特殊意义。结果之一是预处理器#ifdef
指令和defined
运算符将识别出该宏已被定义,就像它们识别任何其他已定义的宏一样。与任何其他宏一样,它可以被预处理器扩展,预处理器用其(空)替换列表替换宏名。
尽管带有空替换列表的宏经常专门用于定义性测试目的,例如在您的包含保护示例中,但它们在实践中并不局限于此。有时您希望符号扩展为零,至少在某些情况下是这样。
例如,为了避免对同一变量进行多次定义,,但需要有一个没有(显式)声明的源extern
。有时实现这一点的一种方法是这样的(尽管我实际上并不推荐它):
标头
#ifndef EXTERN
#define EXTERN extern
#endif
// The following appearance of EXTERN will be expanded by the preprocessor:
EXTERN int my_variable;
most_source_文件.c
#include "header.h"
// gets:
// extern int my_variable;
一个特殊源文件.c
#define EXTERN
#include "header.h"
#undef EXTERN
// gets:
// int my_variable;
这样,相同的标题extern
通常会提供明确的声明,这是大多数地方所需要的,但extern
在一个选定的地方提供相应的非明确声明,EXTERN
定义为扩展为无。
5
-
您强调了空列表和无列表之间的细微区别,但不清楚这如何回答这个问题。您多次提到空定义有其他用途,但没有说明。您能否提供一些例子,说明空列表和无列表之间的区别很重要?
–
-
不,@Schwern,我强调的是,OP 所询问的宏定义不是特殊情况。看到粗体斜体文本了吗?它确实为符号提供了定义,无论你想用什么词来表示,这与 OP 似乎假设的相反。所有其他语义都源于此。至于除了测试定义性之外的其他用途的例子,当然,我可以提供一些,但你不是在其他地方坚持严格关注问题范围的人吗?
–
-
楼主是 C 语言的新手,我怀疑他们可能不太明白这段对话。你是专家,你强调了这种区别。我认为它在某种程度上很重要,但我看不出来。为了我自己的教育,你能举个例子来说明这种区别很重要吗?
–
-
@Schwern,我不会低估原帖作者,但鉴于你诉诸于他们对语言的不成熟,我认为“这不是特殊情况”比“这种情况有特殊规则”的认知负担要小,前者可以避免让他们产生混淆,因为他们后来发现他们询问的宏并非严格用作标志。即使忽略其中的第二个,我也不明白你为什么喜欢特殊情况描述。无论如何,我提供了一个我认为相当容易理解的例子。
–
-
谢谢你举的例子,你的回答好多了。我想我明白你所做的区分:“EXTERN 被定义为一个空列表 [什么?],它会扩展为空”。而“EXTERN 被定义为空,并且不会扩展为空”则意味着一种特殊情况。但这需要知道它是一个标记列表(并且“空”和“空列表”是不同的),这需要了解语法。如果你已经知道这一点,那么你可能不是听众。“空会扩展为空”是自然的假设。这是一种慢慢引入复杂性的教学技巧,而不是一下子引入。
–
|
–
–
–
#define NAME x
。在这种情况下,x
是一个空字符串,因此当您使用宏时,它会被替换为一个空字符串。–
–
|