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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 10:43:12