如何在`printf()`中使用"zd"说明符?

21

寻求有关在 printf() 中使用 "zd" 的澄清。

当然,对于 C99 及更高版本,以下用法是正确的。

void print_size(size_t sz) {
  printf("%zu\n", sz);
}

根据 C 规范,似乎允许使用 printf("%zd\n", sz),具体取决于如何解读:

7.21.6.1 函数 fprintf

z 指定后面的 diouxX 转换说明符适用于 size_t 或相应的带符号整数类型参数;或者指定后面的 n 转换说明符适用于指向带符号整数类型(与 size_t 相应)的指针参数。C11dr §7.21.6.1 7

这应该被解读为:

  1. "z 指定后面的 d ... 转换说明符适用于 size_t 或相应的带符号整数类型参数 ... " (两个类型),以及 "z 指定后面的 u ... 转换说明符适用于 size_t 或相应的带符号整数类型参数 ... " (两个类型)

还是

  1. "z 指定后面的 d ... 转换说明符适用于相应的带符号整数类型参数 ... " (仅限带符号类型),以及 "z 指定后面的 u ... 转换说明符适用于 size_t"(仅限无符号类型)。

我一直使用 #2 的定义,但现在不太确定了。

哪个是正确的,1、2 还是其他什么?

如果 #2 是正确的,请举一个可以使用 "%zd" 的类型的例子。

2个回答

30

printf 使用 "%zd" 格式要求提供与无符号类型 size_t 相对应的有符号类型的参数。

标准 C 并未为此类型提供名称或良好的确定方式。例如,如果 size_t 是 typedef 为 unsigned long,那么 "%zd" 需要提供一个 long 类型的参数,但这并不是可移植的假设。

标准要求相应的有符号和无符号类型在表示非负值的情况下具有相同的表示方法。一条脚注指出,这意味着它们可以作为函数参数互换使用。因此:

size_t s = 42;
printf("s = %zd\n", s);
应该工作,并应该打印“42”。它将解释无符号类型size_t的值42,就好像它是相应有符号类型的值。但实际上没有什么充分的理由这样做,因为"%zu"也是正确和明确定义的,而不需要诉诸于其他语言规则。并且"%zu"适用于所有size_t类型的值,包括那些超出相应有符号类型范围之外的值。
最后,POSIX在头文件<unistd.h><sys/types.h>中定义了一种类型ssize_t。虽然POSIX没有明确说明,但ssize_t很可能是与size_t对应的有符号类型。
因此,如果你编写特定于POSIX的代码,则"%zd"是(可能)用于打印ssize_t类型值的正确格式。
更新:POSIX 明确说明ssize_t不一定是size_t的有符号版本,因此编写假定其为此版本的代码是不明智的:

@MK:很奇怪。它显示你在2小时前编辑了我的答案(我不反对),并删除了正文中的2个字符,但我没有看到任何更改。 - Keith Thompson
5
@KeithThompson 是的,我不得不这样做。因为已经过去了两个小时,所以它不允许我用“up vote”替换“downvote”,这完全是愚蠢的,因为我可以编辑和更改。我在meta上询问是否可以取消只有有足够声望的人才能编辑的限制,但没有任何进展。更改是删除链接注释前面的额外空行,这些注释是不显示的,所以你看不到它(我真的试图想出一个有意义的编辑,但答案已经很好了 :) - MK.
好的,没问题。只是我觉得奇怪的是我在编辑历史中看不到任何更改。 - Keith Thompson
“标准要求相应的有符号和无符号类型在表示两种类型都可表示的非负值时使用相同的表示法。” --> 这是因为C11 §6.5.2.2 6吗? - chux - Reinstate Monica
3
@chux: 6.2.6.2p5: "对于带符号整数类型的有效(非陷阱)对象表示,其中符号位为零,则是相应的无符号类型的有效对象表示,并且应该表示相同的值。" 我刚刚注意到这并未指定另一个方向,尽管这需要一种奇特的表示方式,即用多个表示来表示相同的非零值才能打破对称性。 - Keith Thompson

0
根据我所做的小测试,“zd”始终为真,但“zu”对于负数不起作用。
测试代码:
  #include <stdio.h>
  int main (void)
  {  int i;
     size_t uzs = 1;
     ssize_t zs = -1;
    for ( i= 0; i<5 ;i++, uzs <<= 16,zs <<= 16 )
    {
       printf ("%zu\t", uzs); /*true*/
       printf ("%zd\t", uzs); /*true*/

       printf ("%zu\t", zs); /* false*/
       printf ("%zd\n", zs); /*true*/
    }
    return 0;
}

3
这里提到的“true”和“false”是关于什么的? - Dietrich Epp
感谢测试,但是当uzs很大时,某些“'zd'始终为真”的情况将失败,例如SIZE_MAX - chux - Reinstate Monica
6
测试(Testing)不是确定 C 语言中某些事情是否合法的正确方式。 - user694733
请注意,当给定负值时,%u始终会以另一种方式(以明确定义的方式)报告其他内容。 - Phlarx
1
@Phlarx "%u 会总是以另一种(明确定义的)方式报告" --> 它并没有被明确定义,但通常这种“未定义行为”是不可抗拒的。 - chux - Reinstate Monica
@chux 我的意思是“明确定义”,即任何特定无符号数的位模式都是明确定义的。无论如何,从有符号到无符号的转换可能会根据体系结构是可预测和一致的。我不会说是未定义的,因为C标准只指出了2的补码、1的补码和符号-幅值作为可能性。更像是实现定义或体系结构定义。但这在语义上已经没有意义了。 - Phlarx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接