【问题标题】:do { ... } while (0) — what is it good for? [duplicate]do { ... } while (0) — 它有什么用? [复制]
【发布时间】:2010-09-20 10:07:20
【问题描述】:

可能重复:
Why are there sometimes meaningless do/while and if/else statements in C/C++ macros?

我已经看到这种表达方式 10 多年了。我一直在想它有什么好处。由于我主要在#defines 中看到它,我认为它适用于内部范围变量声明和使用中断(而不是 goto)。

它对其他东西有好处吗?你会用吗?

【问题讨论】:

  • 看看this question
  • 实际上,它不是重复的,因为链接的 q/a 没有具体定义。比较两个答案很容易说明它不是重复的。
  • 参见 Redis 第 53 行的“decrement_used_memory” [link]github.com/antirez/redis-tools/blob/master/zmalloc.c
  • 重复的问题是标记为可能重复的问题(帖子的第一行),而不是 Federico A. Ramponi 给出的问题。
  • do { ... } while ((void)0, 0) 的其他变体用于消除有关“恒定条件”的编译器警告。

标签: c loops


【解决方案1】:

它是 C 中唯一可以用于#define 多语句操作、在其后放置分号并仍可在if 语句中使用的构造。一个例子可能会有所帮助:

#define FOO(x) foo(x); bar(x)

if (condition)
    FOO(x);
else // syntax error here
    ...;

即使使用大括号也无济于事:

#define FOO(x) { foo(x); bar(x); }

if 语句中使用它需要省略分号,这是违反直觉的:

if (condition)
    FOO(x)
else
    ...

如果你这样定义 FOO:

#define FOO(x) do { foo(x); bar(x); } while (0)

那么下面的语法是正确的:

if (condition)
    FOO(x);
else
    ....

【讨论】:

  • 不会#define FOO(x) if(true){ foo(x); bar(x); } else void(0) 也可以工作,即使它更丑陋?
  • @user10607:是的,如果你的宏扩展是一个表达式的列表,它就可以工作。但是,如果您想在扩展中包含 ifwhile,则该技巧也不起作用。
  • 我仍然认为这是丑陋的,草率的编码,如果编码正确,可以避免。这可以追溯到一些常识性的 C 规则,即始终在 IF 语句上使用 curly,即使在 IF 之后只有一个操作。这应该是标准做法,IMVHO...
  • @LarryF:问题是,如果您正在编写一个供他人使用的头文件,您无法选择其他人将如何使用您的宏。为了避免给您的用户带来意外的惊喜,您必须使用类似上述的技术来使您的宏的行为与任何其他 C 语句的行为一样。
  • @AaronFranke 因为 {...};实际上由两个语句组成,{...} 部分和它后面的另一个空语句。做 {...} 而(0);不过,这是一种说法。当用户编写 FOO(a, b, c) 时,Larry/Marco 期望它(除非明确另有说明)会产生一个表达式,就像调用返回 void 的函数一样。你的方法实际上是草率的。您的用户的代码样式约定应该与您无关。
【解决方案2】:

这是一种简化错误检查并避免深层嵌套 if 的方法。例如:

do {
  // do something
  if (error) {
    break;
  }
  // do something else
  if (error) {
    break;
  }
  // etc..
} while (0);

【讨论】:

  • er,将其分解为一个方法,并使用提前返回。
  • 或者...只使用 do { } while(0)。
  • 或使用goto。不,说真的,对我来说,if (error) goto error; 和接近尾声的 error: ... 似乎是完成同样事情的更简洁的版本。
  • @WChargin:您链接的文章中的“goto fail”代码也会因“break”而失败。有人只是在那里复制了一条线。这不是 goto 的错。
  • @NiccoloM。 “当然,goto 不是错误,这实际上使它更有趣”
【解决方案3】:

它有助于将多个语句组合成一个语句,这样一个类似函数的宏实际上可以用作一个函数。假设你有:

#define FOO(n)   foo(n);bar(n)

你做到了:

void foobar(int n) {
  if (n)
     FOO(n);
}

然后这扩展为:

void foobar(int n) {
  if (n)
     foo(n);bar(n);
}

请注意,第二次调用 bar(n) 不再是 if 语句的一部分。

将两者包装到do { } while(0) 中,您也可以在if 语句中使用宏。

【讨论】:

  • 非常明确的答案。 +1
  • 明白了。 ;)
  • 为什么不在它周围放大括号呢? #define FOO(n) {foo(n);bar(n)}
  • @endolith 因为#define FOO(n) {foo(n);bar(n)} 会扩展为{foo(n);bar(n)}。注意bar(n)后面没有分号。
【解决方案4】:

有趣的是,请注意以下情况,其中 do {} while (0) 循环不会为您工作:

如果你想要一个返回值的类函数宏,那么你需要一个statement expression: ({stmt; stmt;}) 而不是 do {} while(0):


#include <stdio.h>

#define log_to_string1(str, fmt, arg...) \
    do { \
        sprintf(str, "%s: " fmt, "myprog", ##arg); \
    } while (0)

#define log_to_string2(str, fmt, arg...) \
    ({ \
        sprintf(str, "%s: " fmt, "myprog", ##arg); \
    })

int main() {
        char buf[1000];
        int n = 0;

        log_to_string1(buf, "%s\n", "No assignment, OK");

        n += log_to_string1(buf + n, "%s\n", "NOT OK: gcc: error: expected expression before 'do'");

        n += log_to_string2(buf + n, "%s\n", "This fixes it");
        n += log_to_string2(buf + n, "%s\n", "Assignment worked!");
        printf("%s", buf);
        return 0;
}

【讨论】:

  • 这是一个 GCC 扩展。不过,在 C++11 中,你可以用 lambda 做同样的事情。
  • TIL 语句表达式。整洁的。 lambda 看起来如何?我很好奇。 lambda 返回一个仿函数,本质上,您仍然必须调用它,然后从宏返回结果。 lambda 和调用都需要形成一个语句。你怎么做呢?运算符,?
【解决方案5】:

一般来说,do/while 适用于任何类型的循环构造,其中必须执行循环至少一次。可以通过直接的while 甚至for 循环来模拟这种循环,但结果通常不太优雅。我承认这种模式的特定应用相当罕见,但它们确实存在。我想到的是一个基于菜单的控制台应用程序:

do {
    char c = read_input();

    process_input(c);
} while (c != 'Q');

【讨论】:

  • 它在 C# 中也可用,它没有宏。我不确定为什么有人对这个回复投了反对票,但我认为它几乎是正确的答案,除了它忽略了 while 条件中明确的“0”。请各位,如果您对某人的回复投反对票,请发表评论。
  • 我不是投反对票的人;但是,这个问题非常具体,而且答案总体上是正确的,但与上下文无关。
  • 声明是正确的,但它与上下文无关。
猜你喜欢
  • 2012-03-08
  • 2010-10-29
  • 1970-01-01
  • 2021-08-19
  • 2012-03-18
  • 2016-08-22
相关资源
最近更新 更多