#undefing in Practice?

时间:2008-10-19 19:47:05

标签: c c-preprocessor

我想知道#undef在C中的实际应用。我正在通过K& R工作,并且要接受预处理器。其中大部分内容都是我(或多或少)理解的内容,但第90页(第二版)的某些内容突然出现在我身上:

  

#undef可能未定义名称,   通常是为了确保例程   真的是一个功能,而不是一个宏:

     

#undef getchar

     

int getchar(void) { ... }

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

8 个答案:

答案 0 :(得分:15)

它做什么

如果您阅读Plauger的The Standard C Library(1992),您会看到允许<stdio.h>标题提供getchar()getc()作为类似函数的宏(具有特殊功能) getc()多次评估其文件指针参数的权限!)。但是,即使它提供了宏,实现也必须提供执行相同工作的实际函数,主要是为了使您可以访问名为getchar()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行会调用宏。

答案 1 :(得分: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

答案 2 :(得分:6)

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

答案 3 :(得分:2)

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

答案 4 :(得分: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;
}

为了向读者显示此宏仅在函数内部有用,最后它是未定义的。我不想鼓励任何人使用这种hackish宏。但如果你必须,最后#undef他们。

答案 5 :(得分:1)

  

这是一种常见的做法来防范某人#defineing一个与你的功能同名的宏吗?或者这真的是一个不会在现实中发生的样本? (EG,没有人在他的权利,错误或疯狂的头脑中应该重写getchar(),所以它不应该出现。)

两者都有。好的代码不需要使用#undef,但是你必须使用很多不好的代码。当有人像#undef那样采取行动时,#define bool int可以证明是无价的。

答案 6 :(得分:1)

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

  • assert宏可以在编译单元的中间更改它的定义,以便您可能希望对代码的某些部分执行调试而不对其他部分执行调试。除assert本身需要#undef执行此操作外,还需要重新定义NDEBUG宏以重新配置assert

    <的所需行为/ LI>
  • 我见过一种技术,用于通过使用宏将变量声明为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"

/* ------------------------------------------------------ */

答案 7 :(得分:0)

如果可以定义宏,则必须有设施来取消。

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

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

关于更具体的问题:命名空间通常是emul;在C中通过为库函数添加标识符前缀。

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