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

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 */

原因分析

  1. 核心缺陷:自定义实现未跟踪原内存块大小
    系统realloc内部会记录每个分配块的实际容量,但你的p_reallocdp没有这个机制。当调用函数扩容时(比如tokenizer第36行,要把容量从idx扩展到idx+1),函数会循环读取dest(即idx+1)个元素,但原指针ptr指向的内存块只有idx个char*的空间,循环到i=idx时,会读取原内存块边界外的无效内存,触发Valgrind的"Invalid read"错误。

  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 20:53:29