C语言内存分配出现Invalid write/read错误的原因与修复
分割字符串函数的内存错误分析与修复
我实现了一个split_text函数,接收char数组text,以指定token为分隔符将其分割为子数组,结果存储在指向子字符串的指针数组res中。代码运行可正常输出分割结果,但用valgrind检测时出现大量Invalid write of size 8和Invalid read of size 8错误。
原代码
int split_text(char * text, char * token, char ** res) { int x =0; char * tmp = strtok(text , token); while (tmp != NULL) { res[x] = malloc(1024); strcpy(res[x], tmp); x++; *res = realloc(*res, sizeof(char*)*(x+1)); tmp = strtok(NULL , token); } return x; } int main() { char text[] = "Hello world this is elliot"; char ** ptr = (char**)malloc(sizeof(char*)); int x = split_text(text, " ", ptr); for (int i =0; i< x; i++) printf("%s\n", ptr[i]); return 0; }
Valgrind检测输出
==74210== Memcheck, a memory error detector ==74210== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==74210== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info ==74210== Command: ./a.out ==74210== ==74210== Invalid write of size 8 ==74210== at 0x1091D0: split_text (split.c:23) ==74210== by 0x1092B2: main (split.c:38) ==74210== Address 0x4a3f048 is 0 bytes after a block of size 8 alloc'd ==74210== at 0x483F7B5: malloc (vg_replace_malloc.c:381) ==74210== by 0x109294: main (split.c:36) ==74210== ==74210== Invalid read of size 8 ==74210== at 0x1091E7: split_text (split.c:24) ==74210== by 0x1092B2: main (split.c:38) ==74210== Address 0x4a3f048 is 0 bytes after a block of size 8 alloc'd ==74210== at 0x483F7B5: malloc (vg_replace_malloc.c:381) ==74210== by 0x109294: main (split.c:36) ==74210== Hello ==74210== Invalid read of size 8 ==74210== at 0x1092D3: main (split.c:42) ==74210== Address 0x4a3f048 is 0 bytes after a block of size 8 alloc'd ==74210== at 0x483F7B5: malloc (vg_replace_malloc.c:381) ==74210== by 0x109294: main (split.c:36) ==74210== world this is elliot ==74210== ==74210== HEAP SUMMARY: ==74210== in use at exit: 4,152 bytes in 6 blocks ==74210== total heap usage: 12 allocs, 6 frees, 6,312 bytes allocated ==74210== ==74210== LEAK SUMMARY: ==74210== definitely lost: 4,104 bytes in 5 blocks ==74210== indirectly lost: 48 bytes in 1 blocks ==74210== possibly lost: 0 bytes in 0 blocks ==74210== still reachable: 0 bytes in 0 blocks ==74210== suppressed: 0 bytes in 0 blocks ==74210== Rerun with --leak-check=full to see details of leaked memory ==74210== ==74210== ERROR SUMMARY: 12 errors from 3 contexts (suppressed: 0 from 0) ==74210== ==74210== 4 errors in context 1 of 3: ==74210== Invalid read of size 8 ==74210== at 0x1092D3: main (split.c:42) ==74210== Address 0x4a3f048 is 0 bytes after a block of size 8 alloc'd ==74210== at 0x483F7B5: malloc (vg_replace_malloc.c:381) ==74210== by 0x109294: main (split.c:36) ==74210== ==74210== ==74210== 4 errors in context 2 of 3: ==74210== Invalid read of size 8 ==74210== at 0x1091E7: split_text (split.c:24) ==74210== by 0x1092B2: main (split.c:38) ==74210== Address 0x4a3f048 is 0 bytes after a block of size 8 alloc'd ==74210== at 0x483F7B5: malloc (vg_replace_malloc.c:381) ==74210== by 0x109294: main (split.c:36) ==74210== ==74210== ==74210== 4 errors in context 3 of 3: ==74210== Invalid write of size 8 ==74210== at 0x1091D0: split_text (split.c:23) ==74210== by 0x1092B2: main (split.c:38) ==74210== Address 0x4a3f048 is 0 bytes after a block of size 8 alloc'd ==74210== at 0x483F7B5: malloc (vg_replace_malloc.c:381) ==74210== by 0x109294: main (split.c:36) ==74210== ==74210== ERROR SUMMARY: 12 errors from 3 contexts (suppressed: 0 from 0)
错误原因
- 参数传递逻辑错误:
split_text需要修改main中指针数组的内存地址(realloc可能会迁移内存块),但当前参数是char** res,传值调用导致函数内的*res = realloc(...)操作实际修改的是指针数组的第一个元素,而非指针数组本身的地址。 - 内存越界访问:main中初始只给指针数组分配了1个
char*的空间,函数内未正确扩容指针数组,后续访问res[x](x≥1)时超出了初始分配的内存范围,触发非法读写。 - 内存泄漏:未释放分配的子字符串内存和指针数组内存,导致程序退出时内存未回收。
修复方案
修正后的代码
#include <stdio.h> #include <stdlib.h> #include <string.h> int split_text(char *text, char *token, char ***res) { int x = 0; // 初始化指针数组,初始容量设为1 *res = malloc(sizeof(char*)); if (*res == NULL) { return -1; } char *tmp = strtok(text, token); while (tmp != NULL) { // 扩容指针数组:先分配新空间,避免realloc失败丢失原数据 char **temp_ptr = realloc(*res, sizeof(char*) * (x + 1)); if (temp_ptr == NULL) { // 内存分配失败,清理已分配的内存 for (int i = 0; i < x; i++) { free((*res)[i]); } free(*res); return -1; } *res = temp_ptr; // 按实际长度分配子字符串内存,减少浪费 (*res)[x] = malloc(strlen(tmp) + 1); if ((*res)[x] == NULL) { // 内存分配失败,清理已分配的内存 for (int i = 0; i < x; i++) { free((*res)[i]); } free(*res); return -1; } strcpy((*res)[x], tmp); x++; tmp = strtok(NULL, token); } // 扩容添加NULL标记,方便后续遍历 char **temp_ptr = realloc(*res, sizeof(char*) * (x + 1)); if (temp_ptr != NULL) { *res = temp_ptr; (*res)[x] = NULL; } return x; } int main() { char text[] = "Hello world this is elliot"; char **ptr = NULL; int count = split_text(text, " ", &ptr); if (count == -1) { fprintf(stderr, "内存分配失败\n"); return 1; } for (int i = 0; i < count; i++) { printf("%s\n", ptr[i]); } // 释放所有分配的内存,避免泄漏 for (int i = 0; i < count; i++) { free(ptr[i]); } free(ptr); return 0; }
关键修改点
- 参数类型调整:将
split_text的第三个参数改为char*** res,传入指针数组的地址(&ptr),确保函数内可以修改main中的ptr变量。 - 正确扩容指针数组:使用
*res指向指针数组,realloc操作针对*res进行,确保指针数组的地址被正确更新。 - 内存安全检查:增加
realloc和malloc的失败检查,避免内存分配失败导致的崩溃或泄漏,并在失败时清理已分配的内存。 - 优化内存使用:子字符串内存按实际长度分配(
strlen(tmp)+1),而非固定1024字节,减少内存浪费。 - 内存释放逻辑:在main中添加内存释放步骤,回收所有分配的堆内存,消除内存泄漏。
内容的提问来源于stack exchange,提问作者loaded_dypper
相关产品推荐
相关产品推荐

