C语言 保存getline()输出到外部数组



外部数组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,并且将会修改。(如果当前缓冲区之后没有足够的连续可用内存,可能会发生这种情况。在这种情况下,内存将被分配到堆上的其他地方。如果这是你感兴趣的,我建议你研究是因为动态内存分配是一个相当复杂的话题,否则只知道指针可能会改变,这就足够了。

现在这是你的程序的问题(至少这是我可以从我所拥有的信息中推断出来的)。我可能错了,但这似乎是最合理的解释):

  • 在循环的第一次迭代中lnptrNULL。因此,getline在堆上分配内存并存储行,并更新lnptr以指向新分配的缓冲区。
  • 在循环内你将指针存储到srclns[0]
  • 中分配的缓冲区。
  • 在后续迭代中缓冲区被getline覆盖,可能会调整大小,你仍然存储指针到相同的缓冲区srclns[count]
  • 循环后你释放缓冲区并丢弃srclns中每个指针指向的内存。
  • 打印你很可能读到一个无效的内存区(这是你刚刚释放的指针指向的区域),幸运的是,它似乎以一个终止字符开始(你的文件的最后一行可能是一个空行,没有任何主动改变这个内存区后的自由…)

如何修复:您可以显式地使用malloc和/或calloc处理动态分配,但这似乎有点复杂,正如前面所示,getline可以为您处理它。我的建议如下:

  1. 设置srclns中的所有元素为NULL
    for(int i = 0; i < MAXSRC; ++i)
    {
    srclns[i] = NULL;
    }
    
  2. 然后修改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
    }
    }
    
  3. 在您为printf访问它之后,在main中释放所有分配的内存
    for(int i = 0; i < MAXSRC; ++i)
    {
    if(srclns[i] != NULL)
    {
    free(srclns[i]);
    }
    }
    
  4. 调整。我没有对代码进行测试,所以我可能犯了一些错误……请随意修改。您可能还需要调整代码以满足您的需求。

函数getline只会分配内存,如果lnptrNULL(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是一个很好的实践是另一个你可能想要研究的讨论。它不是最便携的解决方案。

最新更新