【问题标题】:Is the %c fprintf specifier required to take an int argument%c fprintf 说明符是否需要采用 int 参数
【发布时间】:2013-07-26 20:36:12
【问题描述】:

在 C99 标准的 第 7.19.6.1 节第 8 段中:

c 如果不存在 l 长度修饰符,则 int 参数将转换为 unsigned char,然后写入生成的字符。

在 C99 标准的 第 7.19.6.1 节第 9 段中:

如果任何参数不是相应转换规范的正确类型,则行为未定义。

  • fprintf 函数是否需要 int 参数?

例如,传递unsigned int 会导致未定义的行为:

unsigned int foo = 42;

fprintf(fp, "%c\n", foo); /* undefined behavior? */

这让我很担心,因为实现可以将 char 定义为与 unsigned char 具有相同的行为(第 6.2.5 节第 15 段)。

对于这些情况,整数提升可能会规定 charpromoted to unsigned int on some implementations。因此,让以下代码冒着在这些实现上出现未定义行为的风险:

char bar = 'B';

fprintf(fp, "%c\n", bar); /* possible undefined behavior? */
  • int 变量和文字 int 常量是使用 %c 说明符将值传递给 fprintf 的唯一安全方法吗?

【问题讨论】:

  • 你总是可以写fprintf(fp, "%c\n", 'B');
  • 啊,是的,我忘了字符常量在 C 中是 int 类型的,不是吗?

标签: c c99 language-lawyer printf variadic-functions


【解决方案1】:

%cfprintf 转换规范需要 int 参数。在默认参数提升之后,该值必须是int 类型。

unsigned int foo = 42;
fprintf(fp, "%c\n", foo);

未定义行为:foo 必须是 int

char bar = 'B';
fprintf(fp, "%c\n", bar);

非未定义行为:bar 被提升(默认参数提升)为 int,因为 fprintf 是一个可变参数函数。

编辑:公平地说,仍有一些非常罕见的实现可能是未定义的行为。例如,如果char 是一个无符号类型,并非所有char 值都可以在int 中表示(如在this implementation 中),则默认参数提升为unsigned int

【讨论】:

  • 我认为 OP 特别担心char unsigned 和int 一样宽,在这种情况下bar 被提升为unsigned int
  • @ouah char默认参数提升 不属于 第 6.5.2.2 节 第 6 段的integer promotions are performed on each argument 类别吗?
  • 我很想知道这样的实现会为CHAR_MAX 打印什么字形:)
  • @VilhelmGray 是的,我指的是本段的默认参数提升。
  • @JensGustedt 我认为所有带有UCHAR_MAX > INT_MAX 的实现都是独立的实现,并且独立的实现不需要包含标准 IO 库。所以我认为fprintf 可能在这些实现中不可用。
【解决方案2】:

是的,printf"%c" 需要一个 int 参数——或多或少。

如果参数的类型小于int,那么它将被提升。在大多数情况下,升级到int,具有明确的行为。在极少数情况下,普通的char 是未签名的,而sizeof (int) == 1(这意味着CHAR_BIT >= 16),char 参数被提升为unsigned int,这可能导致未定义的行为。

字符常量已经是int 类型,所以printf("%c", 'x') 即使在外来系统上也有很好的定义。 (题外话:在 C++ 中,字符常量的类型为 char。)

这个:

unsigned int foo = 42;
fprintf(fp, "%c\n", foo);

严格来说有未定义的行为。 N1570 7.1.4p1 说:

如果函数的参数有......类型(提升后)不是 具有可变数量参数的函数所期望的行为 未定义。

fprintf 调用显然与此相冲突。 (感谢 ouah 指出这一点。)

另一方面,6.2.5p6 说:

对于每个有符号整数类型,都有一个对应的(但 不同的)无符号整数类型(用关键字指定 unsigned)使用相同的存储量(包括符号信息)并具有相同的对齐要求。

和 6.2.5p9 说:

有符号整数类型的非负值范围是一个子范围 对应的无符号整数类型,以及 每种类型的相同值都是相同的。

带脚注:

相同的表示和对齐要求意味着 可互换性作为函数的参数,返回值来自 职能和工会成员。

脚注说intunsigned int 类型的函数参数是可以互换的,只要值在这两种类型的可表示范围内。 (对于典型的 32 位系统,这意味着该值必须在 0 到 231-1 的范围内;int 的值从 -231 到 - 1 和 unsigned int 的值从 231 到 232-1,超出了其他类型的范围,不可互换。)

但 C 标准中的脚注是非规范性的。它们通常旨在澄清规范性文本中陈述的要求,而不是强加新的要求。但是这里的规范文本只是说明相应的有符号和无符号类型具有相同的表示形式,这并不必然暗示它们以与函数参数相同的方式传递。原则上,编译器可以忽略该脚注,例如,在不同的寄存器中传递 intunsigned int 参数,使 fprintf(fp, "%c\n", foo); 未定义。

但在实践中,没有理由让实现玩这种游戏,您可以依靠fprintf(fp, "%c\n", foo); 来按预期工作。我从未见过或听说过它不起作用的实现。

就个人而言,我不喜欢依赖它。如果我正在编写该代码,我会通过强制转换添加显式转换,这样一开始就不会出现这些问题:

unsigned int foo = 42;
fprintf(fp, "%c\n", (int)foo);

或者我首先将foo 设为int

【讨论】:

  • 当您说我的示例“实际上具有明确定义的行为”时,您是说它对于我的特定值 42 是明确定义的,而对于超出范围的值可能未定义可以由int表示?
  • @VilhelmGray:是的。当且仅当值在intunsigned int 的范围内时,它是明确定义的(如果我们假设脚注是规范的)。
  • @KeithThompson C 还说unsigned intint 的大小和对齐方式在 p6 中是相同的)“对于每个有符号整数类型,都有一个对应的(但不同)无符号整数类型 [...] 使用相同的存储量(包括符号信息)并具有相同的对齐要求” 尽管使用 unsigned int 可能不会失败,但从技术上讲它是 UB因为它显然违反了 7.1.4p1 “如果函数的参数具有 [...] 具有可变数量参数的函数所不期望的类型(提升后),则行为未定义”
  • @VilhelmGray:是的,已修复,谢谢! (对于阅读这些 cmets 的任何人:罕见的情况不是普通的 char 未签名,而是普通的 char 未签名 sizeof (int) == 1。)
  • @VilhelmGray:是的,我相信这是正确的。传递char 变量将几乎 不会导致未定义的行为。事实上,我从未听说过带有CHAR_BIT > 8托管 实现,并且只需要托管实现来实现fprintf
猜你喜欢
  • 2017-05-06
  • 1970-01-01
  • 2019-08-22
  • 2017-06-30
  • 2021-07-05
  • 2016-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多