C语言自定义realloc函数p_reallocdp的Valgrind内存错误问题
自定义字符串指针数组realloc实现的Valgrind无效读问题排查
Valgrind错误信息
==17128== Invalid read of size 8 ==17128== at 0x1095C5: p_reallocdp (c_stdlib.c:66) ==17128== by 0x10AA88: tokenizer (tokenizer.c:36) ==17128== by 0x10A3B5: shell_loop (shell_loop.c:23) ==17128== by 0x10A16C: main (main.c:42) ==17128== Address 0x4ad8fd0 is 0 bytes after a block of size 64 alloc'd ==17128== at 0x4841848: malloc (vg_replace_malloc.c:431) ==17128== by 0x10954C: _reallocdp (c_stdlib.c:53) ==17128== by 0x10A9EA: tokenizer (tokenizer.c:24) ==17128== by 0x10A3B5: shell_loop (shell_loop.c:23) ==17128== by 0x10A16C: main (main.c:42)
问题代码
p_reallocdp函数
char **p_reallocdp(char **ptr, size_t dest) { char **newptr; size_t i; if (ptr == NULL) return (malloc(sizeof(char *) * dest)); /* line 53 */ newptr = malloc(sizeof(char *) * (dest)); for (i = 0; i < dest; i++) newptr[i] = ptr[i]; /* line 66 */ free(ptr); return (newptr); }
Tokenizer代码片段
char **tokens = NULL; char *token; size_t idx = 0; while (token) { tokens = p_reallocdp(tokens, ((idx + 1) * sizeof(char *))); /* line 24 */ tokens[idx] = _strdup(token); idx++; token = _strtok(NULL, delim); } tokens = p_reallocdp(tokens, ((idx + 1) * sizeof(char *))); /* line 36 */
原因分析
核心缺陷:自定义实现未跟踪原内存块大小
系统realloc内部会记录每个分配块的实际容量,但你的p_reallocdp没有这个机制。当调用函数扩容时(比如tokenizer第36行,要把容量从idx扩展到idx+1),函数会循环读取dest(即idx+1)个元素,但原指针ptr指向的内存块只有idx个char*的空间,循环到i=idx时,会读取原内存块边界外的无效内存,触发Valgrind的"Invalid read"错误。tokenizer调用逻辑放大问题
循环内每次扩容到idx+1,此时原块大小为idx,函数错误地复制idx+1个元素;循环结束后,idx是已存储的token数量,再次扩容到idx+1时,同样会读取原块边界外的内存。
解决方法
方法1:修改p_reallocdp,增加原大小参数
让函数接收原内存块的有效元素数量,只复制已有的元素:
char **p_reallocdp(char **ptr, size_t old_size, size_t new_size) { char **newptr; size_t i; // 只复制原块和新块中较小的数量 size_t copy_count = old_size < new_size ? old_size : new_size; if (ptr == NULL) return malloc(sizeof(char *) * new_size); newptr = malloc(sizeof(char *) * new_size); if (!newptr) { // 处理内存分配失败,可返回NULL或自定义报错逻辑 return NULL; } for (i = 0; i < copy_count; i++) newptr[i] = ptr[i]; free(ptr); return newptr; }
同步修改tokenizer的调用:
// 循环内扩容 tokens = p_reallocdp(tokens, idx, idx + 1); // 循环结束后扩容(添加终止NULL) tokens = p_reallocdp(tokens, idx, idx + 1);
方法2:简化扩容逻辑(临时方案)
如果不想修改函数参数,可以在tokenizer中确保每次扩容时,dest等于原块的有效元素数+1,同时修改p_reallocdp的循环条件为i < old_size(但需要额外变量跟踪原大小,本质和方法1类似)。不过方法1是最清晰可靠的方案。
内容的提问来源于stack exchange,提问作者yungryce
相关产品推荐
相关产品推荐

