我很好奇用动态语言实现 printf() 模拟的聪明方法。问题是参数列表可以包含深度嵌套的数据类型,所以我不能轻易知道应该为最终缓冲区分配多少内存。这样做的明显方法是通过参数进行 2 次传递:一个用于估计缓冲区大小,另一个用于实际格式化字符串。有没有更好的方法来做到这一点?
澄清:我正在考虑为 Erlang 编写 C 函数。Erlangs 的数据类型被深度装箱,所以要使用类似 asprintf 的函数,我需要将它们全部拆箱(并可能重写格式字符串),这很昂贵。
我很好奇用动态语言实现 printf() 模拟的聪明方法。问题是参数列表可以包含深度嵌套的数据类型,所以我不能轻易知道应该为最终缓冲区分配多少内存。这样做的明显方法是通过参数进行 2 次传递:一个用于估计缓冲区大小,另一个用于实际格式化字符串。有没有更好的方法来做到这一点?
澄清:我正在考虑为 Erlang 编写 C 函数。Erlangs 的数据类型被深度装箱,所以要使用类似 asprintf 的函数,我需要将它们全部拆箱(并可能重写格式字符串),这很昂贵。
如果printf
您要模拟的是,那么您没有问题,因为您不需要缓冲区,您可以在找到每个令牌时将其写入控制台。
如果您想模仿,sprintf
那么您需要更新您的问题。
对于 sprintf 。. . 使用可扩展的字符串缓冲区。
如果您必须自己动手,请从 512 字节的合理缓冲区开始。当您达到此限制时,分配另一个两倍于先前限制的缓冲区(因此第一次为 1024,第二次为 2048 等),将缓冲区 1 复制到缓冲区 2,将新缓冲区换成旧缓冲区并丢弃/释放/删除/解除分配第一个缓冲区。
然后当你完成时,你分配一个正确长度的字符串,将你的缓冲区复制到字符串并返回它。
如果您不介意将缓冲区作为结果传回,则可以忽略最后一步,即使它在技术上太大并且可能大部分未使用。
由于重新分配,更新
感觉像是次优解决方案。我错了吗?
一句话,是的。
这就是在 C++ STL 和 .Net 框架等主要框架中实现动态列表和数组的方式。如果您考虑一种格式突破 512 字节的可能性,那么突破 1024 或 2048 字节的可能性有多大?如果字符串最终那么长,那就是三个额外的副本。您可能可以应用 80/20 规则,在 80% 的情况下您永远不会达到第一个 512 限制(您可能会将第一个分配减少到 64 个字节并仍然应用 80/20 规则)
现在考虑您的替代方案,对要格式化的项目进行两次传递。
如果你有一个 32 位的 int,你必须把它转换成一个字符串来找出这个字符串的长度。您将为列表中的每个项目额外执行一次,这是为执行转换分配缓冲区、执行转换的时间,然后取消分配字符串。与其他一些数据类型相比,获取 int 的长度相对简单。
还要考虑复杂的对象,如果你得到了这些对象的长度,它们的表示(可能)是通过调用一些.ToString
类似的方法来建立的,这会将它的所有子对象ToString
方法的结果连接在一起,你将再次这样做两次。
考虑到可扩展字符串缓冲区之间的折腾,并额外构建所有字符串以获得它们的长度?我每次都会去缓冲区。
有一个sprintf()
被调用的变体asprintf()
,这个变体 malloc 分配空间来存储结果字符串,而您不必提前知道它的长度。
它作为 C stdlib 的一部分在大多数平台上都可用,您可以使用 或在联机帮助页的此在线副本中阅读更多信息(可能man asprintf
)。:
从手册页:
printf() 系列函数根据如下所述的格式生成输出。printf() 和 vprintf() 函数将输出写入标准输出流 stdout;fprintf() 和 vfprintf() 将输出写入给定的输出流;dprintf() 和 vdprintf() 将输出写入给定的文件描述符;sprintf()、snprintf()、vsprintf() 和 vsnprintf() 写入字符串 s;asprintf() 和 vasprintf() 使用 malloc(3) 动态分配一个新字符串。
添加:
asprintf() 和 vasprintf() 函数将 *ret 设置为指向足够大以保存格式化字符串的缓冲区的指针。这个指针应该被传递给 free(3) 以在不再需要时释放分配的存储空间。如果无法分配足够的空间, asprintf() 和 vasprintf() 将返回 -1 并将 ret 设置为 NULL 指针。
(加粗强调)
这是一个小示例用法:
char *buffer;
asprintf(buffer, "Hello %s", myunknownlengthstring);
这应该分配足够的空间来存储生成的格式化字符串并将其存储在&buffer
. 你将负责释放这个内存,否则它会泄漏,free(buffer)
当不再需要字符串时,一个简单的就足够了。