9

我有一个基于printf-style 格式的日志框架:

void Logger::debug(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    this->output(DebugLevel, fmt, args);
    va_end(args);
}

如果Logger::output抛出,编译器会正确展开堆栈,还是我需要va_end(args)在 catch 子句中添加一个 try/catch 块?这可以改为 RAII'ed,还是太神奇va_end了?如果可能,请包括对标准的引用。

4

3 回答 3

7

不,他们不能。他们不能因为它们是宏的原因是愚蠢的。宏可以从构造函数和析构函数中使用,没有任何问题。但是,va_start并且va_end有特定的要求,它们必须从同一个函数中调用。将它们移动到单独的函数是无效的。C++ 指的是 C 标准,C 标准说“每次调用va_startva_copy宏都应与va_end同一函数中相应的宏调用相匹配”。(7.15.1) 如果您确实va_end从帮助类的析构函数中调用,它可能有效,也可能无效。由于它不符合标准的要求,因此行为未定义。

编辑:至于另一个问题,va_end当抛出异常时你是否需要,可以提出一个合法的论点,即“调用va_end宏”实际上并不要求代码到达调用该宏的位置(因为宏调用严格来说是仅编译时的操作),但它强烈建议您确实需要它。所以是的,使用try/catch如果例外是可能的。C99 的基本原理在其描述中简要说明了va_copy可能va_start会分配内存。(我知道它实际上没有实现。)在这样的实现中,va_end然后会释放该内存,因此跳过va_end会导致内存泄漏。

于 2012-07-25T08:35:45.063 回答
0

va_startva_end是宏。因此,它们不能被RAII编辑。此外,对于例外情况也不需要特别注意。

于 2012-07-25T08:13:32.920 回答
0

是的,这可以使用 Boost.ScopeExit 进行RAII,即使 va_start/va_end 是宏。

于 2012-07-25T08:19:08.560 回答