当char 或short(包括有符号和无符号版本)用作没有特定类型的函数参数时(如... 的参数printf(format,...))1 sup>,它会自动提升为int(假设它还没有int那么宽2)。
所以printf("%x\n", m_short); 有一个int 参数。这个论点的价值是什么?在赋值 m_short = m_int; 中,您尝试为其赋值 -5339876(用字节 0xffae851c 表示)。但是,-5339876 不适合这个 16 位短。在赋值中,会自动执行转换,并且当整数到有符号整数类型的转换不合适时,结果由实现定义。看起来你的实现,和很多人一样,使用二进制补码并且只取整数的低位。因此,它将字节 0x851c 放入 m_short,表示值 -31460。
回想一下,它被提升回int 以用作printf 的参数。在这种情况下,它适合 int,所以结果仍然是 -31460。在二进制补码int 中,用字节 0xffff851c 表示。
现在我们知道传递给printf的是什么:一个int,字节0xffff851c代表值-31460。但是,您使用%x 打印它,它应该收到unsigned int。由于这种不匹配,行为不是由 C 标准定义的。然而,这是一个相对较小的不匹配,许多 C 实现让它滑动。 (即使使用-Wall,GCC 和 Clang 也不会发出警告。)
假设您的 C 实现不将 printf 视为一个特殊的已知函数,而只是在您编写它时为调用生成代码,并且您稍后将该程序与 C 库链接。在这种情况下,编译器必须根据您平台的应用程序二进制接口 (ABI) 规范传递参数。 (除其他外,ABI 指定如何将参数传递给函数。)为了符合 ABI,C 编译器会将格式字符串的地址放在一个位置,将 int 的位放在另一个位置,然后它将调用printf。
printf 例程将读取格式字符串,参见%x,并查找相应的参数,它应该是unsigned int。在我所知道的每个 C 实现和 ABI 中,int 和 unsigned int 在在同一个地方传递。它可能是处理器寄存器或堆栈上的位置。假设它在寄存器 r13 中。因此,编译器设计了您的调用例程,将带有字节 0xffff851c 的 int 放入 r13 中,printf 例程在 r13 中查找 unsigned int 并找到字节 0xffff851c。
所以结果是printf 将字节 0xffff851c 解释为好像它们是 unsigned int,将它们格式化为 %x,并打印“ffff851c”。
基本上,你侥幸逃脱,因为 (a) short 被提升为 int,其大小与 printf 所期望的 unsigned int 相同,以及 (b) 大多数 C 实现对于与printf 相同宽度的整数类型不匹配并不严格。如果您尝试使用%ld 打印int,您可能会得到不同的结果,例如打印值的高位中的“垃圾”位。或者您可能会遇到这样一种情况,您传递的参数应该与预期的参数printf 完全不同,因此没有一个位是正确的。在某些架构中,错误地传递参数可能会破坏堆栈并以各种方式破坏程序。
脚注
1这种自动提升也发生在许多其他表达方式中。
2关于这些自动整数提升的一些技术细节目前不需要我们关注。