题
我想知道 #undef 在 C 中的实际用途。我正在通过 K&R 进行工作,并由预处理器负责。其中大部分内容是我(或多或少)理解的,但第 90 页(第二版)上的一些内容让我印象深刻:
名称可能未定义
#undef
,通常是为了确保例程确实是一个函数,而不是宏:
#undef getchar
int getchar(void) { ... }
这是防御某人的常见做法吗 #define
- 一个与你的函数同名的宏?或者这实际上更像是现实中不会发生的样本?(EG,任何人在他正确、错误或疯狂的头脑中都不应该重写 getchar()
, ,所以它不应该出现。)使用您自己的函数名称,您觉得有必要这样做吗?如果您正在开发供其他人使用的库,这种情况会改变吗?
解决方案
它做什么
如果您阅读Plauger的标准C库(1992),您会看到允许<stdio.h>
标题提供getchar()
和getc()
作为类函数的宏(具有core_function()
特殊权限,以便多次评估其文件指针参数!)。但是,即使它提供了宏,实现也必须提供执行相同工作的实际函数,主要是为了使您可以访问名为isxxxx()
或<ctype.h>
的函数指针并将其传递给其他函数。
即通过:
#include <stdio.h>
#undef getchar
extern int some_function(int (*)(void));
int core_function(void)
{
int c = some_function(getchar);
return(c);
}
正如所写,#undef
毫无意义,但它说明了这一点。例如,您也可以使用getchar
中的(
宏执行相同的操作。
通常,您不希望这样做 - 您通常不想删除宏定义。但是,当你需要真正的功能时,你可以掌握它。提供库的人可以模拟标准C库的功能,以达到良好的效果。
很少需要
另请注意,您很少需要使用显式function
的原因之一是因为您可以通过编写来调用函数而不是宏:
int c = (getchar)();
因为return
之后的标记不是<=>,它不是类函数宏的调用,所以它必须是对函数的引用。同样,即使没有<=>,上面的第一个例子也会正确编译和运行。
如果使用宏覆盖实现自己的功能,可以使用此功能,但除非另有说明,否则可能会有些混乱。
/* function.h */
…
extern int function(int c);
extern int other_function(int c, FILE *fp);
#define function(c) other_function(c, stdout);
…
/* function.c */
…
/* Provide function despite macro override */
int (function)(int c)
{
return function(c, stdout);
}
函数定义行不会调用宏,因为<=>之后的标记不是<=>。 <=>行确实调用了宏。
其他提示
宏通常用于生成大量代码。它通常是一个非常本地化的用法,并且在特定标题的末尾#undef
任何帮助器宏都是安全的,以避免名称冲突,因此只有实际生成的代码才会被导入其他地方,而用于生成代码的宏则不会。
/编辑:举个例子,我用它来为我生成结构。以下是实际项目的摘录:
#define MYLIB_MAKE_PC_PROVIDER(name) \
struct PcApi##name { \
many members …
};
MYLIB_MAKE_PC_PROVIDER(SA)
MYLIB_MAKE_PC_PROVIDER(SSA)
MYLIB_MAKE_PC_PROVIDER(AF)
#undef MYLIB_MAKE_PC_PROVIDER
由于预处理器#define
s都在一个全局命名空间中,因此很容易产生命名空间冲突,尤其是在使用第三方库时。例如,如果要创建名为OpenFile
的函数,则可能无法正确编译,因为头文件<windows.h>
定义了要映射到OpenFileA
或OpenFileW
的标记UNICODE
(取决于是否< =>是否已定义)。在定义函数之前,正确的解决方案是#undef
<=>。
我只在#included
文件中的宏干扰我的某个功能时使用它(例如,它具有相同的名称)。然后我#undef
宏,所以我可以使用自己的功能。
虽然我认为Jonathan Leffler给了你正确的答案。这是一个非常罕见的情况,我使用#undef。通常,宏应该可以在许多函数中重用;这就是你在文件顶部或头文件中定义它的原因。但有时你在函数中有一些重复的代码,可以用宏缩短。
int foo(int x, int y)
{
#define OUT_OF_RANGE(v, vlower, vupper) \
if (v < vlower) {v = vlower; goto EXIT;} \
else if (v > vupper) {v = vupper; goto EXIT;}
/* do some calcs */
x += (x + y)/2;
OUT_OF_RANGE(x, 0, 100);
y += (x - y)/2;
OUT_OF_RANGE(y, -10, 50);
/* do some more calcs and range checks*/
...
EXIT:
/* undefine OUT_OF_RANGE, because we don't need it anymore */
#undef OUT_OF_RANGE
...
return x;
}
为了向读者显示此宏仅在函数内部有用,最后它是未定义的。我不想鼓励任何人使用这种hackish宏。但如果你必须,最后#undef他们。
这是一种常见的做法来防范某人#defineing一个与你的功能同名的宏吗?或者这真的是一个不会在现实中发生的样本? (EG,没有人在他的权利,错误或疯狂的头脑中应该重写getchar(),所以它不应该出现。)
两者都有。好的代码不需要使用#undef
,但是你必须使用很多不好的代码。当有人像#define bool int
那样拉扯伎俩时,<=>可以证明是非常宝贵的。
除了修复宏污染全局命名空间的问题之外,另一个用途是 #undef
在这种情况下,宏可能需要在不同的地方有不同的行为。这并不是一个真正常见的场景,但我想到的几个场景是:
这
assert
如果您可能希望对代码的某些部分而不是其他部分执行调试,则可以在编译单元的中间更改宏的定义。此外assert
本身需要成为#undef
想要做到这一点,NDEBUG
需要重新定义宏以重新配置所需的行为assert
我见过一种技术,用于通过使用宏将变量声明为来确保全局变量仅定义一次
extern
, ,但是对于使用标头/声明来定义变量的单一情况,宏将被重新定义为空。
类似的东西(我并不是说这一定是一种好技术,只是我在野外见过的一种技术):
/* globals.h */
/* ------------------------------------------------------ */
#undef GLOBAL
#ifdef DEFINE_GLOBALS
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL int g_x;
GLOBAL char* g_name;
/* ------------------------------------------------------ */
/* globals.c */
/* ------------------------------------------------------ */
#include "some_master_header_that_happens_to_include_globals.h"
/* define the globals here (and only here) using globals.h */
#define DEFINE_GLOBALS
#include "globals.h"
/* ------------------------------------------------------ */
如果可以定义宏,则必须有设施来取消。
我使用的内存跟踪器定义了自己的新/删除宏来跟踪文件/行信息。这个宏打破了SC ++ L。
#pragma push_macro( "new" )
#undef new
#include <vector>
#pragma pop_macro( "new" )
关于更具体的问题:命名空间通常是emul;在C中通过为库函数添加标识符前缀。
盲目取消宏会增加混乱,降低可维护性,并可能破坏依赖于原始行为的事物。如果你被迫,至少使用push / pop来保持其他地方的原始行为。