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

时间:2013-09-09 23:39:45

标签: c data-conversion

将整数转换为文本时,通常我会创建一个 big 缓冲区,以便与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的建议研究了<locale.h>的影响,并且没有区域设置影响整数转换%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

4 个答案:

答案 0 :(得分:3)

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

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

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

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

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

答案 1 :(得分:2)

asprintf()非常方便,它需要一个char **并使用malloc()来获取所需的空间,因此您需要稍后释放它。

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

int asprintf(char **ret, const char *format, ...); 

char *p
asprintf(&p, "%XXXX", ...); 
:
:
free(p);

答案 2 :(得分:2)

这些都很好。

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

不幸的是,有些不符合snprintf()的实现无论如何都无法正常工作。 [编辑:它适用于jxh's answer中的用法,在那里提供一个大缓冲区;失败的情况是当你提供一个太小的缓冲区来找出你需要多少空间。]

答案 3 :(得分:2)

这是一个计划,扩展了我之前的评论。您使用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()的额外计算。如果使用较大整数类型的大多数数字实际上采用远小于最大值的数值,那么这种技术可以为您节省大量成本(取决于您创建的数组数量)。