打印一个字符时会发生哪些整数提升?

11

我最近读到

unsigned char x=1;
printf("%u",x);

由于格式说明符%u,printf函数期望一个无符号整数,因此调用未定义的行为。但我仍然希望了解这个示例中发生了什么。

我认为在表达式printf("% u",x)x所表示的值中应用整数提升规则。

 

A.6.1整数提升

 

字符、短整型或整型位域,都可以是有符号的也可以是无符号的,或者是枚举类型的对象,都可以在表达式中使用整数。如果int类型可以表示原始类型的所有值,则将值转换为int类型;否则将值转换为无符号int类型。此过程称为整数提升。

这里的“可以使用”是什么意思?它是指“语法上正确”还是“已定义的行为”?

那么在这个例子中如何提升x的类型?我读到过它被提升为int,但是如果printf("%u", (int x)) 仍然是未定义行为,那么我就真的不明白为什么了...


1
我认为行为确实已经定义了,正如你所提到的那些原因。 - luser droog
1
推荐观看:http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/Stephan-T-Lavavej-Core-C-7-of-n - Kerrek SB
1
@luserdroog:你认为“可以使用”意味着行为应该被定义吗?还是我没有理解你的观点? - lee77
@lee77 是的,你说得对。"may be used" 意味着它是被允许的。它被标准保证能够正确工作。很好地进行了批判性思考。你在哪里找到这个未定义的声明? - luser droog
@luser droog:我在stackoverflow的一个问题的倒数第二个答案中找到了它(向下滚动一点): https://dev59.com/G2Uo5IYBdhLWcg3w1yTH - lee77
啊,如果只是char且实现隐式为有符号,那么他所说的是正确的。但这里你使用的是unsigned char,所以一切都在范围内。 - luser droog
3个回答

4
由于printf使用可变参数列表,整数参数会应用整数提升。在任何正常的C实现中,整数提升将把unsigned char转换为int。然后你使用unsigned int格式说明符来格式化一个int,所以行为是未定义的。
虽然可以使用字符代替整数,但关于使用%u打印什么内容的规则仍然适用。如果使用字符得到的是适合格式说明符的整数,则行为是定义的。如果使用字符得到的是不适合格式说明符的整数,则C标准未定义该行为。
Stack Overflow上的其他讨论得出结论:一种奇特的C实现理论上可能符合C标准,并且char类型(普通,有符号和无符号)与int类型一样宽。在这样的实现中,int无法表示unsigned char的所有值,因此必须将unsigned char提升为unsigned int。但是,这种实现是奇特而麻烦的(尤其是处理EOF),在实践中可以忽略它。

所以有两个步骤:首先,由于x表示整数且printf使用可变参数列表,因此进行整数提升。现在有两种可能性: 1)int无法表示所有无符号字符可以表示的值(这将相当奇特)。然后x会被提升为已定义的unsigned int。 2)int可以表示所有无符号字符可以表示的值,因此x被提升为int。在这种情况下,在解析va_arg宏时,printf内部会有一个定义unsigned int <some variable name> = <value of x>吗?这也将被定义? - lee77
请注意,“在SO上的其他讨论”在此处:https://dev59.com/5W445IYBdhLWcg3w3Nwf - R.. GitHub STOP HELPING ICE
1
@lee77:不,这并不一定有任何这样的赋值。当printf实现看到%u说明符时,它将尝试获取unsigned int参数的字节。它所采用的方式取决于实现。它不需要通过C赋值表达式来完成。它可以是纯汇编或机器语言。您唯一可以依赖的规则是:如果使用unsigned int说明符,则必须传递unsigned int - Eric Postpischil
1
@EricPostpischil:intunsigned int不是保证具有相同的布局兼容性吗? - Kerrek SB
@lee77:没错。在某些情况下遇到未定义行为的易发性是该语言的缺陷。 - Eric Postpischil
显示剩余6条评论

1

由于printf使用可变参数列表,因此它将通过va_arg进行解包。C ++参考C标准的va_arg规则。 C99标准如下所述:


va_arg宏扩展为一个表达式,该表达式具有指定的类型和调用中下一个参数的值。参数ap应已由va_startva_copy宏进行了初始化(对于同一ap而言,在调用va_end宏之前没有中间调用)。每次调用va_arg宏都会修改ap,以便按顺序返回连续参数的值。参数type应为指定的类型名称,使得可以通过将*后缀添加到type来获取指向具有指定类型的对象的指针的类型。如果没有实际的下一个参数,或者type实际下一个参数的类型(根据默认参数提升进行推广)不兼容,则行为是未定义的,但以下情况除外:

  • 一个类型是有符号整数类型,另一个类型是相应的无符号整数类型,并且该值在两种类型中均可表示;
  • 一个类型是指向void的指针,另一个类型是指向字符类型的指针。

显然,确定实际类型和期望类型匹配时,会考虑整数提升。而有符号与无符号不匹配则由第一个要点涵盖。


由于x = 1显然是unsigned int可以表示的值,而将unsigned char提升会生成signed int(如果INT_MAX >= UCHAR_MAX)或unsigned int(如果INT_MAX < UCHAR_MAX),所以这是完全合法的。


1
如果你的平台的 int 类型可以表示所有 unsigned char 类型的值,那么提升会为 int,否则为 unsigned int。所以这取决于你的平台。
至于“为什么”,这是因为你将 x 作为变量参数传递,而变量参数的规则指出标准提升会发生(可能是为了简化实现)。

所以你说上面的“may be used”是指语法上正确,还是有不同的意思? - lee77
促销活动取决于平台,合法性则不受影响。 - Ben Voigt

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