realloc变更内存地址后vsnprintf调用结果异常问题排查
问题根源与修复方案
嘿,我一眼就看出问题出在哪了——你重复使用了同一个va_list却没有重置或复制!
让我给你拆解一下:当你第一次调用vsnprintf(0, 0, format, argp)计算字符串长度时,这个函数会遍历整个可变参数列表,把argp内部的指针移动到参数列表的末尾。等你第二次再用同一个argp去调用vsnprintf写入缓冲区时,它已经没有参数可以读取了,输出自然全错。realloc变更内存地址只是让这个隐藏的问题彻底暴露出来,其实不管地址变不变,你的函数逻辑本身就有bug。
修复后的代码
直接给你改好的版本,我还加了注释说明关键调整:
bool write_str(char **str, const char *format, ...) { va_list argp, argp_copy; va_start(argp, format); // 复制一份va_list专门用来计算长度,避免原argp被消耗 va_copy(argp_copy, argp); // 计算需要的缓冲区大小(包含终止符) size_t len = vsnprintf(0, 0, format, argp_copy); va_end(argp_copy); // 用完副本必须及时结束,避免资源泄漏 // 分配/重新分配内存 if (*str) { *str = realloc(*str, len + 1); } else { *str = malloc(len + 1); // 这里初始化**str=0完全没必要,后面的vsnprintf会覆盖整个缓冲区 // **str = 0; } if (!*str) { va_end(argp); // 内存分配失败时,别忘了结束原va_list return false; } // 使用原va_list写入字符串,注意第二个参数是len+1(要给终止符留位置) vsnprintf(*str, len + 1, format, argp); // 其实vsnprintf会自动添加终止符,手动加一遍也更保险 (*str)[len] = 0; va_end(argp); return true; }
几个重要的细节提醒
- va_list的生命周期:每一个
va_start都必须对应一个va_end,包括你用va_copy复制出来的副本,否则可能会导致未定义行为。 - vsnprintf的缓冲区大小:写入的时候第二个参数要传
len + 1,因为len是字符串的有效长度,终止符还需要额外一个字节的空间。 - 多余的初始化:当
*str为NULL时,初始化**str=0是多余的,因为后续的vsnprintf会覆盖整个缓冲区,最后我们也手动加了终止符。
内容的提问来源于stack exchange,提问作者rphii
相关产品推荐
相关产品推荐

