C语言int*内存分配与char**地图加载函数段错误排查求助
地图加载函数的段错误分析与修复
问题概述
需要实现两个核心函数:
nb_cols_pline:返回int*类型,存储地图每行的字符长度load_map:将地图文件内容加载到char**类型的二维数组并返回
当前代码运行时触发段错误,Valgrind检测到两类问题:
- 使用大小为8的未初始化值
- 大小为1的无效写入
同时需要明确C语言中用malloc分配int*的正确方式。
代码错误分析
1. nb_cols_pline函数的问题
- stat调用未做错误检查:如果
stat(filepath, size)执行失败,size->st_size会是未初始化的垃圾值,后续用这个值分配buffer内存时,会触发"未初始化值"错误。 - 行遍历逻辑失效:内层循环始终从
buffer[0]开始查找换行符,从未移动到下一行的起始位置;如果文件最后一行没有换行符,会直接越界访问缓冲区,触发"无效写入"错误。 - 重复调用
nb_lines:每次调用都会重新打开文件统计行数,不仅效率低下,若nb_lines实现有问题,还会导致count数组的分配大小错误。
2. load_map函数的问题
- 内存泄漏与冗余计算:循环中每次调用
nb_cols_pline都会分配新的int*,但未释放,同时重复计算每行长度完全无意义。 - map分配逻辑完全错误:
- 首次分配
map时,运算符优先级问题导致分配大小计算错误(需加括号保证先执行乘法); - 后续循环中
map = malloc(...)直接覆盖了之前的二级指针,导致原分配内存泄漏,且map变为一级指针,后续map[y][x]的访问属于越界写入,是"无效写入"的核心原因。
- 首次分配
- 索引变量误用:内层循环错误使用
nb_cols[i],实际应使用nb_cols[y],i的递增逻辑也会导致buffer访问越界。 - 同样存在stat未检查返回值的问题。
C语言中malloc int*的正确方式
要分配一个能存储n个int的动态数组,语法如下:
int *arr = malloc(sizeof(int) * n);
- 必须检查分配结果:如果
malloc返回NULL,说明内存分配失败,需要处理该异常。 - 使用完成后必须调用
free(arr)释放内存,避免内存泄漏。
修复后的代码
修复后的nb_cols_pline
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> // 假设nb_lines是已实现的函数,返回文件总行数 int nb_lines(char *filepath); int *nb_cols_pline(char *filepath) { int x, y = 0; struct stat size_buf; // 用栈上结构体替代malloc,同时检查stat返回值 if (stat(filepath, &size_buf) == -1) { perror("stat failed"); return NULL; } int line_count = nb_lines(filepath); int *count = malloc(sizeof(int) * line_count); if (!count) { perror("malloc count failed"); return NULL; } int fd = open(filepath, O_RDONLY); if (fd == -1) { perror("open failed"); free(count); return NULL; } // 多分配1字节存储字符串终止符 char *buffer = malloc(sizeof(char) * size_buf.st_size + 1); if (!buffer) { perror("malloc buffer failed"); close(fd); free(count); return NULL; } ssize_t bytes_read = read(fd, buffer, size_buf.st_size); if (bytes_read == -1) { perror("read failed"); free(buffer); close(fd); free(count); return NULL; } buffer[bytes_read] = '\0'; // 手动添加终止符,避免越界 char *ptr = buffer; for (y = 0; y < line_count; y++) { x = 0; // 遍历到换行符或字符串结束 while (ptr[x] != '\n' && ptr[x] != '\0') { x++; } count[y] = x; // 移动指针到下一行起始位置(跳过换行符) if (ptr[x] == '\n') { ptr += x + 1; } else { // 最后一行无换行符,直接退出循环 break; } } close(fd); free(buffer); return count; }
修复后的load_map
char **load_map(char *filepath) { struct stat size_buf; if (stat(filepath, &size_buf) == -1) { perror("stat failed"); return NULL; } int line_count = nb_lines(filepath); int *nb_cols = nb_cols_pline(filepath); if (!nb_cols) { return NULL; } // 分配二级指针数组,+1用于存储NULL结束标记 char **map = malloc(sizeof(char *) * (line_count + 1)); if (!map) { perror("malloc map failed"); free(nb_cols); return NULL; } // 为每行单独分配内存(多1字节存终止符) for (int x = 0; x < line_count; x++) { map[x] = malloc(sizeof(char) * (nb_cols[x] + 1)); if (!map[x]) { perror("malloc map line failed"); // 释放已分配的内存,避免泄漏 for (int i = 0; i < x; i++) { free(map[i]); } free(map); free(nb_cols); return NULL; } } map[line_count] = NULL; // 设置结束标记 int fd = open(filepath, O_RDONLY); if (fd == -1) { perror("open failed"); // 释放已分配内存 for (int i = 0; i < line_count; i++) { free(map[i]); } free(map); free(nb_cols); return NULL; } char *buffer = malloc(sizeof(char) * size_buf.st_size + 1); if (!buffer) { perror("malloc buffer failed"); close(fd); // 释放已分配内存 for (int i = 0; i < line_count; i++) { free(map[i]); } free(map); free(nb_cols); return NULL; } ssize_t bytes_read = read(fd, buffer, size_buf.st_size); if (bytes_read == -1) { perror("read failed"); free(buffer); close(fd); // 释放已分配内存 for (int i = 0; i < line_count; i++) { free(map[i]); } free(map); free(nb_cols); return NULL; } buffer[bytes_read] = '\0'; char *ptr = buffer; for (int y = 0; y < line_count; y++) { int x = 0; while (ptr[x] != '\n' && ptr[x] != '\0') { map[y][x] = ptr[x]; x++; } map[y][x] = '\0'; // 每行末尾添加字符串终止符 if (ptr[x] == '\n') { ptr += x + 1; } else { break; } } close(fd); free(buffer); free(nb_cols); return map; }
关键修复点总结
- 所有系统调用(stat、open、read)和
malloc都添加了错误检查,避免使用无效值或空指针。 - 修复了行遍历的指针移动逻辑,确保每次处理下一行的正确起始位置。
- 修正了
map的分配逻辑:先分配二级指针数组,再为每行单独分配内存。 - 避免了冗余计算和内存泄漏,直接复用
nb_cols_pline的返回值。 - 为所有字符串添加终止符,避免后续操作出现未定义行为。
内容的提问来源于stack exchange,提问作者G.exe
相关产品推荐
相关产品推荐

