我想知道#undef在C语言中的实际用途。我正在学习K&R,并且正在学习预处理器。其中大部分内容我(或多或少)都理解,但第90页(第二版)上的一些内容让我印象深刻:#undef
可以不定义名称,通常是为了确保例程实际上是一个函数,而不是宏:#undef getchar
int getchar(void) { ... }
这是一个常见的做法,以防止有人#define
-ing一个宏与您的函数同名?或者这真的是一个更大的样本,不会发生在现实中?(例如,任何人都不应该重写getchar()
,所以它不应该出现。)使用自己的函数名,你觉得有必要这样做吗?如果你正在开发一个供他人使用的库,这种情况会改变吗?
8条答案
按热度按时间u59ebvdq1#
它的作用
如果你读过普劳格的The Standard C Library(1992),您将看到允许
<stdio.h>
头文件提供getchar()
和getc()
作为类似函数的宏(具有getc()
多次评估其文件指针参数的特殊权限!)。然而,即使它提供了宏,实现也有义务提供执行相同工作的实际函数,主要是为了访问一个名为getchar()
或getc()
的函数指针,并将其传递给其他函数。也就是说,通过做:
正如所写的那样,
core_function()
非常没有意义,但它说明了这一点。例如,您也可以对<ctype.h>
中的isxxxx()
宏做同样的事情。通常情况下,你不想这样做-你通常不想删除宏定义。但是,当你需要真实的的函数时,你可以得到它。提供库的人可以模仿标准C库的功能,以达到良好的效果。
很少用到
还要注意,很少需要使用显式
#undef
的原因之一是因为您可以通过编写以下代码来调用函数而不是宏:因为
getchar
后面的标记不是(
,所以它不是对类似函数的宏的调用,所以它必须是对函数的引用。类似地,上面的第一个例子,即使没有#undef
也能正确编译和运行。如果您使用宏重写来实现自己的函数,则可以使用它来获得良好的效果,尽管除非进行说明,否则可能会有点混乱。
一个二个一个一个
函数定义行不调用宏,因为
function
后面的标记不是(
。return
行调用宏。rta7y2nd2#
宏通常用于生成大量代码。它通常是一个非常本地化的用法,并且为了避免名称冲突,在特定头部的末尾
#undef
任何帮助宏都是安全的,因此只有实际生成的代码会被导入到其他地方,而用于生成代码的宏不会。/Edit:作为一个例子,我已经用它来为我生成结构体。下面是一个实际项目的摘录:
unftdfkk3#
因为预处理器
#define
都在一个全局命名空间中,所以很容易导致命名空间冲突,特别是在使用第三方库时。例如,如果您想创建一个名为OpenFile
的函数,它可能无法正确编译,因为头文件<windows.h>
定义了要Map到OpenFileA
或OpenFileW
的令牌OpenFile
(取决于是否定义了UNICODE
)。正确的解决方案是在定义函数之前先定义#undef
OpenFile
。p1tboqfb4#
虽然我认为Jonathan Leffler给了你正确的答案。这里有一个非常罕见的例子,我使用了一个#undef。通常一个宏应该在许多函数中可重用;这就是为什么你要在文件的顶部或头文件中定义它。但是有时候你在一个函数中有一些重复的代码,这些代码可以用宏来缩短。
为了向读者展示这个宏只在函数内部有用,它在最后是未定义的。我不想鼓励任何人使用这样的黑客宏。但是如果你必须这样做,在最后#undef它们。
fdx2calv5#
这是一种常见的做法来防止某人#定义一个与你的函数同名的宏吗?或者这真的是一个在现实中不会发生的例子?(例如,没有人在他的正确,错误或疯狂的头脑中应该重写getchar(),所以它不应该出现。
好的代码不需要使用
#undef
,但是有很多坏的代码需要使用。当有人使用#define bool int
这样的技巧时,#undef
可以证明是非常宝贵的。bvjveswy6#
我只在
#included
文件中的宏干扰我的函数时使用它(例如,它具有相同的名称)。然后我#undef
宏,以便我可以使用自己的函数。enxuqcxy7#
除了修复宏污染全局命名空间的问题外,
#undef
的另一个用途是宏可能需要在不同的地方具有不同的行为。这不是一个真正常见的场景,但想到的两个是:assert
宏可以在编译单元的中间更改其定义。除了需要对assert
本身进行#undef
处理以执行此操作之外,还需要重新定义NDEBUG
宏以重新配置assert
的所需行为extern
来确保全局变量只定义一次,但是对于使用头/声明来定义变量的单一情况,宏将被重新定义为空。类似于(我不是说这一定是一个好的技术,只是我在野外看到的一个):
dz6r00yl8#
如果一个宏可以定义,那么必须有一个工具来定义。
我使用的一个内存跟踪器定义了它自己的new/delete宏来跟踪文件/行信息。这个宏破坏了SC++L。
关于你更具体的问题:名称空间通常是emul;通过在库函数前面加上一个标识符来实现。
盲目地取消宏的定义会增加混乱,降低可维护性,并且可能会破坏依赖于原始行为的东西。如果你被强迫,至少使用push/pop来保留其他地方的原始行为。