我在运行下面的代码时遇到分段错误。
它基本上应该读取一个超过 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: %sn", count, p);
count++;
}
free(line);
fclose(data);
return 0;
}
我想这与内存分配有关,但无法弄清楚如何解决它。
getline
和strsep
的组合通常会导致混淆,因为这两个函数都会更改指针,即通过指针作为初始参数传递它们。如果将已通过strsep
的指针再次传递到getline
,则在第二次迭代时将面临未定义行为的风险。
考虑一个例子:getline
为line
分配 101 个字节,并在其中读取一个 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(©, "|");
printf("Line number: %lu t p: %sn", count, p);
count++;
}
现在,传递给getline
line
将在循环迭代之间保留。
查看表达式getline(&line, &len, data)
并读取手册页:
如果 *line 设置为 NULL 并且 *len 在呼叫前设置为 0,则 getline() 将分配一个缓冲区来存储该行。 此缓冲区 应该由用户程序释放,即使 getline() 失败。
第一次循环时应该是这种情况(虽然我们看不到在哪里声明len
,让我们假设你的真实代码正确地做到这一点)
或者,在调用 getline() 之前,*line 可以包含一个 指向 malloc(3) 分配的缓冲区 *len 字节大小的指针。 如果 缓冲区不够大,无法容纳该行,getline() 会调整其大小 使用 realloc(3),根据需要更新 *line 和 *len。
好的,所以如果line != NULL
它必须指向按大小len
分配的缓冲区malloc
。第一次调用getline
(如上所述)分配的缓冲区满足此要求。
请注意,line
指向该缓冲区的某个地方是不够的,它必须是开始。
现在查看表达式strsep(&line,"|")
并阅读手册页:
。此令牌通过用 空字节 ('\0'),并且 *line 更新为指向标记
因此,第一个参数 (line
) 被更改,以便您可以使用相同的第一个参数再次调用strsep
,并获取下一个令牌。这意味着line
不再是getline
的有效参数,因为它不是malloc
缓冲区的开始(而且len
长度现在也是错误的)。
在实践中,要么
getline
将尝试将len
字节读取到您提供给它的缓冲区中,但由于您按第一个令牌的长度提前line
,因此它会注销您分配的块的末尾。这可能只会损坏堆,而不是立即死亡getline
会尝试重新分配您提供给它的缓冲区,但由于它不是有效的分配块,因此您再次受到堆损坏。
当我们在这里时,您也不会检查p
是否为非 NULL,但损坏line
是主要问题。
哦,如果您认为问题与分配有关,请尝试使用valgrind
- 它通常会发现事情首先出错的那一刻。