格式化字符串

Time: 十月 23, 2008
Category: Programming practices

这是在 mozilla open source 代码里发现的一个问题,目前还不确信是不是个BUG

c99引入了uintptr_t,uintmax_t 这样的标准数据类型,我们知道 printf() 函数对于常见的数据类型都有一个对应的 length modifier 用于格式化输出,int 的是 d,ptrdiff_t 的是 t,但是对于非标准的 pid_t,uintmax_t 等就没有对应的 length modifier 了。在c89的世界里,解决这种问题的常规化方案是强制类型转换到尽可能大的整数类型,比如这个 pid_t,POSIX 只要求它是一个 signed integer type,所以转化成 long 就OK了:

C++代码
  1. pid_t pid;
  2. printf("%ldn", (long )pid);

不过,c99新引了另一种方式,就是使用宏来替代 length modifier,例如 uint32_t,uintmax_t 可以使用 PRIu32 和 PRIuMAX,这些宏可以在 <inttypes.h> 中看到其定义(在 glibc 源代码中对应 sysdeps/generic/inttypes.h)。有兴趣的可以直接去看一下源代码,其实就是对已有的 format conversion 进行的宏封装。显然,第二种方式更优雅,但最近发现了一种更加优秀的方式,虽然看起来绕了一些弯路

C++代码
  1. /* Make sure BUFSIZE is large enough. */
  2. #define UMAX2S_BUFSIZE 21
  3. static char *
  4. umax2s(uintmax_t x, char *s)
  5. {
  6.     unsigned i;
  7.     /* LINTED */
  8.     assert(sizeof(uintmax_t) <= 8);
  9.     i = UMAX2S_BUFSIZE - 1;
  10.     s[i] = '�'';
  11.     do {
  12.         i--;
  13.         s[i] = "0123456789"[x % 10];
  14.         x /= (uintmax_t)10LL;
  15.     } while (x > 0);
  16.     return (&s[i]);
  17. }

这里以 uintmax_t 为例,逐个逐个的将整数的末尾转换成字符保存在缓冲区里,然后全体右移,小测一下,以数字 2334532234523为例,可以看到程序是怎么处理末位字符的

不过要注意 uintmax_t 是无符号整形,如果要处理有符号的,要剖离符号位使之变成绝对值来处理,这里只是提一下,其他自由发挥即可。另外,mozilla 里也是使用了一样的方法。

We don’t want to depend on vsnprintf() for production builds, since that can cause unnecessary bloat for static binaries.  umax2s() provides minimal integer printing functionality, so that malloc_printf() use can be limited to MALLOC_STATS code.

C++代码
  1. /* LINTED */
  2.  assert(sizeof(uintmax_t) <= 8);
  3.  i = UMAX2S_BUFSIZE - 1;
  4.  s[i] = '�'';
  5.  do {
  6.      i--;
  7.      s[i] = "0123456789"[(int)x % 10];
  8.      x /= (uintmax_t)10LL;
  9.  } while (x > 0);

我之所以觉得有问题是上面的第8行代码,mozilla 将 x 强制转换成了 int 型,因为 uintmax_t 类型要比 int 型所能表达的范围要大,如果 uintmax_t 值大于 int 所表示的范围而且相对 int 的最高位恰好是 1,强制转换后就是负数,负数对10取余,结果会是什么呢?大概是个BUG吧

Leave a Comment