fprintf在底层使用malloc()吗?

21

我希望有一个最简的o-damn-malloc-just-failed处理程序,它可以将一些信息写入文件(可能只是标准错误)。 我更喜欢使用fprintf()而不是write(),但如果fprintf()本身尝试进行内存分配,则这将失败。

是否存在某种保证,无论是在C标准中还是仅在glibc中,fprintf都不会这样做?


重要提示!!!在malloc处理程序中,为了确保安全,您应该执行以下操作:a)重新注册malloc处理程序(所有4个)到标准处理程序 b)进行日志记录等操作。 c)根据调用者的需要执行实际的内存重新分配/释放。 d)恢复您的处理程序。 e)然后继续退出。这具有重要优点,即您仍然可以在处理过程中执行任何所需操作,并且无需担心它们是否正在使用malloc。我曾经这样做过。文件太慢了,我使用了一个套接字。 :) - Ethouris
只是为了澄清,我实际上没有注册任何malloc处理程序。我只是有一些低级函数调用malloc(),当分配失败时,我希望该函数打印一个日志消息。 - Adrian Ratnapala
啊!首先,如果malloc()失败了,你怎么能继续程序呢?我猜你唯一能做的就是在文本中打印预定义的消息并使用write()发送它。通常,现代软件中的分配失败都被视为“在分配失败时调用abort()”。如果这是用于调试/诊断,核心转储将比任何错误消息告诉您更多(除非您当然已经破坏了堆栈)。 - Ethouris
这就是我提出问题的确切动机:在退出之前,我想要可靠地传递一条消息。我想知道是否可以使用 printf() 来实现,或者我必须坚持使用 write() - Adrian Ratnapala
所以,你应该为最坏的情况做好准备 - 也就是说,printf() 很可能会调用 malloc() - Ethouris
不要忘记,即使没有内存可用,如果启用了过度承诺(通常对于Unix / Linux系统而言),malloc通常也不会失败。 - lalala
3个回答

27

不能保证不会发生,但是我见过的大多数实现 tend to use a fixed size buffer for creating the formatted output string (a)(通常使用固定大小的缓冲区来创建格式化的输出字符串)。

就glibc而言(源代码在这里),stdio-common/vfprintf.c内确实调用了malloc,许多printf家族在底层都使用它,所以如果我是你,我不会依赖它。甚至像sprintf这样的字符串缓冲区输出调用,你可能认为不需要它,似乎也会在设置一些棘手的类似于FILE的字符串句柄之后解析到该调用——参见libio/iovsprintf.c

我的建议是编写自己的代码来进行输出,以确保在幕后没有进行任何内存分配(当然希望write本身不会这样做(不像*printf会这样做))。由于你可能不会输出太多的转换内容(可能只是"Dang, I done run outta memory!"),因此对格式化输出的需求应该是有问题的。


(a) C99环境考虑给出了一个提示,即至少有一些早期的实现具有缓冲限制。从我对Turbo C的记忆来看,我认为4K左右是极限,确实,C99规定(在7.19.6.1 fprintf中):

  

任何单个转换产生的字符数都应至少为4095。

(C89的任务是将现有做法编码成标准,而不是创建一种新语言,这也是为什么一些这些最小值/最大值被放入标准的原因之一——它们被带入到后来的标准版本中)。


@DevSolar,通常需要某种缓冲区,例如将整数和浮点数扩展为其字符表示形式。它不必很大,并且可以分段使用,每个%-项一次,而不是整个输出一次(这将需要更大的缓冲区)。 C99的环境限制表明需要考虑某些实现 - 请参见更新。 - paxdiablo
1
@paxdiablo:不一定。我使用递归实现了printf(),而且没有使用任何缓冲区。 - DevSolar
@DevSolar:我没有看到你的实现,但是我要指出递归使用了一个缓冲区——那个缓冲区就是栈。你仍然会遇到实现限制。 - Billy ONeal
1
@Dev:我不明白这与将一个20个字符宽的块放在堆栈上有什么区别。我并不是说你的解决方案不好。我只是想说,必须要使用缓冲区。递归并不能消除这个问题,它只会让问题更加隐蔽。 - Billy ONeal
1
@DevSolar:我并不是在批评你。我只是说有一个缓冲区在使用中。你的实现对于整数中的每个数字都会在栈上放置至少一个intmax_t和一个指针。这就是一个缓冲区。用递归来隐藏它并不能让缓冲区消失。(而且,对于大于两位数的整数,一个普通的char缓冲区所占用的空间会更小) - Billy ONeal
显示剩余5条评论

8

C标准并不保证fprintf在底层不会调用malloc。实际上,它对覆盖malloc时会发生什么事情也没有任何保证。您应该参考特定C库的文档,或编写自己类似于fprintf的函数,直接进行系统调用,避免任何堆分配的可能性。


谢谢。实际上我并没有覆盖malloc(),只是对它进行了包装。但你的回答原则是一样的。我将不得不执行一个普通的write()。 - Adrian Ratnapala

4
您可以合理确定不会调用malloc的函数是那些由POSIX标记为async-signal-safe的函数。由于malloc不要求是async-signal-safe,而且基本上不可能使其成为可用但效率低下的async-signal-safe函数,因此通常情况下,async-signal-safe函数不能调用它。
话虽如此,我几乎可以确定glibc的printf函数(包括fprintf和甚至snprintf)可以并且将使用malloc来处理某些(全部?)格式字符串。

是的,但覆盖/包装 malloc 总是 非法的(UB),当然... :-) - R.. GitHub STOP HELPING ICE
R - 你说包装malloc是非法的是什么意思?也许你的意思是“在另一个名为malloc的函数中包装malloc”。通过包装,我想提供一个新函数,它返回malloc分配的内存,并提供其他功能。 - Adrian Ratnapala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接