什么情况下需要使用 #define 而不需要添加标记字符串?

6

在学习C语言时,我遇到过预处理器指令 #define,后来在阅读一些代码时也遇到了它。但除了用它来定义常量的替换和宏的定义之外,我并不真正理解它在没有"body"或令牌字符串的特殊情况下使用的意义。

例如,考虑这行代码:

#define OCSTR(X)

就是这样!那么这有什么用,或者更好的说,何时需要使用#define呢?


这可以与另一组允许条件编译的指令结合使用。没有标记字符串的 #define 会从源文件中删除标识符的出现。该标识符仍然被定义,并且可以通过使用 #if defined 和 #ifdef 指令进行测试。 - AminM
8个回答

8

这个用法有两种情况。第一种也是最常见的情况涉及条件编译:

#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,则会调用该函数,否则不会执行任何操作。

接受这个答案并不是因为其他答案不正确,而是因为它对正确答案进行了更多的澄清。谢谢。 - JWL

3

这种宏通常成对出现,在条件#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

2

最近我挖掘出一个奇怪的案例来回答一个问题,结果发现它只是一种评论性质的内容。这个问题中的代码看起来像:

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

我发现它只是一个空的#define,作者选择记录函数访问项目中全局变量的方式:C++语法:void CLASS functionName()?。因此,与/* CLASS */没有太大区别,除了不允许拼写错误,如/* CLAAS */...或许还有其他一些小的好处?

1

我同意每一个答案,但我想指出一件小事。

作为一个C语言纯粹主义者,我一直坚信每一个#define都应该是一个表达式,所以,即使使用以下常见做法:

#define WHATEVER

并用它进行测试

#ifdef WHATEVER

我认为写作时最好的方式是:

#define WHATEVER (1)

还有#debug宏应该是表达式:

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

通过这种方式,您可以完全避免 #宏 的误用,并防止出现不良问题(特别是在一个拥有1000万行C代码的项目中)。


这个#if#ifdef的辩论可能会引起您的兴趣,个人而言我更倾向于使用#if。https://dev59.com/MnVC5IYBdhLWcg3w9GLM - HostileFork says dont trust SE
想到了使用#if 0来注释掉整块代码的有效方法:D。顺便说一下,这场辩论很有意思。像往常一样,没有正确或错误的做法,取决于你在做什么。我会尽快阅读它。谢谢。 - BigMike
在我的情况下,当我使用#define TEST而没有表达式时,在clang中,#elif TEST宏会抛出一个错误,说“错误:表达式中需要值”。 - rosshjb

0

#define宏在预处理期间被简单地替换为其替换文本。如果没有替换文本,那么它们将被替换为空!因此,这段源代码:

#define FOO(x)

print(FOO(hello world));

将被预处理成这样:

print();

这可以用于摆脱你不想要的东西,比如说assert()。它主要在条件情况下有用,在某些条件下有非空的主体。


0

正如您在上面的回复中所看到的,它在调试代码时非常有用。

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

所以,在调试时,该函数将打印行号和文件名,以便您知道是否存在错误。如果您不进行调试,则不会产生任何输出。


0

这样的东西有很多用途。

例如,其中一个用途是让宏在不同的构建中具有不同的行为。例如,如果您想要调试消息,可以像这样:

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

另一个用途可能是根据您的系统自定义头文件。这是来自我在Linux中实现的Mesa 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);

0

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

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

使用方法:

void foo ()
{
  PRINT("foo() starts\n");
  ...
}

你的回答(以及许多其他回答)是正确的,但你的示例很危险。如果在 PRINT(X) 之前存在一个没有括号 { } 包围的 if(),会怎样呢?对读者来说:请注意,这只是一个简单的示例,不能直接使用。 - Offirmo
这就是为什么在C/C++中每个宏都应该是一个表达式,以避免这种情况的原因。谢谢。 - BigMike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接