【问题标题】:Why does the binary value of my char change when using %u?为什么使用 %u 时我的 char 的二进制值会发生变化?
【发布时间】:2021-03-26 14:27:45
【问题描述】:
char a = 0b11111111;

printf("%u", a);

我们在一个有符号字符(在 gcc 中默认 char 是有符号的)中存储 1111 1111,意思是 -1。但是我们使用 %u 打印,所以 printf 应该看到 0000 0000 0000 0000 0000 0000 1111 1111。

这个数字,有或没有二进制补码 - 是 255。那么为什么我得到 (2^32 - 1)?似乎程序没有放置前导零(如我所料)而是放置前导零。

【问题讨论】:

  • 类型提升。 (char) -1 被提升为(int) -1,而后者被传递给printf
  • "printf 应该看到 0000 0000 0000 0000 0000 0000 1111 1111。" --> 不,用"%u"打印-1是UB。
  • 更相关的可能是:stackoverflow.com/q/7084857/1216776
  • 旁白:假设非标准的0b11111111完全等价于0xff,而CHAR_MAX为127,那么初始化char a = 0x11111111;a初始化为实现定义的值,不一定是-1,否则它会引发实现定义的信号。编译器可能会产生警告。
  • 当您调用像printf() 这样的可变参数函数时,作为...(省略号)一部分传递的参数将经历default argument promotions。这意味着char 被提升为int,而负的char 值(当普通char 是有符号类型时)被提升为negative integer

标签: c


【解决方案1】:

您的示例中有多个问题:

  • char a = 0b11111111; 使用二进制文字的扩展。

  • 如果char 已签名且CHAR_MAX < 255,则char a = 0b11111111; 具有实现定义的行为。

  • printf("%u", a) 具有未定义的行为,因为 chara 在传递给 printf 时被提升为 int,它需要 unsigned int 格式为 %u

    一个例外是罕见的架构(主要是 DSP),其中 char 默认是无符号的,并且大小与 unsigned int 相同。但随后char 未签名,您的示例不会造成问题。

如果你想打印unsigned char类型的确切值,你应该使用%hhu,或者使用%u并将参数转换为(unsigned char)

【讨论】:

    【解决方案2】:

    正如@Lundin 所述,转换规则适用

    正确打印:

        printf("%hhu\n", (unsigned char)a);
        printf("%u\n", (unsigned char)a);
        printf("%"PRIu8"\n", (uint8_t)a);
    

    【讨论】:

    • printf("%u\n", (unsigned char)a); 不正确,因为它为 %u 传递了一个 int(提升后),它需要 unsigned int,并且 C 2018 7.21.6.1 9 说“......如果有任何参数是不是相应转换规范的正确类型,行为未定义。”
    • @EricPostpischil "一种提升类型是有符号整数类型,另一种提升类型是对应的无符号整数类型,并且值在两种类型中都是可表示的;" § 6.5.2.2 6 似乎在值为正时免除这种情况(表示与 unsignedint 相同)。
    • @chux-ReinstateMonica:这是函数调用的一般规则,它只适用于调用没有原型定义的函数(不适用于带有... 的变量参数)。 (我们不知道printf 是如何定义的;它是由标准库子句指定的,它没有说明它是如何定义的,只是它的行为方式以及标题如何声明它。)7.21.6.1 中的规则9 专门针对printf/fprintf。我预计 7.21.6.1 9 可能会无意中将这种行为定义为未定义,但它可能已被定义,但这就是它所说的。
    • @EricPostpischil if sizeof(int) > sizeof(char) then this cast is OK
    • @0___________:从aunsigned char 的演员阵容还可以。由unsigned charint 的促销引起的转换是可以的。为%u 的转换规范传递int 参数是不行的。 %u 应该传递一个 unsigned int,而我从 7.21.6.1 9 中引用的文本意味着,当一个 int 传递给一个期望 unsigned int 的转换规范时,行为没有定义。
    【解决方案3】:

    首先,char 可能是有符号或无符号的,具体取决于编译器,因此不适合记录原始二进制文件,请参阅 Is char signed or unsigned by default?

    在您的情况下,它显然已签名,在这种情况下,值 0b11111111 = 255 将不适合。分配后,255 将以编译器特定的方式隐式转换为signed char。很可能是 2 的补数 -1

    现在,当您将任何小整数类型传递给printf 时,它们会被称为默认参数提升 的奇怪规则隐式提升,该规则适用于所有可变数量的参数函数。这条规则是:

    整数提升在每个参数上执行,并且参数 将 float 类型提升为 double

    关于“小整数类型”的含义和整数提升,请查看Implicit type promotion rules

    这意味着传递给printf 的是int,其值仍为-1,但符号扩展。由于int 可能类似于 4 字节,它现在包含原始二进制 0xFFFFFFFF 而不仅仅是 0xFF。仍然是十进制数-1,只是更大的类型。

    最后你告诉printf 将它打印为unsigned int,所以它被printf 转换为0xFFFFFFFF 的无符号表示。这是一个定义明确的转换(C17 6.3.1.3):

    否则,如果新类型是无符号的,则通过重复添加或转换值 比新类型可以表示的最大值减一 直到值在新类型的范围内。

    在 32 位 int 计算机上,您最终会得到 2^32 - 1 = 4294967295。

    【讨论】:

    • 这个答案混淆了转换和重新解释。当int 被传递给printf%u 时,printf 无法知道存在int,因为它没有给出该信息。所以它不能执行从intunsigned 的任何转换。所以没有“定义明确的转换”。 C 标准未定义为 printf 传递错误参数类型的行为。经常发生的是在新类型中重新解释这些位。这不是转换。
    • 最后你告诉 printf 将它打印为 unsigned int,所以它被 printf 转换为 0xFFFFFFFF 的无符号表示。 这不是发生的事情。 printf() 需要一个 unsigned int 参数,并且不会从实际传递的 int 转换。它从传递参数的任何位置检索参数,就好像传递了 unsigned int 一样。如果int 值不是负数,C 标准要求检索到的值是相同的,但如果不是,则行为最多只能是实现定义。在假设的符号/幅度机器上,printf 可能会输出 32769
    • @Lundin:我同意,但是6.3.1.3的转换语义不适用于这种情况。
    • 首先,重新解释数据是否安全与它是对位的重新解释,而不是转换这一事实无关。 “它被printf 转换”的说法是错误的。这些位被重新解释;该值未转换。其次,根据别名规则,重新解释可能是安全的,但这并不能使重新解释安全,因为printf/fprintf 有一个明确的规则,即“如果任何参数不是相应转换规范的正确类型,行为未定义。”
    • @Lundin:左值转换与重点无关。你传递了一个int 值,而printf 可能使用了一个unsigned int 左值,该左值被转换为unsigned int 值。在此过程中不会发生算术转换,只是重新解释表示的位,并且仅因为 intunsigned int 以兼容的方式传递(相同的寄存器,堆栈位置......),这甚至不能保证C 标准。 UB 的一个很好的例子是传递一个int,其中应该是一个double。输出字符串甚至可能与 int 值无关。
    猜你喜欢
    • 1970-01-01
    • 2020-01-06
    • 2014-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-13
    相关资源
    最近更新 更多