按位和超过 32 位

Bitwise & over 32 bits

#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 中 ~ 的语义。

The result of the ~ operator is the bitwise complement of its (promoted) operand (that is, each bit in the result is set if and only if the corresponding bit in the converted operand is not set). The integer promotions are performed on the operand, and the result has the promoted type. If the promoted type is an unsigned type, the expression ~E is equivalent to the maximum value representable in that type minus E.

这是 C17, 6.5.10.3.

中一元 & 的语义

The usual arithmetic conversions are performed on the operands.

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

不带任何后缀的整数常量1等同于(int)1。因此 ~1 == ~(int)1 == FFFF FFFE(16)~ 不会进行整数提升)。

FFFF FFFE(16)的类型在
转换为unsigned long long 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)

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

我花了一些时间才弄明白真正的问题是什么。
所以我来不及击败 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 的建议。

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

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

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

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

    a = a & ~(unsigned)1;

它会如您所愿

https://godbolt.org/z/G6757W

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

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

由于 a 的类型为 unsigned long long~1 的类型为 int,那么通过通常的算术转换,表达式 ~1(数值 - 2) 转换为 unsigned long long 类型的值,方法是在数学上将 ULLONG_MAX 加一(无限宽度)到数值。因此 a = a & ~1; 等同于 a = a & -2; 等同于 a = a & (ULLONG_MAX - 1);

由于a的值为0x8000000000000000ull(等同于9223372036854775808ull)并且ULLONG_MAX的最低有效64位的值为0xffffffffffffffffull,那么(ULLONG_MAX - 1) 的最低有效 64 位的值为 0xfffffffffffffffeull。由于 0x8000000000000000ull & 0xfffffffffffffffeull 等于 0x8000000000000000ull 并且 a 的前 64 位之外的任何位都为零,因此 a 的值将不会因赋值而改变。

"Therefore ~1 == ~(int)1 == FFFF FFFE(16)" 是错误的开始。

~(int)1 可能有 位模式 FFFF FFFE(16),但它的 value 没有改变:仍然 - 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 位时。

  • 确实没有对 ~ 操作数进行整数提升,因为它已经是 int.

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

  • 操作数a的类型为unsigned long long,操作数-2的类型为int.

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

    Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.

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

    Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

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

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

  • 现在,这与 -2 在符号扩展为 signed long long 时得到的值完全相同 signed long long 在 2 的补码上,但没有这样转换为更大的符号类型实际上发生在任何地方。

  • 0x8000000000000000 & 0xFFFFFFFFFFFFFFFE 给出 0x8000000000000000.