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
相关产品推荐
相关产品推荐

