【问题标题】:getline() / strsep() combination causes segmentation faultgetline() / strsep() 组合导致分段错误
【发布时间】:2018-06-09 05:26:37
【问题描述】:

我在运行下面的代码时遇到了分段错误。

它基本上应该读取一个超过 3M 行的.csv 文件,然后执行其他操作(与问题无关),但在 207746 次迭代后它返回分段错误。如果我删除 p = strsep(&line,"|"); 并只打印整个 line 它将打印 >3M 行。

int ReadCSV (int argc, char *argv[]){

    char *line = NULL, *p;
    unsigned long count = 0;

    FILE *data;
    if (argc < 2) return 1;
    if((data = fopen(argv[1], "r")) == NULL){
        printf("the CSV file cannot be open");
        exit(0);
    }


    while (getline(&line, &len, data)>0) {

        p = strsep(&line,"|");  

        printf("Line number: %lu \t p: %s\n", count, p);
        count++;
    }

    free(line);
    fclose(data);

    return 0;
}

我猜这与内存分配有关,但不知道如何解决。

【问题讨论】:

  • #207746 行中是否至少有一个分隔符?如果没有,strsep() 的行为会有所不同。
  • strsep 修改了它的第一个参数,所以你失去了第一次分配给你的缓冲区getline 的开始。不要那样做,如果必须使用strsep,请使用单独的临时文件。并检查 p 不是 NULL。 len 在哪里声明?在你打电话给getline之前是零吗?

标签: c segmentation-fault getline strsep


【解决方案1】:

查看表达式getline(&amp;line, &amp;len, data) 并阅读manpage

如果在调用前 *line 设置为 NULL 并且 *len 设置为 0,那么 getline() 将分配一个缓冲区来存储该行。这个缓冲区 即使 getline() 失败,也应该由用户程序释放。

这应该是您第一次循环时的情况(虽然我们看不到 len 的声明位置,但我们假设您的真实代码正确执行此操作)

或者,在调用 getline() 之前,*line 可以包含一个 指向 malloc(3) 分配的缓冲区的指针 *len 字节大小。如果 缓冲区不够大,无法容纳行,getline() 调整它的大小 使用 realloc(3),根据需要更新 *line 和 *len。

好的,所以如果line != NULL 它必须指向由malloc 分配的大小为len 的缓冲区。您第一次调用getline(如上)分配的缓冲区满足这一点。

注意line某个地方指向该缓冲区是不够的,它必须是开始。

现在查看表达式strsep(&amp;line,"|") 并阅读manpage

... 这个标记通过用 a 覆盖分隔符来终止 空字节('\0'),并且 *line 被更新为指向过去的标记

因此,第一个参数 (line) 已更改,因此您可以使用相同的第一个参数再次调用 strsep,并获得 next 标记。这意味着line 不再是getline 的有效参数,因为它不是malloc'd 缓冲区的开始(现在len 的长度也是错误的)。

在实践中,要么

  1. getline 将尝试将 len 字节读入您给它的缓冲区,但由于您将 line 推进了第一个令牌的长度,它会注销您分配的块的末尾。这可能只会损坏堆而不是立即死亡
  2. getline 会尝试重新分配你给它的缓冲区,但由于它不是一个有效的分配块,你会再次受到堆损坏。

虽然我们在这里,但您也不要检查 p 是否为非 NULL,但损坏的 line 是主要问题。

哦,如果您认为问题与分配有关,请尝试使用 valgrind - 它通常会在出现问题的那一刻发现。

【讨论】:

    【解决方案2】:

    getlinestrsep 的组合经常会引起混淆,因为这两个函数都会更改您通过指针作为初始参数传递它们的指针。如果再次将通过strsep 的指针传递给getline,则在第二次迭代中存在未定义行为的风险。

    考虑一个例子:getline 分配 101 个字节给line,并读入一个 100 个字符的字符串。请注意,len 现在设置为 101。您调用 strsep,它会在字符串中间找到 '|',因此它将 line 指向以前的 line+50。现在您再次拨打getline。它看到另外 100 个字符的行,并得出结论,可以将其复制到缓冲区中,因为 len 仍然是 101。但是,由于 line 现在指向缓冲区的中间,因此写入 100 个字符成为未定义的行为.

    在调用strsep之前复制line

    while (getline(&line, &len, data)>0) {
        char *copy = line;
        p = strsep(&copy, "|");  
        printf("Line number: %lu \t p: %s\n", count, p);
        count++;
    }
    

    现在,您传递给 getlineline 在循环迭代之间被保留。

    【讨论】:

      猜你喜欢
      • 2020-12-31
      • 2021-03-26
      • 1970-01-01
      • 1970-01-01
      • 2018-01-18
      • 1970-01-01
      • 2021-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多