【问题标题】:The %x format specifier with an unsigned char in C [duplicate]C 中带有无符号字符的 %x 格式说明符 [重复]
【发布时间】:2011-11-03 13:44:55
【问题描述】:

我遇到了以下示例程序,但我并不完全理解它的输出:

#include <stdio.h>

int main( void ) {

    unsigned char i, m =0xFF, n=0x1;

    for ( i = 0; i != 8; i++,n+=n, m/=2 )
        printf("%5x %5x %5x %5x %5x %5x\n", n,m,n&m,n|m,n^m,~n);

    return 0;
}

打印出来:

    1    ff     1    ff    fe fffffffe
    2    7f     2    7f    7d fffffffd
    4    3f     4    3f    3b fffffffb
    8    1f     8    1f    17 fffffff7
   10     f     0    1f    1f ffffffef
   20     7     0    27    27 ffffffdf
   40     3     0    43    43 ffffffbf
   80     1     0    81    81 ffffff7f

问题是最后一列。由于它是无符号字符,我希望它在每列中只打印出 2 个位置。 ~n 作为结果生成一个无符号字符,但它似乎被转换为有符号的 32 位值并由 %5x 说明符扩展符号。

这怎么可能,这是怎么回事?

【问题讨论】:

  • nunsigned char~nint(或者可能是 unsigned int——现在懒得检查了)。试试printf("%d %d\n", (int)sizeof n, (int)sizeof ~n); :-)
  • 并非如此。 &amp;|^~ 操作将它们的操作数提升为 [unsigned] int 并产生 [unsigned] int 类型的值。
  • @pmg:是的,我刚刚在 K&R 中查到了,第 页。 197 - 198,第 A6.5 节算术转换:...否则,两个操作数的类型均为 int。
  • @pmg: 如果该类型可以容纳unsigned char 的所有值(很可能),则为int,否则为unsigned int

标签: c


【解决方案1】:

问题在于它被提升为 int 当你将它传递给 printf 的 varags - 正如 Dietrich 的回答 - 当你否定它时。

不幸的是,您需要将其剥离为一个字节才能传入,即(~n &amp; 0xff)

【讨论】:

  • 但是为什么没有前导 1 的其他实例,例如第一行第二列中的 FF,也被提升和符号扩展?
  • 问题不在于unsigned char在传递给printf时被提升,而是在取位补码之前被提升。您可以删除 printf 并观察相同的值。
  • @Robert 扩展符号是什么意思?你期待什么结果?
  • @Dietrich Epp:你是说按位补码总是从较短的类型提升为 int 吗?为什么这与其他按位运算符不同?或者您是说它们都从较短的类型转换为长的类型?实际上,您说它正在做类似~(unsigned int)n 的事情。如果我要执行n = ~n; 之类的操作,我会收到某种截断警告吗?
  • @Robert:他们都被提升为int(不是unsigned int)。你只是没有注意到它,因为例如0x000000ff ^ 0x00000001 = 0x000000fe。 C 编译器通常不会给出截断警告,因为您会收到无数错误警报,尽管当您将指针转换为太小的整数类型时,Clang 会给出警告。
【解决方案2】:

我的猜测是 all 您传递的chars 被解释为 32 位整数,但是在前 4 种情况下,输出是相同的。只有在最后一张它显示出它的丑陋脸:)

您必须屏蔽所有其他位才能获得前 4 个示例中类似的结果。

【讨论】:

  • 但为什么会有差异?不应该扩展所有具有前导 1 的值,例如第一行第二列中的 FF 吗?为什么只在最后一列?
  • 因为 32 位:~OxFF 等于 OxFFFFFF00,所以前 3 个字节中有非零值。查看OxFF 只设置了整数中的最后一个字节。
  • @Robert 他们都被扩展了。您期望的值是什么?
【解决方案3】:

整数类型在用于算术运算时会被提升(顺便说一下,这与printf 无关)。

例如,

unsigned char x = 0xff;
int y = ~x; // x is promoted to 0x000000ff, then changed to 0xffffff00
unsigned char z = ~x; // truncated back to 0x00

整数提升导致各种问题:

unsigned char x = 1;
if (x << 8)
    puts("x << 8 is true"); // does print
x <<= 8;
if (x)
    puts("x <<= 8 is true"); // does not print

截断事物的两种方法是强制转换和掩码。使用你喜欢的任何东西。

unsigned char x = 0xab;
printf("x = %02x\n", (unsigned char) x);
printf("x = %02x\n", x & 0xff);

整数提升并不总是发生,而且它不是唯一一种隐式转换。它也有点微妙,确切的规则很难记住。如果您使用 64 位数字,您只有真正需要担心它,因为 1U &lt;&lt; 32 最终可能会成为 01 或其他完全不同的东西。 (在 x86 上通常是 1)。

【讨论】:

  • 但是为什么其他列中的值没有在前导 1 被符号扩展?
  • 永远不会在unsigned char 上执行签名扩展。前一位是由补码运算符~引起的。
  • 常规算术运算也会发生这种情况吗?顺便说一句,我认为移动超过类型的宽度是一个未定义的操作。
  • @Robert:这正是1U &lt;&lt; 32 存在问题的原因——它可能应该是1ULL &lt;&lt; 32。人们忘记了1 通常是 32 位宽(除了在预处理器中......)。是的,它确实发生在常规算术运算中。
  • +1 唯一正确答案。
【解决方案4】:

1) 您已将宽度指定为 %5x,但 C 中有关于宽度说明符的规则。如果要打印的数字的宽度大于指定的宽度,则忽略宽度说明符。所以在最后一列中,数字内容不能以指定的宽度 5 表示,因此宽度限制已被忽略。为什么宽度大于 5 我已在第三点解释。现在在第二点我告诉 %x vs unsigned char 。

2) 见朋友,您已将变量 'n' 声明为无符号字符,但您使用了 %x,它表示无符号十六进制整数。因此,在打印时,n 的值会被提升,或者从技术上讲,您可以说类型转换为无符号十六进制整数。不仅在 unsigned char 和 %x 的情况下。你应该在你的编译器上尝试不同的组合,就像 int a=-5; printf("%d %u",a,a); 现在你会得到 -5 by %d 并且对于 %u 你会得到一些其他的解释,这取决于编译器属性是 16 位还是 32 位。 现在如果你尝试 无符号整数 a=-5; printf("%d %u",a,a); 结果仍将与以前相同。在第一种情况下你写 int a=5 和在第二种情况下你写 unsigned int =5 但结果取决于解释所以这是这样的。

这是关于类型转换的,或者你可以说编译器的解释。当你说 %x 你自己对编译器说它是一个无符号的十六进制整数。我希望它现在很清楚,以防万一我建议你在许多编译器上运行一些不同的程序。像 int x=7; 这样的程序printf("%f"x);等等等等..你一定会明白的。现在我解释一下为什么它像 fffffffe。

3) 我们考虑循环的第一次运行。这里 n=0x1。在 32 位编译器中,它将在内存中表示为 0000 0001,因为 char 提供了 1 个字节的内存。但是之后,当您将其类型转换为无符号十六进制整数时,32 位编译器中的交互是 0000 0000 0000 0000 0000 0000 0000 0001 如果你声明 int n=1 或者你声明 unsigned int n=1 或者你声明 int n=0x1。表示将是相同的。即使您使用 unsigned char n=1 ,最右边的数字也将是 1 而所有其他数字都是零,尽管零的数量可能会更少。现在在循环语句中通过表示 %x 告诉编译器将内容解释为无符号十六进制 int,因此编译器将其空间作为十六进制提供,并且您现在通过执行此操作来执行此操作“~n”,位表示变成这样1111 1111 1111 1111 1111 1111 1111 1110.(注意 - 请记住 printf("%d",~n); case 不同于 printf("%d",n++); 在 n++ 的情况下,内存中变量的值也得到更新。但是使用 printf("%d",~n) 与使用 printf("%d",n+8) 类似。)因此,根据这种新的位表示,除了最右边的位之外,所有位都是 1。现在,当它被打印出来时,它会像 fffffffe 一样打印出来。简单!

4)您已经编写了“但它似乎被转换为有符号的 32 位值并由 %5x 说明符进行符号扩展。”嗯 %x 不是有符号的十六进制整数 %x 是无符号的十六进制整数,所以没有必要假设减号被截断。

5) 在您的程序中,您使用了 '~n' 现在只需检查不同的版本,如 '-~n' 或 '-n' 。这是为了实验目的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-31
    • 2013-02-13
    • 2019-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-10
    • 1970-01-01
    相关资源
    最近更新 更多