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

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指针完全没动。

对应到测试用例的执行流程:

  1. 处理格式符'c':调用print_char,副本args读取第一个参数'B',但主函数的args仍指向第一个参数。
  2. 处理格式符'e':无匹配打印函数,跳过。
  3. 处理格式符'i':调用print_int,再次使用主函数的args(仍指向第一个参数),读取到'B'的ASCII值66,而非预期的第二个参数3。
  4. 处理格式符'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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 18:24:53