3

运行下面的代码时出现分段错误。

它基本上应该读取一个.csv超过 3M 行的文件,然后再做其他事情(与问题无关),但在 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;
}

我想这与内存分配有关,但无法弄清楚如何解决它。

4

2 回答 2

6

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

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

line在调用之前制作一份副本strsep

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

现在line您传递给getline在循环迭代之间保留。

于 2017-12-28T17:01:28.460 回答
1

查看表达式getline(&line, &len, data)并阅读手册页

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

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

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

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

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

现在查看表达式并strsep(&line,"|")阅读手册

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

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

在实践中,无论是

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

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

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

于 2017-12-28T17:15:25.103 回答