You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

解释:

  1. va_copy用于复制va_list,因为vsnprintf会消耗va_list,复制后可以多次使用原参数列表。
  2. 专门的v开头函数(vsmprintf)是转发可变参数的标准做法,避免直接传递va_list给可变参数函数的错误。
  3. 调用完后必须free分配的内存,避免内存泄漏。

内容的提问来源于stack exchange,提问作者user17471643

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 05:45:12