【问题标题】:Type casting ~0 to (unsigned int) and (unsigned short) with format specifier %d and %u produces same result for short but different result for int使用格式说明符 %d 和 %u 将 ~0 类型转换为 (unsigned int) 和 (unsigned short) 对 short 产生相同的结果,但对 int 产生不同的结果
【发布时间】:2020-10-12 16:54:27
【问题描述】:

在计算 char、short、int 和 long 变量(包括有符号和无符号)范围的过程中,我采用了以下解决方案:

  1. https://stackoverflow.com/a/19085193/11320006

  2. http://www.learntosolveit.com/cprogramming/Ex_2.1_cal_limits.html

根据解决方案 1,我希望下面代码中的 (unsigned short)~0 输出 -1 和 65535,假设它的行为与两个格式说明符的 (unsigned int)~0 代码相同。

// the two statements below produce different results
printf("Value of unsigned int is %d\n", (unsigned int)~0); // outputs -1                                                                                                                                                     
printf("Value of unsigned int is %u\n", (unsigned int)~0); // outputs 4294967295

// whereas, the two statements below produce the same result. Why?
printf("Value of short unsigned int is %d\n", (unsigned short)~0); // outputs 65535, expected -1
printf("Value short unsigned int is %u\n", (unsigned short)~0); // outputs 65535

为什么(unsigned short)~0(unsigned int)~0 的行为会有所不同?

【问题讨论】:

  • 与可变参数“扩展”有关。所以它就像(int)(unsigned short)~0
  • 65535 可以被int 持有,但4294967295 不能。
  • %d 用于int。它会将传递的任何值视为int。格式说明符和参数不匹配(例如为%d 传递unsigned int)会导致未定义的行为
  • 可变参数函数的参数经过default argument promotions。这意味着任何小于int(有符号或无符号都无关紧要)的整数值都将是promoted to an int
  • 扩大整数转换应该尝试并保持价值。这意味着扩大无符号将对其进行零扩展,而扩大有符号将对其进行符号扩展。当转换对象不适合时转换为有符号是实现定义的,但实际上您可以依靠 2 的补码重新解释。格式说明符与相应参数的类型不匹配在技术上是 UB。

标签: c casting bitwise-operators twos-complement ones-complement


【解决方案1】:

为什么(unsigned short)~0(unsigned int)~0 的行为会有所不同?

这些表达式的行为是类似的。假设int 类型的二进制补码表示,每个都计算其(无符号)类型的最大可表示值。

但是,printf 等可变参数函数的可变参数受默认参数提升的约束。这会影响unsigned short,如果int 可以代表所有unsigned short 值,则将其提升为int,如您的示例中的情况(否则为unsigned int)。它不影响 intunsigned int 类型或更宽的整数类型的参数。

所呈现代码的关键问题是......

printf("Value of unsigned int is %d\n", (unsigned int)~0);

... 由于 %d 指令的类型未正确匹配到相应的参数,因此表现出未定义的行为。 %d 必须与 已签名 int 匹配,但您已将其与 unsigned int 关联。在实践中,UB 表现为将参数的位模式解释为signed int 而不是无符号的,但原则上,程序可以在其能力范围内做任何事情。

请注意,由于类型不匹配,这也具有未定义的行为:

printf("Value short unsigned int is %u\n", (unsigned short)~0);

指令%hu 将是对应实际参数的最佳匹配,但%d 是可以接受的,因为上述自动类型提升。 %u 不匹配。然而,在这种情况下,显示的 UB 与您预期的行为相同——至少就输出所指示的而言。在实践中,非负signed int 参数的位模式已被解释为unsigned int

【讨论】:

    【解决方案2】:

    简单 - 因为您使用了错误的 printf 格式。

    int main(void)
    {
        printf("Value of short unsigned int is %hd\n", (unsigned short)~0); 
        printf("Value short unsigned int is %hu\n", (unsigned short)~0); 
    
        printf("Value of short unsigned int is %hhd\n", (unsigned char)~0); 
        printf("Value short unsigned int is %hhu\n", (unsigned char)~0); 
    }
    

    记住

    在使用 printf 系列函数时始终使用正确的格式!!!。使用错误的可能会导致未定义的行为

    【讨论】:

    • printf 不忽略 hhh 长度说明符吗?
    • @ChristianGibbons 它回答了你的问题吗? godbolt.org/z/4K1P76显然不是无视它
    • 不,@ChristianGibbons,至少在程序员可能会忽略它们的意义上。该标准并没有减轻程序在宽度上正确匹配printf 参数的问题。它在实践中有所作为,兼容性方面。在 OP 的平台上,%d%hu 都与 unsigned short 参数匹配,但在 intshort 宽度相同的平台上,%d 将不匹配,但 %hu还是会。
    • 格式说明符“%hhd”是否适用于所有架构,还是语言有保证?在RISC-V 64位,我知道可以用,MIPS我不是很确定。
    • @HasloVardos,hh 宽度前缀是自 C99 以来的标准,可与 signed charunsigned char 参数一起使用。特别是,%hhd 是在任何符合 C99 或更高版本的实现上匹配类型 signed char 的适当指令,尽管有架构。
    猜你喜欢
    • 2021-12-13
    • 1970-01-01
    • 2015-01-20
    • 1970-01-01
    • 2012-01-31
    • 1970-01-01
    • 2011-05-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多