C语言读取文件到char**时出现段错误且丢失最后一行求助
排查C语言段错误与文件读取问题
让我帮你梳理代码里的问题——你遇到的段错误和最后一行未读取确实和数组操作、文件计数逻辑有关,咱们一步步拆解:
核心问题分析
1. 行数统计错误,导致最后一行丢失
你的countlines函数统计的是文件中'\n'的数量,但实际行数 = 换行符数量 + 1(如果文件最后一行没有以换行符结尾)。比如文件内容是line1\nline2\nline3,换行符有2个,实际行数是3,但你的函数返回2,后续分配的数组大小不够,自然读不到最后一行。
2. 数组越界触发段错误
getlines函数的循环条件是i <= maxlines,但你用calloc分配的lines数组大小是lines_count(也就是countlines返回的数值),数组索引范围是0 ~ lines_count-1。当i = maxlines时,你访问了lines[maxlines],这已经超出数组的合法内存范围,直接触发段错误。
3. 数组遍历逻辑完全错误
main函数里的循环:
for (String *sp = lines; sp != NULL; sp++) printf("%s", *sp);
这个写法是给链表设计的,但lines是连续的数组,没有NULL终止符。循环会一直向后移动指针,直到访问到非法内存,必然导致段错误。
4. 额外隐患:行长度限制过小
你定义的MAXLINE=10,意味着fgets最多只能读取9个有效字符(要留一个位置给字符串终止符'\0')。如果文件中有超过9个字符的行,fgets会截断内容,剩余字符留在文件流中,导致后续读取混乱。
修复后的代码示例
修改后的files_utils.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXLINE 100 // 增大行长度,避免截断 typedef char *String; // 更高效的文件长度计算方式 long file_length(FILE *fp) { long original_pos = ftell(fp); fseek(fp, 0, SEEK_END); long len = ftell(fp); fseek(fp, original_pos, SEEK_SET); return len; } // 修正行数统计逻辑 long countlines(FILE *fp) { long original_pos = ftell(fp); rewind(fp); int c = fgetc(fp); // 处理空文件 if (c == EOF) { fseek(fp, original_pos, SEEK_SET); return 0; } long line_count = 1; while ((c = fgetc(fp)) != EOF) { if (c == '\n') { line_count++; } } fseek(fp, original_pos, SEEK_SET); return line_count; } // 修正数组越界,增加错误处理 String *getlines(String lines[], long maxlines, FILE *fp) { for (int i = 0; i < maxlines; i++) { lines[i] = malloc(MAXLINE * sizeof(char)); if (!lines[i]) { // 内存分配失败,释放已分配的内存避免泄漏 for (int j = 0; j < i; j++) { free(lines[j]); } return NULL; } // 检查fgets返回值,处理读取失败的情况 if (fgets(lines[i], MAXLINE, fp) == NULL) { free(lines[i]); lines[i] = NULL; break; } } return lines; }
修改后的main.c
#include "files_utils.h" int main() { FILE *fp = fopen("expl", "r"); if (!fp) { perror("Failed to open file"); return -1; } long lines_count = countlines(fp); long flen = file_length(fp); printf("file length: %ld\n", flen); printf("file lines: %ld\n", lines_count); String *lines = calloc(lines_count, sizeof(String)); if (!lines) { perror("Failed to allocate lines array"); fclose(fp); return -1; } if (getlines(lines, lines_count, fp) == NULL) { perror("Failed to read lines"); free(lines); fclose(fp); return -1; } printf("finished\n"); // 正确遍历数组,同时释放内存 for (long i = 0; i < lines_count; i++) { if (lines[i] != NULL) { printf("%s", lines[i]); free(lines[i]); } } free(lines); printf("before close\n"); fclose(fp); printf("closed\n"); return 0; }
后续排查方向建议
- 数组边界检查:任何时候访问数组都要确认索引在
0 ~ 长度-1范围内,循环条件避免出现<=这类可能越界的写法。 - 文件计数逻辑验证:打印计数结果后,手动核对文件实际行数/长度,确认统计逻辑符合预期。
- 指针遍历合法性:如果是数组,一定要用长度限制循环次数;如果是链表,确保最后一个节点是
NULL。 - 内存操作严谨性:分配内存后检查返回值,使用完毕及时释放,避免内存泄漏或非法访问。
- 文件操作返回值处理:
fopen、fgets、fseek等函数都可能失败,一定要检查返回值并处理异常情况。
内容的提问来源于stack exchange,提问作者Maty
相关产品推荐
相关产品推荐

