2
#include <stdio.h>
#include <limits.h>

int main()
{
    unsigned long long a = 9223372036854775808; // a = 2^63
    a = a & ~1;
    printf("%llu\n", a);
    printf("%d, %lld", INT_MAX, LLONG_MAX);
}

输出

9223372036854775808
2147483647, 9223372036854775807

这是~C17(用我的粗体字)6.5.3.3.4 中的语义。

~ 运算符的结果是其(提升)操作数的按位补码(即,当且仅当未设置转换操作数中的相应位时,结果中的每个位都被设置)。整数提升在操作数上执行,结果具有提升的类型。如果提升的类型是无符号类型,则表达式 ~E 等价于该类型中可表示的最大值减去 E。

这是&C17 6.5.10.3 中一元的语义。

通常的算术转换是在操作数上执行的。

a等于9223372036854775808等于8000 0000 0000 0000(16)

没有任何后缀的整数常量1(int)1. 因此~1== ~(int)1== FFFF FFFE(16)(整数提升不会发生在~)。

的类型通过通常的算术转换FFFF FFFE(16)转换为unsigned long longat 。
a = 8000 0000 0000 0000(16) & FFFF FFFE(16)因此
a = 8000 0000 0000 0000(16) & FFFF FFFE(16)==
a = 8000 0000 0000 0000(16) & 0000 0000 FFFF FFFE(16)

最后,
a = a & ~1==
a = 8000 0000 0000 0000(16) & FFFF FFFE(16)==
a = 8000 0000 0000 0000(16) & 0000 0000 FFFF FFFE(16)==
a = 0000 0000 0000 0000(16)

但是输出说好像
a = a & ~1==
a = 8000 0000 0000 0000(16) & FFFF FFFE(16)==
a = 8000 0000 0000 0000(16) & FFFF FFFF FFFF FFFE(16)==
a = 8000 0000 0000 0000(16)

我的问题是如何显示输出?

4

5 回答 5

3

~1在二进制补码系统(所有现代系统 - 包括您的 PC 使用它)中是-2.

-2 在 4 字节长整数中的二进制表示是0xfffffffe.

当您将其提升为保留long long integer-2但二进制表示更改时:0xffffffffffffffffe. 这个值是二进制AND的,你的变量a- 所以它的值保持不变。

如果你想防止这种行为,你需要告诉编译器你想使用什么大小的数据:

    a = a & ~(unsigned)1;

它会像你期望的那样表现

https://godbolt.org/z/G6757W

于 2020-11-10T10:15:39.993 回答
2

我假设正常的有符号整数类型,例如int在以下答案中具有 2 的补码表示,否则 的数值~1将不是 -2。

整数常量1的类型为int。该表达式~1将具有值 -2 和类型int。因此,a = a & ~1;等价于a = a & -2;

由于ahas typeunsigned long long~1has type int,然后通过通常的算术转换,表达式~1(数值 -2)unsigned long long通过在数学上将(具有无限宽度)加到数值上,转换为 type 的ULLONG_MAX值。因此a = a & ~1;等于a = a & -2;which 等于a = a & (ULLONG_MAX - 1);

既然a有值0x8000000000000000ull(相当于9223372036854775808ull)和 的最低 64 位ULLONG_MAX有值0xffffffffffffffffull,那么 的最低 64 位(ULLONG_MAX - 1)就有值0xfffffffffffffffeull。由于0x8000000000000000ull & 0xfffffffffffffffeull等于0x8000000000000000ull并且超出 的前 64 位的任何位a都为零,因此a赋值不会改变 的值。

于 2020-11-10T10:32:17.800 回答
1

我花了一些时间才弄清楚问题到底是什么。
所以我来不及击败 P__J 得到真正有启发性的技术答案。

因此,我将答案更改为另一个答案的可视化。


#include <stdio.h>
#include <limits.h>

int main()
{
    unsigned long long a = 9223372036854775808ull; // a = 2^63
    printf("%llx\n", a);
    printf("%d\n", ~1);
    printf("%x\n", (unsigned) ~1);
    printf("%llx\n", (unsigned long long )~1);
    a = a & ~1;
    printf("%llx\n", a);
    printf("%x, %llx, %llx", INT_MAX, LLONG_MAX, ULLONG_MAX);
}

这让你的输出

8000000000000000
-2
fffffffe
fffffffffffffffe
8000000000000000
7fffffff, 7fffffffffffffff, ffffffffffffffff

在我看来,它没有任何不可信之处。

我使用了评论中的一些建议,尤其是 Jabberwocky 的建议。

于 2020-11-10T09:45:29.940 回答
1

“因此〜1 ==〜(int)1 == FFFF FFFE(16)”是错误的开始。

~(int)1可能具有位模式FFFF FFFE(16),但其不变:仍为 -2(10) 或 -2(16)。它没有 4,294,967,294(10) 或 FFFF,FFFE(16) 的值。

当 -2(16) 转换为 时unsigned long long,添加ULLONG_MAX + 1*成为 18,446,744,073,709,551,614(10) 或 FFFF,FFFF,FFFF,FFFE(16)。

  8000 0000 0000 0000(16)
& FFFF FFFF FFFF FFFE(16)
= 8000 0000 0000 0000(16)

*什么时候unsigned long long是 64 位。

于 2020-11-10T11:35:39.720 回答
1
  • 实际上,对于 ~ 操作数没有进行整数提升,因为它已经是int

  • a & ~1;相当于a & -2(给定 2 的补码)。

  • 操作数a是类型unsigned long long,操作数-2是类型int

  • 提升根据通常的算术转换 (C17 6.3.1.8) 进行:

    否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数类型的等级,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。

  • 这意味着有关有符号到无符号转换的规则适用(C17 6.3.1.3):

    否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上重复加减一,直到该值在新类型的范围内。

  • 以上是指不关心类型的纯数学计算。最大值ULLONG_MAX为 2^64 - 1。根据上述规则添加“多一个”:

    -2 + 2^64 - 1 + 1 = 18446744073709551614 = 0xFFFFFFFFFFFFFFFE

  • -2现在碰巧的是,这与将符号扩展到 2 的补码时得到的值完全相同signed long long,但实际上在任何地方都不会发生这种到更大的有符号类型的转换。

  • 0x8000000000000000 & 0xFFFFFFFFFFFFFFFE0x8000000000000000.

于 2020-11-10T12:30:51.760 回答