外部数组srclns应该保留文本文件中的每个读行。但读取它的内容之后似乎读行是空字符串。我在下面的代码中遗漏了什么?
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#define MAXSRC 20
char *srclns[MAXSRC]; /* source lines */
size_t read_lines(char *path)
{
FILE *stream;
ssize_t read;
char *lnptr;
size_t n;
size_t count;
stream = fopen(path, "r");
lnptr = NULL;
n = 0;
count = 0;
if (!stream) {
fprintf(stderr, "Can't open source '%s'n", path);
exit(EXIT_FAILURE);
}
while ((read = getline(&lnptr, &n, stream)) != -1) {
srclns[count++] = lnptr;
}
free(lnptr);
fclose(stream);
return count;
}
int main()
{
size_t n = read_lines("foo.txt");
for (size_t i = 0; i<n; i++)
printf("%zu %sn", i, srclns[i]);
exit(EXIT_SUCCESS);
}
只打印行号后面似乎是空字符串的行号:
0
1
2
3
4
5
所以从我可以看到,不仅你的程序不工作,但它可能有内存泄漏。这是由于getline使用动态分配的行为。
让我们仔细看看你的程序是做什么的,特别是while ((read = getline(&lnptr, &n, stream)) != -1)
循环:
getline将与char**
类型的&lnptr
一起工作。
-
指针为
NULL
它将在堆上分配足够的内存(动态)来存储正在读取的行。 -
如果指针不是
NULL
那么它应该指向大小为n
的缓冲区- 如果缓冲区足够大(大于或等于行长度),用于存储字符串。
- 如果缓冲区太小然后由
getline
重新分配内存,因此有足够大的缓冲区可用。在重新分配时,n
被更新为新的缓冲区大小。在某些情况下,重新分配将意味着必须修改lnptr
,并且将会修改。(如果当前缓冲区之后没有足够的连续可用内存,可能会发生这种情况。在这种情况下,内存将被分配到堆上的其他地方。如果这是你感兴趣的,我建议你研究是因为动态内存分配是一个相当复杂的话题,否则只知道指针可能会改变,这就足够了。
现在这是你的程序的问题(至少这是我可以从我所拥有的信息中推断出来的)。我可能错了,但这似乎是最合理的解释):
-
在循环的第一次迭代中
lnptr
为NULL
。因此,getline在堆上分配内存并存储行,并更新lnptr
以指向新分配的缓冲区。 -
在循环内你将指针存储到
srclns[0]
中分配的缓冲区。 - 在后续迭代中缓冲区被getline覆盖,可能会调整大小,你仍然存储指针到相同的缓冲区
srclns[count]
。 - 循环后你释放缓冲区并丢弃
srclns
中每个指针指向的内存。 - 打印你很可能读到一个无效的内存区(这是你刚刚释放的指针指向的区域),幸运的是,它似乎以一个终止字符开始(你的文件的最后一行可能是一个空行,没有任何主动改变这个内存区后的自由…)
如何修复:您可以显式地使用malloc
和/或calloc
处理动态分配,但这似乎有点复杂,正如前面所示,getline
可以为您处理它。我的建议如下:
- 设置
srclns
中的所有元素为NULL
for(int i = 0; i < MAXSRC; ++i) { srclns[i] = NULL; }
- 然后修改while循环,在每次迭代中传递一个新的
srclns
元素。每次调用getline
都会看到一个NULL
指针,从而分配内存并更新srclns
的单元格以指向它。这个实现的好处是你永远不会超出srclns
的界限:for(int i = 0; i < MAXSRC; ++i) { n = 0 if(getline(&srclns[i], &n, stream) == -1) { break; // We get out if we reached OEF } }
- 在您为
printf
访问它之后,在main中释放所有分配的内存for(int i = 0; i < MAXSRC; ++i) { if(srclns[i] != NULL) { free(srclns[i]); } }
- 调整。我没有对代码进行测试,所以我可能犯了一些错误……请随意修改。您可能还需要调整代码以满足您的需求。
函数getline
只会分配内存,如果lnptr
是NULL
(ref)。这是第一次迭代的情况,但之后需要将其重置为NULL
:
while ((read = getline(&lnptr, &n, stream)) != -1) {
srclns[count++] = lnptr;
lnptr = NULL;
}
否则,lnptr
仍然指向在第一次迭代中分配的内存,并且getline
将反复尝试写入该位置。
即使它不是问题的原因,分配的内存也应该被释放。例如,通过在exit(EXIT_SUCCESS)
之前添加这些行:
for (size_t i = 0; i<n; i++)
free(srclns[i]);
是否使用getline
是一个很好的实践是另一个你可能想要研究的讨论。它不是最便携的解决方案。