哪些用例需要 #define 没有令牌字符串

What use cases necessitate #define without a token-string?

本文关键字:令牌 字符串 #define      更新时间:2023-10-16

我之前在学习C时遇到过#define预处理器指令,然后在我读的一些代码中也遇到了它。但是,除了使用它来明确替换常量和定义宏之外,我还没有真正承担过在没有"主体"或标记字符串的情况下使用它的特殊情况。

以这一行为例:

#define OCSTR(X)

就这样!这个或更好的用是什么,什么时候需要这种使用#define

在两种情况下使用。 第一个也是最常见的涉及条件编译:

#ifndef XYZ
#define XYZ
//  ...
#endif

你自己肯定用过这个包括警卫,但它也可以用于系统依赖关系等内容:

#ifdef WIN32
//  Windows specific code here...
#endif

(在这种情况下,WIN32 更有可能在命令行上定义,但它也可以在"config.hpp"文件中定义。这通常会仅涉及类似对象的宏(没有参数列表或括号(。

第二个是有条件编译的结果。 东西喜欢:

#ifdef DEBUG
#define TEST(X) text(X)
#else
#define TEST(X)
#endif

这允许编写以下内容:

TEST(X);

如果定义了DEBUG,它将调用该函数,如果它不是。

这样的宏通常成对出现在条件#ifdef中,如下所示:

#ifdef _DEBUG
   #define OCSTR(X)
#else
   #define OCSTR(X)  SOME_TOKENS_HERE
#endif

再比如,

#ifdef __cplusplus
   #define NAMESPACE_BEGIN(X) namespace X {
   #define NAMESPACE_END }
#else
   #define NAMESPACE_BEGIN(X) 
   #define NAMESPACE_END
#endif

我最近挖出的一个奇怪的案例来回答一个问题,结果证明它本质上只是评论。 有问题的代码如下所示:

void CLASS functionName(){
  //
  //
  //
}

我发现这只是一个空#define,作者选择记录该函数访问项目中的全局变量:

C++ 语法:void CLASS functionName((?

所以和它说/* CLASS */并没有太大区别,除了不允许像/* CLAAS */这样的错别字......也许还有其他一些小好处(?

我同意每一个答案,但我想指出一件微不足道的小事。

作为一个C纯粹主义者,我从小就断言每个#define都应该是一个表达,所以,即使这是通常的做法:

#define WHATEVER

并进行测试

#ifdef WHATEVER

我认为写起来总是更好的:

#define WHATEVER (1)

此外#debug宏应为表达式:

#define DEBUG (xxx) (whatever you want for debugging, value)

通过这种方式,您可以完全避免滥用#macros并防止出现令人讨厌的问题(尤其是在 1000 万行 C 项目中(

当您可能想要使某些函数静音时,可以使用此功能。例如,在调试模式下,您希望打印一些调试语句,而在生产代码中,您希望省略它们:

#ifdef DEBUG
#define PRINT(X) printf("%s", X)
#else
#define PRINT(X)  // <----- silently removed
#endif

用法:

void foo ()
{
  PRINT("foo() startsn");
  ...
}

#define宏在预处理期间只是简单地替换为它们的替换文本。如果没有替换文本,则...他们被什么都没有取代!所以这个源代码:

#define FOO(x)
print(FOO(hello world));

将被预处理成这样:

print();

这对于摆脱您不想要的东西很有用,例如,assert() .它主要在条件情况下有用,但在某些情况下,有一个非空的身体。

正如您在上面的响应中看到的那样,它在调试代码时很有用。

#ifdef DEBUG
#define debug(msg) fputs(__FILE__ ":" (__LINE__) " - " msg, stderr)
#else
#define debug(msg)
#endif

因此,当您调试时,该函数将打印行号和文件名,以便您知道是否存在错误。如果你不调试,它只会产生没有输出

这样的事情有很多用途。

例如,一种是宏在不同的版本中具有不同的行为。例如,如果你想要调试消息,你可以有这样的东西:

#ifdef _DEBUG
  #define DEBUG_LOG(X, ...) however_you_want_to_print_it
#else
  #define DEBUG_LOG(X, ...) // nothing
#endif

另一个用途可能是根据您的系统自定义头文件。这是来自我在 Linux 中实现的 OpenGL 标头:

#if !defined(OPENSTEP) && (defined(__WIN32__) && !defined(__CYGWIN__))
#  if defined(__MINGW32__) && defined(GL_NO_STDCALL) || defined(UNDER_CE)  /* The generated DLLs by MingW with STDCALL are not compatible with the ones done by Microsoft's compilers */
#    define GLAPIENTRY 
#  else
#    define GLAPIENTRY __stdcall
#  endif
#elif defined(__CYGWIN__) && defined(USE_OPENGL32) /* use native windows opengl32 */
#  define GLAPIENTRY __stdcall
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303
#  define GLAPIENTRY
#endif /* WIN32 && !CYGWIN */
#ifndef GLAPIENTRY
#define GLAPIENTRY
#endif

并用于标头声明,例如:

GLAPI void GLAPIENTRY glClearIndex( GLfloat c );
GLAPI void GLAPIENTRY glClearColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha );
GLAPI void GLAPIENTRY glClear( GLbitfield mask );
...

(我删除了GLAPI部分(

所以你明白了,在某些情况下使用而在其他情况下不使用的宏可以定义为这些情况下的某些内容,而在其他情况下则不定义

任何内容。

其他情况可能如下:

如果宏不采用参数,则可能只是声明某些情况。一个著名的示例是保护头文件。另一个例子是这样的

#define USING_SOME_LIB

以后可以这样用:

#ifdef USING_SOME_LIB
...
#else
...
#endif
可能是宏在

某个阶段被用来做某事(例如日志(,但在发布时,所有者认为日志不再有用,只是删除了宏的内容,使其变为空。但是不建议这样做,请使用我在答案开头提到的方法。

最后,它可能只是为了更多的解释,例如你可以说

#define DONT_CALL_IF_LIB_NOT_INITIALIZED

你写的函数如下:

void init(void);
void do_something(int x) DONT_CALL_IF_LIB_NOT_INITIALIZED;

虽然最后一种情况有点荒谬,但在这种情况下是有道理的:

#define IN
#define OUT
void function(IN char *a, OUT char *b);