C语言 # 在实践中定义?

bqujaahr  于 2023-03-28  发布在  其他
关注(0)|答案(8)|浏览(156)

我想知道#undef在C语言中的实际用途。我正在学习K&R,并且正在学习预处理器。其中大部分内容我(或多或少)都理解,但第90页(第二版)上的一些内容让我印象深刻:
#undef可以不定义名称,通常是为了确保例程实际上是一个函数,而不是宏:
#undef getchar
int getchar(void) { ... }
这是一个常见的做法,以防止有人#define-ing一个宏与您的函数同名?或者这真的是一个更大的样本,不会发生在现实中?(例如,任何人都不应该重写getchar(),所以它不应该出现。)使用自己的函数名,你觉得有必要这样做吗?如果你正在开发一个供他人使用的库,这种情况会改变吗?

u59ebvdq

u59ebvdq1#

它的作用

如果你读过普劳格的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()非常没有意义,但它说明了这一点。例如,您也可以对<ctype.h>中的isxxxx()宏做同样的事情。
通常情况下,你不想这样做-你通常不想删除宏定义。但是,当你需要真实的的函数时,你可以得到它。提供库的人可以模仿标准C库的功能,以达到良好的效果。

很少用到

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

int c = (getchar)();

因为getchar后面的标记不是(,所以它不是对类似函数的宏的调用,所以它必须是对函数的引用。类似地,上面的第一个例子,即使没有#undef也能正确编译和运行。
如果您使用宏重写来实现自己的函数,则可以使用它来获得良好的效果,尽管除非进行说明,否则可能会有点混乱。
一个二个一个一个
函数定义行不调用宏,因为function后面的标记不是(return行调用宏。

rta7y2nd

rta7y2nd2#

宏通常用于生成大量代码。它通常是一个非常本地化的用法,并且为了避免名称冲突,在特定头部的末尾#undef任何帮助宏都是安全的,因此只有实际生成的代码会被导入到其他地方,而用于生成代码的宏不会。
/Edit:作为一个例子,我已经用它来为我生成结构体。下面是一个实际项目的摘录:

#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
unftdfkk

unftdfkk3#

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

p1tboqfb

p1tboqfb4#

虽然我认为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它们。

fdx2calv

fdx2calv5#

这是一种常见的做法来防止某人#定义一个与你的函数同名的宏吗?或者这真的是一个在现实中不会发生的例子?(例如,没有人在他的正确,错误或疯狂的头脑中应该重写getchar(),所以它不应该出现。
好的代码不需要使用#undef,但是有很多坏的代码需要使用。当有人使用#define bool int这样的技巧时,#undef可以证明是非常宝贵的。

bvjveswy

bvjveswy6#

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

enxuqcxy

enxuqcxy7#

除了修复宏污染全局命名空间的问题外,#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"

/* ------------------------------------------------------ */
dz6r00yl

dz6r00yl8#

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

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

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

关于你更具体的问题:名称空间通常是emul;通过在库函数前面加上一个标识符来实现。
盲目地取消宏的定义会增加混乱,降低可维护性,并且可能会破坏依赖于原始行为的东西。如果你被强迫,至少使用push/pop来保留其他地方的原始行为。

相关问题