C 有没有更好的方法来调整打印整数的缓冲区大小?

C 有没有更好的方法来调整打印整数的缓冲区大小?,c,printf,buffer,C,Printf,Buffer,我想为sprintfing一个整数(在本例中为unsigned int)创建一个缓冲区。一种简单而误导的方法是: char buf[11]; sprintf(buf, "%u", x); 如果我们知道unsigned int的宽度最多为33位,那么这非常有效,但是如果我们想要容纳所有的wierd架构呢?我能想到的最好办法是: char buf[(CHAR_BIT*sizeof(unsigned)+5)/3]; sprintf(buf, "%u", x); 我很有信心,这将在任何实施工作C

我想为
sprintf
ing一个整数(在本例中为
unsigned int
)创建一个缓冲区。一种简单而误导的方法是:

char buf[11];

sprintf(buf, "%u", x);
如果我们知道
unsigned int
的宽度最多为
33
位,那么这非常有效,但是如果我们想要容纳所有的wierd架构呢?我能想到的最好办法是:

char buf[(CHAR_BIT*sizeof(unsigned)+5)/3];

sprintf(buf, "%u", x);

我很有信心,这将在任何实施工作
CHAR\u BIT*sizeof(unsigned)
无符号
中的位数(上限)。然后我加上2,除以3,找到八进制表示中的位数,最后为NUL终止加上1。这意味着缓冲区足以以八进制打印数字,并且由于十进制表示法使用的数字不超过八进制,因此对于十进制表示法也足够了


有更好的方法吗?我所说的“更好”是指无论
x
的值是多少,都能产生更小的缓冲区,而不会有缓冲区溢出的风险(即使面对恶意构造但符合标准的实现)。我的方法将为32位
无符号
生成一个12-
char
缓冲区,尽管
11
就足够了。

编译不同的相关注释,最显著的是:

  • 这个
  • 的评论对此做了很好的总结:“n个二进制数字需要ceil(n*ln(2)/ln(10))≈ ceil(n*0.301)”
你有你的答案:

#define MAX_DECIMAL_SIZE(x)  ((size_t)(CHAR_BIT * sizeof(x) * 302 / 1000) + 1)

char buffer[MAX_DECIMAL_SIZE(unsigned int) + 1];
sprintf(buffer, "%u", x);

/* MAX_DECIMAL_SIZE(uint8_t) => 3
 * MAX_DECIMAL_SIZE(uint16_t) => 5
 * MAX_DECIMAL_SIZE(uint32_t) => 10
 * MAX_DECIMAL_SIZE(uint64_t) => 20
 * MAX_DECIMAL_SIZE(__uint128_t) => 39 */
302/1000
来自
ln(2)/ln(10)
,四舍五入。您可以从
0.3010299956639812…
中获取更多的数字以获得更高的精度,但在使用32768位左右的系统之前,这是一种过度使用。连分数也起作用(见下面Martin R的评论)。无论哪种方法,都要小心
CHAR\u BIT*sizeof(x)*
不要太大,并且记住结果必须大于实际值


如果你真的坚持八进制表示,只需将乘法器改为
ln(2)/ln(8)
(这就是⅓) 您将获得所需的八进制数字。

如果您对动态分配的内存没有问题,您可以使用
asprintf
。此函数将分配适当的内存量来保存字符串

char *buf;
int result = asprintf(&buf, "%u", x);
if (result == -1) {
    perror("asprintf failed");
} else {
    ...
    free(buf);
}

如果数组应该在所有现实世界的计算机上工作,那么
int
可以是2或4个字节。没有其他选择(*)

这意味着它可以容纳的最大值为65535或4.29*10^9。这意味着数组需要容纳5或10位数字

这反过来意味着数组可以声明为:

 char buf [sizeof(int)/2 * 5 + 1];
它将扩展到5+1或10+1,覆盖世界上所有已知的计算机

一个更好、更专业的解决方案是使用
stdint.h
中的固定宽度类型。这样,您就可以随时准确地知道需要多少位数,便于携带,从而可以消除上述“幻数”


(*)在C语言标准理论中,
int
可以是2个字节或更大的字节。但是,由于现实世界中永远不会存在这样的系统,因此没有必要让代码移植到它们身上。C语言已经引入了
long
long
,这是有原因的

那些担心移植到异国情调、完全虚构的系统的人被误导了,他们大多是喜欢摆姿势的C语言律师。你不应该让这些理论上的废话影响你为现实世界的计算机编写专业程序的方式


编辑

“C语言律师poser”版本如下所示:

#include <stdio.h>
#include <limits.h>

#define STRINGIFY(s) #s
#define GET_SIZE(n) sizeof(STRINGIFY(n))
#define DIGITS(type) _Generic((type), unsigned int: GET_SIZE(INT_MAX) )

int main(void) 
{
  unsigned int x;
  char buf [DIGITS(x)];

  printf("%zu", sizeof(buf));

  return 0;
}
#包括
#包括
#定义字符串化
#定义GET_SIZE(n)sizeof(STRINGIFY(n))
#定义数字(类型)\通用((类型),无符号整数:获取大小(整数最大值))
内部主(空)
{
无符号整数x;
字符buf[位数(x)];
printf(“%zu”,sizeof(buf));
返回0;
}

注意,这假设
INT\u MAX
扩展为一个整数常量,而不是一个表达式。当使用
UINT\u MAX
时,我从GCC得到了非常奇怪的结果,因为该宏在内部定义为一个表达式,在限制内。h.

需要这样做的情况很少见:可能一些微控制器de,通过某些串行协议传输值。在这种情况下,使用任何
printf()
函数族都可能会增加最终二进制文件的大小

(在典型的C开发环境中,C库是动态加载的,避免使用标准C库函数绝对没有好处。它不会减少程序大小。)

所以,如果我需要这样的代码,我可能会写一个头文件

#if defined(INTTYPE) && defined (UINTTYPE) && defined (FUNCNAME)

#ifndef DECIMAL_DIGITS_IN
#define DECIMAL_DIGITS_IN(x) ((CHAR_BIT * sizeof (x) * 28) / 93 + 2)
#endif

char *FUNCNAME(const INTTYPE value)
{
    static char buffer[DECIMAL_DIGITS_IN(value) + 1];
    char       *p = buffer + sizeof buffer;
    UINTTYPE    left = (value < 0) ? -value : value;

    *(--p) = '\0';
    do {
        *(--p) = '0' + (left % 10);
        left /= 10;
    } while (left > 0);

    if (value < 0)
        *(--p) = '-';

    return p;
}

#undef FUNCNAME
#undef INTTYPE
#undef UINTTYPE

#endif

在更普通的代码中,最好的方法是使用缓冲区大小“猜测”来避免缓冲区溢出

unsigned int x;

char  buffer[256];
int   len;

len = snprintf(buffer, sizeof buffer, "Message with a number %u", x);
if (len < 0 || (size_t)len >= sizeof buffer - 1) {
    /* Abort! The buffer was (almost certainly) too small! */
} else {
    /* Success; we have the string in buffer[]. */
}
其思想是,您可以跨多个
dynamic\u printf()
调用重用相同的动态缓冲区:

    char   *data = NULL;
    size_t  size = 0;
    size_t  len;

    /* Some kind of loop for example */

        len = dynamic_printf(&data, &size, "This is something I need in a buffer");
        if (errno) {
            /* Abort! Reason is strerror(errno) */
        } else {
            /* data is non-NULL, and has len chars in it. */
        }

    /* Strings are no longer used, so free the buffer */
    free(data);
    data = NULL;
    size = 0;
请注意,在调用之间运行
free(data);data=NULL;size=0;
是完全安全的。
free(NULL)
不执行任何操作,如果缓冲区指针为
NULL
且大小为零,则函数只会动态分配一个新的缓冲区


在最坏的情况下(当缓冲区不够长时),函数会“打印”字符串两次。在我看来,这是完全可以接受的。

OP的解决方案最低限度地满足了设计目标

有没有更好的方法来调整打印整数的缓冲区大小

即使是一个简短的分析也表明,
无符号
所需的位数在打印十进制时每一个值位都会增加一倍
log10(2)
或大约0.30103…在打印八进制时每一个值位都会增加一倍
1/3
。OP的代码使用三分之一或0.33333的系数

unsigned x;
char buf[(CHAR_BIT*sizeof(unsigned)+5)/3];
sprintf(buf, "%u", x);
考虑事项:

  • 如果缓冲区紧密性问题是真的,那么十进制打印的缓冲区应该比oct打印单独考虑
        char   *data = NULL;
        size_t  size = 0;
        size_t  len;
    
        /* Some kind of loop for example */
    
            len = dynamic_printf(&data, &size, "This is something I need in a buffer");
            if (errno) {
                /* Abort! Reason is strerror(errno) */
            } else {
                /* data is non-NULL, and has len chars in it. */
            }
    
        /* Strings are no longer used, so free the buffer */
        free(data);
        data = NULL;
        size = 0;
    
    unsigned x;
    char buf[(CHAR_BIT*sizeof(unsigned)+5)/3];
    sprintf(buf, "%u", x);
    
    #define LOG10_2_N 28
    #define LOG10_2_D 93
    //                              1 for the ceiling                          1 for \0
    #define UINT_BUFFER10_SIZE(type) (1 + (CHAR_BIT*sizeof(type)*LOG10_2_N)/LOG10_2_D + 1)
    
    
    unsigned x;
    char bufx[UINT_BUFFER10_SIZE(x)];
    sprintf(bufx, "%u", x);
    
    size_t z;
    char bufz[UINT_BUFFER10_SIZE(z)];
    sprintf(bufz, "%zu", z);
    
    #define INT_BUFFER_SIZE(type) (1+1+ (CHAR_BIT*sizeof(type)-1)*LOG10_2_N)/LOG10_2_D + 1)
    
    #define INT_STRING_SIZE(x)  (1 /* sign */ + CHAR_BIT*sizeof(x) + 1 /* \0 */)
    
    int x = INT_MIN;
    char buf[INT_STRING_SIZE(x)];
    my_itoa(buf, sizeof buf, x, 2);
    puts(buf); --> "-10000000000000000000000000000000"  (34 char were needed)