我编写了一个简单的库文件,其中包含从任意大小的文件中读取行的函数。该函数通过传入堆栈分配的缓冲区和大小来调用,但如果该行太大,则会初始化一个特殊的堆分配缓冲区并用于传回更大的行。
这个堆分配的缓冲区是函数范围的并且声明为静态的,当然在开始时初始化为 NULL。我在函数的开头写了一些检查,以检查堆缓冲区是否为非空;如果是这种情况,那么上一行读取的内容太长了。自然地,我释放堆缓冲区并将其设置回 NULL,认为下一次读取可能只需要填充堆栈分配的缓冲区(即使在我们的应用程序中,也很少会看到超过 1MB 长的行!)。
我已经仔细阅读代码并通过仔细阅读和运行一些测试来相当彻底地测试它。我有理由相信以下不变量得到维护:
- 如果只需要堆栈缓冲区,则函数返回时堆缓冲区将为空(并且不会泄漏任何内存)。
- 如果堆缓冲区不为空,因为它是需要的,它将在下一个函数调用时被释放(并且可能在下一行需要时被重用)。
但我想到了一个潜在的问题:如果文件的最后一行太长,那么由于该函数可能不会被再次调用,我不确定我是否有办法释放堆缓冲区——毕竟,它是函数范围的。
所以我的问题是,如何在函数范围的静态指针中释放动态分配的内存,理想情况下无需再次调用该函数? (最好不要将其设为全局变量!)
代码可应要求提供。 (对不起,我现在还没有访问权限。我希望这个问题足够笼统并且解释得很好,所以不需要它,但无论如何请随意打消我的想法!)
编辑:我觉得我应该添加一些关于函数用法的注释。
此特定函数以从文件中连续读取行的形式使用,然后立即复制到 POD 结构中,每个结构一行。这些是在读取文件时在堆上创建的,并且这些结构中的每一个都有一个 char 指针,其中包含文件中的一行(清理版本)。为了使这些持续存在,必须已经发生复制。 (这是许多答案中提出的主要反驳之一——哦不,该行需要被复制,哦亲爱的)。
至于多线程,正如我所说,这是为串行使用而设计的。不,它不是线程安全的,但我不在乎。
不过还是感谢大家的回复!当我有时间时,我会更彻底地阅读它们。目前,我倾向于传递一个额外的指针或重新设计函数,以便当 fgets
显示 EOF 时,我可能只是在那里构建释放逻辑,希望用户不需要担心它。
最佳答案
如果可以更改函数,我建议更改函数接口(interface)本身。我知道您已经花了很多时间调试和测试它,但是您当前的实现存在一些问题:
- 它不是线程安全的,
- 用户无法控制数据,因此如果他以后需要它,他必须复制它,很可能是在一个缓冲区中,该缓冲区将被
malloc()
ed,从而使您获得的任何优势都无效通过在您的函数中选择性地使用malloc()
, - 最重要的是,正如您所发现的,用户必须对最后一行执行特殊操作。
您的用户不应该担心您的函数的实现奇怪,他们应该能够“直接使用它”。
除非您出于教育目的这样做,否则我建议您查看 this page ,它有一个“从流中读取任意长行”的实现,并链接到其他此类实现(每个实现都与其他实现略有不同,因此您应该能够找到您喜欢的一个)。
根据您的编辑,MT 安全不是必需的,复制总是会发生。因此,最明显的设计是以下两者之一:
- 让用户提供一个
char **
,它指向您的函数将使用malloc()
和realloc( )
(如果需要)。完成后free()
是用户的责任。这样,用户就不必再次复制数据,因为他可以将指针传递到数据的最终目的地。 - 返回一个由你的函数分配的
char *
。同样,用户有责任free()
它。
两者几乎等同。
对于您当前的实现,如果最后一行很长并且没有以换行结尾,您总是可以返回“not end of file”。然后,用户将再次调用您的函数,然后您可以释放缓冲区。就个人而言,我会更喜欢一个允许我阅读任意多行的功能,而不是强制我转到文件末尾。
关于c - 静态函数范围指针和内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2136424/