【问题标题】:Is it illegal to use the h or hh length modifiers when the corresponding argument to printf was not a short / char?当 printf 的相应参数不是短/字符时,使用 h 或 hh 长度修饰符是否非法?
【发布时间】:2015-09-11 17:41:41
【问题描述】:

printf 系列函数提供了一系列长度修饰符,其中两个是hh(表示signed charunsigned char 参数提升为int)和h(表示@987654328 @ 或 unsigned short 参数提升为 int)。从历史上看,这些长度修饰符仅被引入以创建与 scanf 的长度修饰符的对称性,并且很少用于 printf

这是 ISO 9899:2011 §7.21.6.1 “fprintf 函数”¶7 的节选:

7 长度修饰符及其含义为:

  • hh 指定以下diouxX 转换说明符适用于signed charunsigned char 参数(参数将根据整数提升进行提升,但在打印前将其值转换为signed charunsigned char);或者后面的n 转换说明符适用于指向有符号字符的指针 论据。

  • h 指定以下diouxX 转换说明符适用于short int 或@98765435 (参数将根据整数提升进行提升,但其值在打印前需要转换为short intunsigned short int);或者后面的 n 转换说明符适用于指向短 int 参数的指针。

  • ...

忽略n 转换说明符的情况,这些几乎相同的段落对hhh 的行为有何看法?

  • this answer 中,据称传递了超出signed charsigned shortunsigned charunsigned short 范围的参数。对于带有 hhh 长度修饰符的转换规范。是 未定义的行为, 因为参数不是从 charshort 等类型转换而来的。之前。
  • 我声称对于int 类型的每个值,该函数以明确定义的方式运行,并且printf 的行为就像参数被转换为charshort 等。转换前。
  • 也可以声称在默认参数提升之前使用不属于相应类型的参数调用函数是未定义的行为,但这似乎很深奥。

§7.21.6.1¶7(如果有的话)的这三种解释中哪一种是正确的?

【问题讨论】:

  • 我提出了第一个要求(请参阅链接答案)。实际上,我确实认为,如果我们对段落中使用的词语持迂腐态度,我的主张就成立了(提升前的论点是short 变体),但本段/C 的精神使得FUZxxl 的主张可能更有可能。
  • 我认为第一种方法是正确的。转换例程很可能期望有限的范围,因此使用优化版本(想想 8 位或 16 位 CPU)。不确定,如果输入参数必须是相同的类型。问题是,格式字符串解析器如何从short/etc 中分辨出“真实”int 输入参数。转换为int。另一个方面可能是编译器的内联。在最坏的情况下,它可能会抑制优化,但不会影响其他任何事情,因为它的行为必须相同。
  • 这条线有点不清楚 - %h......but its value shall be converted to short int or unsigned short int before printing) 。那么这是否意味着它在进行整数提升时可以保持大于unsigned short int /short int 的值?如果是这样,它持有的值是否会转换回其原始变量类型?它的结果是什么?
  • 如果你传递一个 64 位整数,而我们 %hhd,那么你会受到伤害,因为这将占用 8 个字节,而 printf 预计它会占用 4 个(Windows 和Linux x86 反正)
  • @Ben 当然是的。我应该更清楚地说明我在谈论 signedunsigned int 参数,因为由于默认参数提升,这些是 hhh 预期的类型。

标签: c printf language-lawyer undefined-behavior short


【解决方案1】:

标准规定:

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

[C2011 7.21.6.1/9]

“正确的类型”是什么意思,可以想象,可以解释,但对我来说最合理的解释是转换规范“适用于”的类型,如同一部分前面指定的那样,如引用的,在部分,在问题中。我认为关于参数提升的括号 cmets 是承认普通的参数传递规则,并避免这些函数是特殊情况的任何暗示。我不认为括号中的 cmets 与确定参数的“正确类型”有关。

如果您传递的参数类型比转换规范正确,实际发生的情况是另一个问题。我倾向于相信 C 系统不太可能由任何人实现,因此 printf() 参数实际上是 char 还是 int 的值在char。但是,我断言编译器检查参数类型与格式的对应关系是有效的行为,如果存在不匹配则拒绝程序(因为在这种情况下所需的行为是明确未定义的)。

另一方面,如果参数的值超出相应转换说明符所隐含的范围,我当然可以想象 printf() 实现实际上行为不端(打印垃圾、损坏内存、吃你的午餐)。由于行为未定义,这也是允许的。

【讨论】:

  • 如果我通过inth 进行转换,那么参数类型确实匹配,因为h 在默认参数提升后期望“有符号或无符号short 的类型, ” 而int 就是这种类型。
  • @FUZxxl,不,您引用错误。标准说h 表示转换说明符“适用于短整型或无符号短整型参数”——思想结束。它确实在这种情况下对参数提升进行了评论,但这是一个单独的辅助考虑因素,如括号中的那些 cmets 所示。
  • @JohnBollinger:所以链接问题中的代码不会调用 UB 如果我因为隐式提升而使用 %d 格式说明符。对吗?
  • @PravasiMeet,按照对标准的最严格解释——事实上我已经改进了——如果给出short int 类型的参数,它确实会产生未定义的行为对于带有说明符 %d 的字段。我声称默认参数提升不考虑所需的类型对应关系。在实践中,我可以想象编译器会拒绝它,但如果代码被接受,那么我看不出怎么会出现运行时问题。
  • 好的,所以在询问了我当地的 UNIX graybeard(他是 POSIX 的作者之一)并测试了 C 编程语言的各种近期和历史实现之后,似乎 h 和 @ 987654335@ 能够在高位打开的情况下打印出字符的值,而不必在之前进行转换(%u 会由于字符的符号扩展而打印错误的值),并且期望的行为是& 0xff 或类似的简单掩蔽。历史悠久的 C 实现(例如 ULTRIX)不知道 h,但所有实现都具有上述行为。
猜你喜欢
  • 2021-09-13
  • 2011-06-02
  • 1970-01-01
  • 2018-08-24
  • 1970-01-01
  • 2020-04-01
  • 2018-03-12
  • 1970-01-01
相关资源
最近更新 更多