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

C语言自定义sprintf实现时realloc扩容与strcat拼接异常问题

自定义无缓冲区sprintf实现逻辑错误排查

问题描述

尝试实现无需预传入输出缓冲区的自定义字符串格式化函数,核心实现思路为:

  • 遍历传入的格式字符串,逐字符检查
  • 识别到%字符后索引后移一位,通过switch-case匹配后续格式符,执行对应参数的拼接逻辑

现有问题代码

utils.c

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils.h>

char *concat(char *p_format, ...) {
   char *p_concat_str = calloc(1, sizeof(char));

   va_list args;
   va_start(args, p_format);

   for (unsigned int i = 0; i < strlen(p_format); ++i) {
      if (p_format[i] == '%') {
         i++;

         void *p_arg_str = va_arg(args, char *);
         /* printf("%s\n", (char *)p_arg_str); */
         p_concat_str = realloc(p_concat_str, (strlen(p_arg_str) + 1));
         switch (p_format[i]) {
            case 's':
               strcat(p_concat_str, (char *)p_arg_str);
               /* printf("%s\n", (char *)p_arg_str); */
               break;
         }
      }
      p_concat_str = realloc(p_concat_str, i + 2);
      p_concat_str[i] = p_format[i];
      p_concat_str[i + 1] = '\0';
   }

   return p_concat_str;
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils.h>

int main() {
   char *p_world = "World";
        /* 预期输出: Hello World */
   char *p_str = concat("Hello %s", p_world);
   printf("Formatted str: %s | its len %lu\n", p_str, strlen(p_str));
   free(p_str);
   return 0;
}

错误运行输出

-- Configuring done
-- Generating done
-- Build files have been written to: /home/prxvvy/workspace/cutils/cmake-build-debug
[2/2] Linking C executable cutils
Formatted str: Hello Ws | its len 8

核心逻辑错误点

代码共有4处关键逻辑问题,直接导致输出不符合预期:

  • 格式符处理后未跳过通用写入逻辑:识别%后i自增指向格式符s,完成参数拼接后,循环末尾仍会执行普通字符写入逻辑,把格式符s本身写入结果串,同时覆盖之前拼接的参数内容。
  • 参数拼接时内存分配大小错误:处理%s时realloc传入的大小为strlen(p_arg_str) + 1,完全没有计算结果串原有内容的长度,分配的内存远小于实际需要的大小,strcat写入时直接发生内存越界。
  • 循环内通用内存分配逻辑错误:每次循环末尾统一按i + 2(原格式串下标+2)分配内存,完全没有考虑替换参数带来的长度增长,结果串实际长度远大于分配的内存大小,造成内存踩踏。
  • 写入位置索引错误:直接用原格式串的下标i作为结果串的写入位置,在插入长度大于2的替换参数后,结果串的实际写入位置和原格式串下标不再对应,按i写入会直接覆盖之前拼接好的参数内容——这也是输出中"World"仅保留首字母'W'、后面直接跟格式符's'的直接原因。

修复方案

修复核心逻辑:

  • 单独维护结果串的实际写入位置/当前长度变量,不复用原格式串的遍历下标
  • 每次分配内存时,精确计算「原有内容长度 + 新增内容长度 + 1个字节的字符串终止符\0」,避免内存不足
  • 处理完格式符后直接进入下一轮循环,不要重复写入格式符本身
  • 普通字符直接追加到结果串的当前写入位置,不要用原格式串下标定位

修复后的utils.c实现

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils.h>

char *concat(char *p_format, ...) {
    // 初始分配1字节存终止符
    char *p_concat_str = calloc(1, sizeof(char));
    size_t current_len = 0; // 维护结果串当前实际长度

    va_list args;
    va_start(args, p_format);

    for (unsigned int i = 0; p_format[i] != '\0'; ++i) {
        if (p_format[i] == '%') {
            i++;
            char *p_arg_str = va_arg(args, char *);
            size_t arg_len = strlen(p_arg_str);
            // 重新分配内存:原有长度 + 参数长度 + 终止符
            p_concat_str = realloc(p_concat_str, current_len + arg_len + 1);
            // 拼接参数到结果串末尾
            strcat(p_concat_str, p_arg_str);
            current_len += arg_len;
            continue; // 跳过后续普通字符写入逻辑,直接处理下一个字符
        }
        // 处理普通字符:扩容1字节存新字符+终止符
        p_concat_str = realloc(p_concat_str, current_len + 2);
        p_concat_str[current_len] = p_format[i];
        p_concat_str[current_len + 1] = '\0';
        current_len++;
    }

    va_end(args);
    return p_concat_str;
}

修复后运行输出

-- Configuring done
-- Generating done
-- Build files have been written to: /home/prxvvy/workspace/cutils/cmake-build-debug
[2/2] Linking C executable cutils
Formatted str: Hello World | its len 11

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 21:06:27