当抛出异常时,我需要调用va_end吗?

11

我有一个基于 printf 格式的日志记录框架:

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

如果Logger::output抛出异常,编译器会正确地解开堆栈,还是我需要在catch子句中添加try/catch块,并在其中加入va_end(args)?这可以被RAII化吗,还是va_end对此来说太神奇了?如果可能,请包含对标准的引用。


这个能用RAII来代替吗?不行,va_* 标识符是宏。 - Alexandre C.
4
最好不要在C++中使用这些东西。它们极不安全,而且绝对是不必要的。 - Puppy
1
我认为无论何处使用va_start(),都必须在可变参数函数内部使用va_end()。请注意,如果您计划记录大量内容,则try-catch会显着降低性能。 - Eitan T
3个回答

8
不行,因为它们是宏。这种说法很荒谬,宏可以在构造函数和析构函数中使用而不会出现任何问题。然而, va_startva_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 会导致内存泄漏。

0

va_startva_end都是宏定义,因此无法进行RAII封装。此外,在处理异常时也不需要特别注意。


4
特别关注例外情况是必要的。在调用va_start后没有匹配的va_end而从函数返回会导致未定义的行为。 - Mike Seymour
根据标准来说是未定义的,没错。但是在实际应用中,超过99%的情况下 va_end 没有作用,并且省略它是无害的。只有在关心那不到1%的特别模糊的实现时,才需要使用 va_end。一个标准上未定义的事物仍然可以在实现中被定义,如果你所关心的所有实现都定义了相同的行为,那么实际上等同于标准对其进行了定义。 - Simon Kissane

0

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


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