17

我想知道#undef 在 C 中的实际用途。我正在研究 K&R,并且正在处理预处理器。其中大部分是我(或多或少)理解的材料,但第 90 页(第二版)的内容让我印象深刻:

名称可以用 未定义#undef,通常是为了确保例程确实是函数,而不是宏:

#undef getchar

int getchar(void) { ... }

这是防止某人#define使用与您的函数同名的宏的常见做法吗?或者这真的更像是一个在现实中不会发生的样本?(EG,没有人在他的正确,错误或疯狂的头脑中应该重写getchar(),所以不应该出现。)有了你自己的函数名,你觉得有必要这样做吗?如果您正在开发一个供其他人使用的库,这种情况会改变吗?

4

8 回答 8

16

它能做什么

如果您阅读 Plauger 的The Standard C Library (1992),您将看到<stdio.h>头文件被允许提供getchar()getc()作为类似函数的宏(具有getc()多次评估其文件指针参数的特殊权限!)。然而,即使它提供了宏,实现也必须提供执行相同工作的实际函数,主要是为了让您可以访问一个名为getchar()or的函数指针getc()并将其传递给其他函数。

也就是说,通过这样做:

#include <stdio.h>
#undef getchar

extern int some_function(int (*)(void));

int core_function(void)
{
   int c = some_function(getchar);
   return(c);
}

正如所写,这core_function()是毫无意义的,但它说明了这一点。isxxxx()例如,您也可以对其中的宏执行相同的操作<ctype.h>

通常,您不想这样做 - 您通常不想删除宏定义。但是,当您需要真正的功能时,您可以掌握它。提供库的人可以模拟标准C库的功能,效果很好。

很少需要

另请注意,您很少需要使用显式的原因之一#undef是您可以通过编写以下代码来调用函数而不是宏:

int c = (getchar)();

因为后面的记号getchar不是(,它不是类函数宏的调用,所以它一定是函数的引用。同样,上面的第一个示例即使没有#undef.

如果您使用宏覆盖实现自己的函数,则可以使用它来获得良好的效果,尽管除非解释,否则可能会有些混乱。

/* 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);
}

函数定义行不调用宏,因为后面的记号function不是(。该return行确实调用了宏。

于 2008-10-20T00:35:39.233 回答
14

宏通常用于生成大量代码。它通常是一种非常本地化的用法,并且#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
于 2008-10-19T20:27:26.503 回答
6

因为预处理器#define都在一个全局命名空间中,所以很容易导致命名空间冲突,尤其是在使用第三方库时。例如,如果您想创建一个名为 的函数OpenFile,它可能无法正确编译,因为头文件定义了要映射到或<windows.h>的标记(取决于是否定义了)。正确的解决方案是在定义你的函数之前。OpenFileOpenFileAOpenFileWUNICODE#undef OpenFile

于 2008-10-19T20:03:28.190 回答
2

我只在文件中的宏#included干扰我的一个函数时使用它(例如,它具有相同的名称)。然后我#undef宏,所以我可以使用我自己的功能。

于 2008-10-19T19:50:44.597 回答
2

虽然我认为 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;
}

为了向读者展示这个宏只在函数内部有用,最后它是未定义的。我不想鼓励任何人使用这种骇人听闻的宏。但是,如果必须,请在最后 #undef 它们。

于 2008-10-20T10:19:27.650 回答
1

这是防御某人#define-ing 与您的函数同名的宏的常见做法吗?或者这真的更像是一个在现实中不会发生的样本?(EG,没有人在他的正确、错误或疯狂的头脑中应该重写 getchar(),所以它不应该出现。)

两者都有一点。好的代码不需要使用#undef,但是有很多不好的代码你必须使用。#undef当有人使用像#define bool int.

于 2008-10-19T20:00:41.050 回答
1

除了修复宏污染全局命名空间的问题外,另一个用途#undef是宏可能需要在不同位置具有不同行为的情况。这不是一个真正常见的场景,但我想到的几个是:

  • assert如果您可能希望对代码的某些部分而不是其他部分执行调试,则宏可以在编译单元的中间更改其定义。除了assert自身需要被#undef'ed 来执行此操作外,还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"

/* ------------------------------------------------------ */
于 2008-10-19T20:25:11.157 回答
0

如果一个宏可以被定义,那么必须有一个工具来取消定义。

我使用的内存跟踪器定义了自己的新/删除宏来跟踪文件/行信息。这个宏破坏了 SC++L。

#pragma push_macro( "new" )
#undef new
#include <vector>
#pragma pop_macro( "new" )

关于您更具体的问题:命名空间通常在 C 中通过在库函数前面加上标识符来模拟。

盲目地取消定义宏会增加混乱,降低可维护性,并可能破坏依赖原始行为的东西。如果你被迫,至少使用 push/pop 来保留其他地方的原始行为。

于 2008-10-20T00:49:09.270 回答