3

将整数转换为文本时,通常我会创建一个缓冲区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()
3snprintf()

1 使用方程式的编译时间最大缓冲区大小(sizeof(integer_type)*CHAR_BIT*0.302) + 3没有被破坏或改进。的影响<locale.h>已按照@paddy 的建议进行了研究,并且没有区域设置影响整数转换%d %x %u %i。发现如果类型已知为有符号或无符号(如下),则可以对等式进行轻微改进。@paddy 关于“更保守”的警告是个好建议。

2asprintf()确实是一个很好的通用解决方案,但不便携。也许在 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

4 回答 4

3

对我来说看起来不错。您已将小数四舍五入,为负号和空值添加了一个额外的字符,并为良好的度量添加了一个额外的字符。如果您不使用<locale.h>.

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

使用堆栈上的临时数组,您通常不会对几个字节大惊小怪,因为它不太可能影响缓存性能。它当然不会破坏你的记忆。

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

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

于 2013-09-10T00:41:17.960 回答
2

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

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

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

char *p
asprintf(&p, "%XXXX", ...); 
:
:
free(p);
于 2013-09-10T00:12:41.417 回答
2

这些都很好。

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

不幸的是,有些不合规的snprintf()实现无论如何都不起作用。[编辑:它适用于jxh's answer中的用法,您可以在其中提供大缓冲区;失败的情况是您提供的缓冲区太小而无法确定需要多少空间。]

于 2013-09-10T00:48:45.300 回答
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(). 如果您使用较大整数类型的大多数数字实际上采用的值远小于最大值,那么这种技术可以为您节省大量成本(取决于您创建的数组数量)。

于 2013-09-10T01:17:27.793 回答