C17环境下可变参数函数异常及smprintf崩溃问题排查
可变参数函数问题排查
问题1:直接将va_list传入printf导致输出异常
环境与代码
使用C17标准,CMake配置:
cmake_minimum_required(VERSION 3.25) project(3 C) set(CMAKE_C_STANDARD 17) add_executable(${PROJECT_NAME} main.c)
测试代码:
#include <stdarg.h> #include <stdio.h> void test(const char *fmt, ...) { va_list args; va_start(args, fmt); printf(fmt, args); // 错误:直接传递va_list给printf va_end(args); } int main() { test("%d %d %d %d %d\n", (unsigned char)1, (unsigned char)2, (unsigned char)3, (unsigned char)4, (unsigned char)5); return 0; }
运行环境:Windows 10、CLion 2023.1.1、MSVC 19.35 x64编译器,输出:
1897200872 2 3 1 1077874749
原因
printf是可变参数函数,需要直接传入可变参数列表,而va_list是用于遍历可变参数的对象(本质是指针/结构体)。直接把va_list传给printf,相当于把va_list的内存地址当作第一个格式化参数的值,后续参数才是原本的1、2、3...,完全不符合格式串%d %d %d %d %d的匹配规则,因此输出乱码。
正确做法是使用vprintf——这类以v开头的标准库函数专门接受va_list作为参数,用于转发可变参数:
vprintf(fmt, args); // 正确
问题2:自定义smprintf函数导致程序崩溃(退出码0xC0000005)
崩溃代码
#include <stdarg.h> #include <stdio.h> #include <stdlib.h> char *smprintf(const char *fmt, ...) { va_list args; va_start(args, fmt); int len = vsnprintf(NULL, 0, fmt, args); va_end(args); len++; char *buf = malloc(len); if (buf == NULL) { return NULL; } va_start(args, fmt); vsnprintf(buf, len, fmt, args); va_end(args); return buf; } void test(const char *fmt, ...) { va_list args; va_start(args, fmt); vprintf("Result: %s.\n", smprintf(fmt, args)); // 错误:直接传递va_list给smprintf的可变参数 va_end(args); } int main() { test("%d %d %d %d %d\n", (unsigned char)1, (unsigned char)2, (unsigned char)3, (unsigned char)4, (unsigned char)5); return 0; }
原因
smprintf的参数是...(可变参数),但直接传入va_list类型的args时,smprintf内部调用va_start会把args本身当作可变参数列表的起始,而非原本的1、2、3...参数列表,导致vsnprintf访问非法内存,触发0xC0000005(访问违例)崩溃。
解决办法
给smprintf实现一个接受va_list的重载版本(命名为vsmprintf),让test函数直接调用这个版本:
// 新增接受va_list的版本 char *vsmprintf(const char *fmt, va_list args) { va_list args_copy; va_copy(args_copy, args); // 复制va_list,避免原args被修改 int len = vsnprintf(NULL, 0, fmt, args_copy); va_end(args_copy); len++; char *buf = malloc(len); if (buf == NULL) { return NULL; } va_copy(args_copy, args); vsnprintf(buf, len, fmt, args_copy); va_end(args_copy); return buf; } // 原smprintf可以保留,作为vsmprintf的包装 char *smprintf(const char *fmt, ...) { va_list args; va_start(args, fmt); char *buf = vsmprintf(fmt, args); va_end(args); return buf; } void test(const char *fmt, ...) { va_list args; va_start(args, fmt); char *result = vsmprintf(fmt, args); // 调用v版本 vprintf("Result: %s.\n", result); free(result); // 记得释放内存,避免泄漏 va_end(args); }
解释:
va_copy用于复制va_list,因为vsnprintf会消耗va_list,复制后可以多次使用原参数列表。- 专门的
v开头函数(vsmprintf)是转发可变参数的标准做法,避免直接传递va_list给可变参数函数的错误。 - 调用完后必须
free分配的内存,避免内存泄漏。
内容的提问来源于stack exchange,提问作者user17471643
相关产品推荐
相关产品推荐

