如何改进打印各种整数类型的缓冲区大小?

将整数转换为文本时,通常我会创建一个缓冲区以与sprintf()一起使用来保存任何可能的结果。

 char BigBuffer[50]; sprintf(BugBuffer, "%d", SomeInt); 

我想更节省空间,当然还有便携性,所以替代50 ,找到了替代方案:
(sizeof(integer_type)*CHAR_BIT*0.302) + 3

 // 0.0302 about log10(2) #define USHORT_DECIMAL_BUFN ((size_t) (sizeof(unsigned short)*CHAR_BIT*0.302) + 3) #define INT_DECIMAL_BUFN ((size_t) (sizeof(int) *CHAR_BIT*0.302) + 3) #define INTMAX_DECIMAL_BUFN ((size_t) (sizeof(intmax_t) *CHAR_BIT*0.302) + 3) int main() { char usbuffer[USHORT_DECIMAL_BUFN]; sprintf(usbuffer, "%hu", USHRT_MAX); printf("Size:%zu Len:%zu %s\n", sizeof(usbuffer), strlen(usbuffer), usbuffer); char ibuffer[INT_DECIMAL_BUFN]; sprintf(ibuffer, "%d", INT_MIN); printf("Size:%zu Len:%zu %s\n", sizeof(ibuffer), strlen(ibuffer), ibuffer); char imbuffer[INTMAX_DECIMAL_BUFN]; sprintf(imbuffer, "%" PRIdMAX, INTMAX_MIN); printf("Size:%zu Len:%zu %s\n", sizeof(imbuffer), strlen(imbuffer), imbuffer); return 0; } Size:7 Len:5 65535 Size:12 Len:11 -2147483648 Size:22 Len:20 -9223372036854775808 

所以问题是:
1替代方程有问题吗?
2更好的解决方案? – 因为这种替代方案有点浪费,看起来过于复杂。

[编辑答案 ]

答案提供3种深思熟虑的方法:
1使用缓冲区[类型的最大大小] (选择答案)
2 asprintf()
3 snprintf()

1使用等式(sizeof(integer_type)*CHAR_BIT*0.302) + 3的编译时最大缓冲区大小未被破坏也没有改进。 根据@paddy的建议研究了的影响,没有区域设置影响整数转换%d %x %u %i 。 如果已知类型已签名或未签名(下图),则可以对等式进行略微改进。 @paddy谨慎对待“更保守”是好建议。

2 asprintf()确实是一个很好的通用解决方案,但不是便携式的。 也许在后C11?

3 snprintf()虽然是标准的,但在提供的缓冲区尺寸不足时已经知道了一致的实现问题。 这意味着使用超大缓冲区调用它,然后生成正确大小的缓冲区。 @jxh建议使用一个线程安全的全局暂存缓冲区来形成一个本地正确大小的缓冲区。 这种新方法值得我考虑使用,但最初的问题更多地集中于在s(n)printf()调用保守缓冲区大小之前确定。

signed ((sizeof(integer_type)*CHAR_BIT-1)*0.302) + 3
unsigned (sizeof(integer_type)*CHAR_BIT*0.302) + 2
*28/93 *0.302可用于代替*0.302

这对我来说很好看。 你已经将小数点四舍五入,为负号和空值添加了额外的字符,并为好的度量添加了一个额外的字符。 如果您不使用function,我认为您不必担心数字会更长。

我的问题是你打算用这些做什么。 你只是在堆栈上构建它们,还是将它们放入内存中?

对于堆栈上的临时数组,您通常不会在几个字节上大惊小怪,因为它不太可能影响缓存性能。 它肯定不会打破你的记忆。

如果您打算存储大量这些,您可能需要考虑池化。 但是,您需要考虑池的内存开销。 池的本质意味着您保留的内存比您要使用的内存多。 如果编译64位,则指针为8个字节。 如果你的大多数数字是4个字符长,那么每个数字的8字节指针加上5个字节的存储将抵消任何可能的好处,除了64位数字。

这些只是我的思考过程。 在我看来,你已经很好地修剪了脂肪。 我倾向于保守一点,但这可能主要是偏执狂。 保持简单通常是要走的路,过度思考可能是一个陷阱。 如果你过度思考,那么请考虑原因,并确定这是否是一个实际需要深思熟虑的问题。

asprintf()很方便,它需要一个char **并使用malloc()来获取所需的空间,所以你需要稍后释放它。

无需担心您需要多少空间。

 int asprintf(char **ret, const char *format, ...); char *p asprintf(&p, "%XXXX", ...); : : free(p); 

这是一个计划,扩展了我之前的评论。 您使用INTMAX_DECIMAL_BUFN作为最坏情况的缓冲区大小,并使用它与snprintf()打印。 snprintf()返回的值用于声明与打印字符串所需的数组大小完全匹配的VLA,并将该字符串复制到VLA。

 #define INTMAX_DECIMAL_BUFN ((size_t) (sizeof(intmax_t)*CHAR_BIT*0.302) + 3) char numstr[INTMAX_DECIMAL_BUFN]; int main () { int n = snprintf(numstr, sizeof(numstr), "%hu", USHRT_MAX); char usbuffer[n+1]; strcpy(usbuffer, numstr); printf("Size:%zu Len:%zu %s\n", sizeof(usbuffer), strlen(usbuffer), usbuffer); } 

如果线程安全性是个问题,那么numstr变量可以是线程本地的(使用C.11的_Thread_local ,或者某些编译器特定的扩展类似于GCC的__thread )。

此解决方案的价值取决于堆栈空间的节省是否值得执行strcpy()的额外计算。 如果使用较大整数类型的大多数数字实际上采用远小于最大值的数值,那么这种技术可以为您节省大量成本(取决于您创建的数组数量)。

这些都很好。

我设计了原始的snprintf()函数(在* BSD中,最终使其成为C99),以返回已经打印的字符数,如果缓冲区足够大的话。 如果你有一个符合条件的snprintf()你可以进行两次打印,第一次告诉你要分配多少空间(当然,你必须为终止'\0'添加一个空间)。 这有两个明显的缺点:它必须进行两次格式化,并且它引入了同步问题的可能性,其中第一次调用改变了某些东西(例如,通过%n指令写入),以便第二次调用产生不同的输出。

不幸的是,有一些不合规的snprintf()实现无论如何都不行。 [编辑:它适用于jxh的答案 ,你提供一个大缓冲区; 失败的情况是当你提供一个太小的缓冲区来找出你需要多少空间。]