C语言可变参数格式化打印函数异常问题排查求助
可变参数格式化打印函数错误排查
我用结构体关联格式字符和打印函数指针,实现了一个可变参数的print_all函数,但测试时输出不符合预期。
结构体定义
typedef struct formatter { char spec; void (*print)(va_list); } fmt;
各格式对应的打印函数
void print_char(va_list args) { printf("%c", va_arg(args, int)); } void print_int(va_list args) { printf("%d", va_arg(args, int)); } void print_float(va_list args) { printf("%f", va_arg(args, double)); } void print_string(va_list args) { char *spec = va_arg(args, char *); if (spec == NULL) { printf("(nil)"); return; } printf("%s", spec); }
主可变参数函数
void print_all(const char *const format, ...) { fmt f[] = { {'c', print_char}, {'i', print_int}, {'f', print_float}, {'s', print_string}, {'\0', NULL}}; int i = 0, j; char *separator = ""; va_list args; va_start(args, format); if (format == NULL) { return; } while (format[i] != '\0') { j = 0; while (f[j].spec) { if (f[j].spec == format[i]) { printf("%s", separator); f[j].print(args); separator = ", "; break; } j++; } i++; } printf("\n"); va_end(args); }
测试用例及问题
int main(void) { print_all("ceis", 'B', 3, "stSchool"); return (0); }
预期输出:B, 3, stSchool
实际输出:B, 66,
问题原因
核心问题出在va_list的传递方式:
C语言中va_list是按值传递给函数的,也就是说,你传递给打印函数的是主函数中args的一个副本。打印函数里调用va_arg只会移动副本的指针位置,主函数中的原args指针完全没动。
对应到测试用例的执行流程:
- 处理格式符
'c':调用print_char,副本args读取第一个参数'B',但主函数的args仍指向第一个参数。 - 处理格式符
'e':无匹配打印函数,跳过。 - 处理格式符
'i':调用print_int,再次使用主函数的args(仍指向第一个参数),读取到'B'的ASCII值66,而非预期的第二个参数3。 - 处理格式符
's':此时主函数的args还是没动,尝试读取第一个参数作为字符串指针,导致未定义行为(实际输出被截断)。
解决方案
保持模块化的前提下,修改打印函数的参数为va_list*(指针类型),让子函数直接操作主函数的va_list,确保指针位置正确移动:
1. 修改结构体定义
typedef struct formatter { char spec; void (*print)(va_list*); // 改为指针类型 } fmt;
2. 修改打印函数实现
void print_char(va_list *args) { printf("%c", va_arg(*args, int)); } void print_int(va_list *args) { printf("%d", va_arg(*args, int)); } void print_float(va_list *args) { printf("%f", va_arg(*args, double)); } void print_string(va_list *args) { char *spec = va_arg(*args, char *); if (spec == NULL) { printf("(nil)"); return; } printf("%s", spec); }
3. 修改print_all中的调用逻辑
// 调用打印函数时传递args的地址 f[j].print(&args);
修改后重新编译测试,就能得到预期输出。另外注意测试用例中格式符"ceis"对应4个参数,但你只传了3个,建议补上对应'e'的参数(或者去掉格式符中的'e'),避免未定义行为。
内容的提问来源于stack exchange,提问作者Ehab Elsayed
相关产品推荐
相关产品推荐

