如果你只有一个标识符,#define 会做什么

What does #define do if you only have an identifer

通常#define 用于定义常量或宏。但是,按以下方式使用#define 是有效代码。

#define MAX // does this do anything?
#define MAX 10 // I know how to treat this.

因此,如果我使用#define MAX 10,我知道我的预处理器会将 MAX 的所有实例替换为 10。如果有人单独使用 #define MAX 但没有后续替换值,则它是有效的。这真的有什么作用吗?

我问的原因是我正在用 c++ 为 c 编写编译器,并且需要处理预处理器指令,但我无法确定发生这种情况时是否需要任何功能,或者是否一旦我的预处理完成,我就忽略它。

我的第一直觉是,这将在我的符号 table 中创建一个没有名为 MAX 的值的符号,但同样有可能它什么都不做。

作为补充问题,我知道这是一种不好的形式,但我真的很好奇。实际代码中是否存在会使用此类内容的情况?

谢谢, 宾克斯

一个典型的例子是header guards:

#ifndef MYHEADER
#define MYHEADER
...
#endif

您可以测试是否使用 #ifdef / ifndef.

定义了某些内容

它不会"do"任何东西,因为它不会向代码行添加任何东西

#define MAX

int x = 1 + 2; MAX // here MAX does nothing

但是空定义的作用是允许你有条件地做某些事情,比如

#ifdef DEBUG
  // do thing
#endif

同样header guards使用宏的存在来指示文件是否已经包含在翻译单元中。

#define 进行逐字符替换。如果您不提供任何值,则标识符将被替换为……无。现在这可能看起来很奇怪。我们经常使用它来创建一个标识符,其存在可以用 #ifdef#ifndef 来检查。最常见的用法是所谓的 "inclusion guards".

在您自己的预处理器实现中,我认为没有理由将其视为特例。行为与任何其他 #define 语句相同:

  1. 在符号table上添加一对symbol/value。

  2. 每当出现该符号时,将其替换为它的值。

对于没有值的符号,第 2 步很可能永远不会发生。但是,如果是,则该符号将被简单地删除,因为它的值为空。

C 预处理器 (CPP) 为使用 #define 宏定义的所有变量创建定义 table。当 CPP 通过代码时,它至少会用这些信息做两件事。

首先,它对定义的宏进行标记替换。

#define MAX(a,b)  (a > b) ? (a) : (b)
MAX(1,2); // becomes (1 > 2) ? (1) : (2);

其次,它允许使用其他预处理器宏搜索这些定义,例如 #ifdef#ifndef#undef#if defined(MACRO_NAME) 等 CPP 扩展。

这允许在值不重要但定义令牌这一事实很重要的情况下灵活地使用宏定义。

这允许像下面这样的代码:

// DEBUG is never defined, so this code would
// get excluded when it reaches the compiler.

#ifdef DEBUG
// ... debug printing statements
#endif

它创建一个具有空白定义的符号,稍后可以在其他预处理器操作中使用。它有一些用途:

1) 分支。

考虑以下因素:

#define ARBITRARY_SYMBOL

// ...

#ifdef ARBITRARY_SYMBOL
    someCode();
#else   /* ARBITRARY_SYMBOL */
    someOtherCode();
#endif  /* ARBITRARY_SYMBOL */

符号的存在可用于分支,根据情况选择性地选择合适的代码。一个很好的用途是处理 platform-specific 等效代码:

#if defined(_WIN32) || defined(_WIN64)
    windowsCode();
#elif defined(__unix__)
    unixCode();
#endif /* platform branching */

这也可以用来伪码出来,视情况而定。例如,如果你想要一个只在调试时存在的函数,你可能有这样的东西:

#ifdef DEBUG
    return_type function(parameter_list) {
        function_body;
    }
#endif  /* DEBUG */

1A) Header 守卫.

基于上述内容,header 守卫是一种虚拟化整个 header 的方法,如果它已经包含在跨越多个源文件的项目中。

#ifndef HEADER_GUARD
#define HEADER_GUARD

// Header...

#endif  /* HEADER_GUARD */

2) 模拟出一个符号。

与分支结合使用时,您还可以使用带有空白定义的定义来模拟符号。考虑以下因素:

#ifdef _WIN32
    #define STDCALL __stdcall
    #define CDECL __cdecl
    // etc.
#elif defined(__unix__)
    #define STDCALL
    #define CDECL
#endif  /* platform-specific */

// ...

void CDECL cdeclFunc(int, int, char, const std::string&, bool);
// Compiles as void __cdecl cdeclFunc(/* args */) on Windows.
// Compiles as void cdeclFunc(/* args */) on *nix.

这样做可以让您编写 platform-independent 代码,但能够在 Windows 平台上指定调用约定。 [请注意 header windef.h 执行此操作,将 CDECLPASCALWINAPI 定义为不支持它们的平台上的空白符号。]也可以在其他情况下使用,只要您需要预处理器符号仅在特定条件下扩展为其他内容。

3) 文档。

空白宏也可用于记录代码,因为预处理器可以将它们去除。微软很喜欢这种方法,在 windef.h 中将其用于 INOUT 符号,这些符号经常出现在 Windows 函数原型中。

可能还有其他用途,但这些是我能想到的唯一用途。