getline() / strsep() 组合导致分段错误

getline() / strsep() combination causes segmentation fault

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

它基本上应该读取一个超过 3M 行的 .csv 文件,然后再做其他事情(与问题无关),但在 207746 次迭代后它 returns 出现分段错误。如果我删除 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;
}

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

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 在循环迭代之间保留。

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

If *line is set to NULL and *len is set 0 before the call, then getline() will allocate a buffer for storing the line. This buffer should be freed by the user program even if getline() failed.

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

Alternatively, before calling getline(), *line can contain a pointer to a malloc(3)-allocated buffer *len bytes in size. If the buffer is not large enough to hold the line, getline() resizes it with realloc(3), updating *line and *len as necessary.

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

请注意,line 某处指向 缓冲区还不够好,它必须是开头。

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

... This token is terminated by overwriting the delimiter with a null byte ('[=33=]'), and *line is updated to point past the token

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

实际上,要么

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

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

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