【问题标题】:Case variadic macro in CC中的大小写可变宏
【发布时间】:2019-03-09 07:39:10
【问题描述】:

我有 2 个用于断言函数输入参数的包装宏:

/**
 * @brief   An assert wrapper with no value return in case assert fails.
 * @param   x_: value to test for being non zero.
 */
#define UTIL_ASSERT_VOID(x_)                                                \
    assert_param(x_);                                                       \
    if (!x_)                                                                \
        return;                                                             \

/**
 * @brief   An assert wrapper with a value return in case assert fails.
 * @param   x_: value to test for being non zero.
 */
#define UTIL_ASSERT_VAL(x_, ret_)                                           \
    assert_param(x_);                                                       \
    if (!x_)                                                                \
        return ret_;                                                        \

前者用于返回 void 的函数,而后者用于返回非 void 的函数。我想知道在 C11(或更早版本)中是否有一种机制允许人们仅使用具有可变参数数量的单个宏。根据提供给宏的参数数量(1 或 2),将编译 returnreturn ret_

【问题讨论】:

  • 闻起来像“XY 问题”。您可能应该做的只是assert(param1); assert(param2); ...。触发断言时从函数返回有什么意义?
  • @Lundin 在一般情况下没有意义。但首先 - 你应该总是处理所有情况,所以无论如何你总是在断言之后返回。其次,仅当程序在由操作系统控制的环境中运行时才有意义,这将在断言时停止它。在没有操作系统和许多线程的嵌入式系统中,当发生分段错误时,可能会发生不好的事情。还取决于断言实现,它并不总是停止线程。
  • 在实际系统中,您总是会在出错时返回错误代码;如果 void 函数出现错误,您不会只是默默地崩溃。所以你的场景是不现实的。您不会有一个函数返回void,另一个函数返回int,而是一个函数类型返回err_t 或其他。此外,裸机系统首先应避免使用 assert()。
  • @Lundin assert 是一个调试工具。它甚至没有在发布版本中编译。它的存在仅对开发目的有意义,并且在发布版本中不予考虑。如果感觉应该这样,那么功能就没有以正确的方式构建。您正在描述不同的机制。
  • 这就是我的意思,在发布构建的情况下,断言将不存在,因此您的 void 函数只会静默返回,并且程序无法判断发生了错误。这简直是​​糟糕的设计。此外,嵌入式系统通常会将参数验证留给调用者,因为它可能很昂贵。

标签: c macros c11 variadic-macros


【解决方案1】:

你可以这样做:

#define UTIL_ASSERT(x_, ...)                                                \
    assert_param(x_);                                                       \
    if (!x_)                                                                \
        return __VA_ARGS__;

但请记住,您不能保证此可变参数宏中只有 1 个参数,因此您需要正确使用它。

更新: 感谢this 线程,我找到了更好的方法:

void assert_param(int x);

#define UTIL_ASSERT_1(x_)   do { assert_param(x_); if (!x_) return; } while(0)

#define UTIL_ASSERT_2(x_, ret_)   do { assert_param(x_); if (!x_) return ret_; } while(0)     

#define GET_MACRO(_1,_2,NAME,...) NAME
#define UTIL_ASSERT(...) GET_MACRO(__VA_ARGS__, UTIL_ASSERT_2, UTIL_ASSERT_1)(__VA_ARGS__)


int foo() {
     UTIL_ASSERT(0,1);
}

void doo() {
     UTIL_ASSERT(0);
}

这个比上一个要好得多,因为它以某种方式验证了参数的数量。

【讨论】:

  • 妈的,搞不懂这个语义,有道理!是的,我理解安全缺陷,就像所有宏一样,但我真的无法忍受压倒性的代码复制。
  • 请注意,这依赖于一个允许您省略... 参数的非标准扩展,如果您使用-pedantic 编译,它将生成警告。 (gcc/clang)。
  • @Bremen 我认为您也应该能够编写更好的宏。我真的写了最简单的形式。
  • @Bremen 我正在考虑使用__VA_OPT__ 将宏扩展为2个不同的宏,一个1个参数,另一个2个。所以我认为如果宏使用不正确,我们应该会出错。但我真的应该自己思考并尝试一下。虽然它会是 C++2a。
  • @Bremen 用更好的方法更新了答案。
【解决方案2】:

有一种方法可以用标准 C 来做这些事情。首先,你有一个核心宏可以同时处理这两种情况

#define ASSERT0(X, RET, ...) 
   /* put your stuff here that only uses X and RET, and ignores the ... */

如您所见,它接收三个或更多参数。而且您必须安排 RET 只是您需要的情况下的空令牌。

现在您可以在其周围放置一个包装器

#define ASSERT1(...) ASSERT0(__VA_ARGS__)

这确保了可能位于单个参数中的逗号将被视为ASSERT0 的参数分隔符。

然后用户级宏可以是

#define MY_ASSERT(...) ASSERT1(__VA_ARGS__, ,)

这确保如果您只使用一个参数ASSERT0 下方将看到一个空的第二个参数。如果你用两个参数调用它,ASSERT0 只会看到这些。

您还应该考虑将内部宏包装在do { ... } while(0) 中。否则,您可能会遇到“悬空else”问题,并将用户与其他句法效果混淆。

【讨论】:

  • 感谢您的回答,这是一个更复杂的问题,它确实有效,但我需要考虑一下......很好。
  • @Afshin 您的解决方案不那么老套(也不能插入超过 2 个参数),而这个整体引入的宏较少。
猜你喜欢
  • 1970-01-01
  • 2014-09-04
  • 1970-01-01
  • 2013-05-17
  • 1970-01-01
  • 2012-11-13
  • 2020-10-10
  • 2019-12-02
  • 2012-04-08
相关资源
最近更新 更多