C++ va_list重复使用导致的崩溃分析 | 实战经验

C++ va_list重复使用导致的崩溃分析 #

引言 #

在C++开发中,va_list的不当使用可能导致严重的跨平台兼容性问题。本文将通过一个实际案例,分析va_list重复使用引发的崩溃问题,并介绍正确的使用方法。

背景 #

最近遇到了个奇怪的问题,同样的代码,在 Windows 下正常运行,在 iOS 下必现 crash。

std::string StringPrintf(const char* format, ...) {
    va_list ap;
    va_start(ap, format);
    
    char* buffer = nullptr;
    const size_t size = std::vsnprintf(nullptr, 0, format, ap) + 1;
    buffer = new char[size];
    
    std::vsnprintf(buffer, size, format, ap);
    va_end(ap);
    
    std::string result(buffer);
    delete[] buffer;
    return result;
}

经过分析定位后发现,这里重复使用了 va_list 并且在 vsnprintf 后还继续使用了 ap,进而导致的 crash。

通过查看文档:https://port70.net/~nsz/c/c11/n1570.html#7.21.6.8

img

这里明确说明,arg 在 vsnprintf 后,会变成不确定的状态。所以才有了在 Windows 上可以正常运行,在 iOS 上会异常退出的问题。

如何解决这个问题? #

标准做法是使用 va_copy 复制一份 va_list,如下:

std::string StringPrintf(const char* format, ...) {
    va_list ap, ap_copy;
    va_start(ap, format);
    va_copy(ap_copy, ap);
    
    const int32_t size = std::vsnprintf(nullptr, 0, format, ap) + 1;
    va_end(ap);
    
    if (size <= 0) {
        return "";
    }
    
    char* buffer = new char[size];
    std::vsnprintf(buffer, size, format, ap_copy);
    va_end(ap_copy);
    
    std::string result(buffer);
    delete[] buffer;
    return result;
}

以前都是直接使用的三方库 StringPrintf,而没有自己实现。这也算是使用 vsnprintf 过程中遇到的一个小坑,在此分享记录一下。