我有个问题。
uint64_t var = 1; // this is 000000...00001 right?
在我的代码中,这有效:
var ^ (1 << 43)
但是它怎么知道 1 应该是 64 位呢?我不应该写这个吗?
var ^ ( (uint64_t) 1 << 43 )
如您所想,1 是一个普通的有符号int
(在您的平台上可能是 2 的补码算术中的 32 位宽),43 也是如此,因此很可能1<<43
会导致溢出:事实上,如果两个参数都是int
运算符规则类型指示结果也将是一个int
。
尽管如此,在 C 中,有符号整数溢出是未定义的行为,因此原则上任何事情都可能发生。在您的情况下,编译器可能会发出代码以在 64 位寄存器中执行该移位,所以幸运的是它似乎可以工作;要获得保证正确的结果,您应该使用您编写的第二种形式,或者,使用后缀指定1
为unsigned long long
文字(保证至少为64 位)。ull
unsigned long long
var ^ ( 1ULL << 43 )
我推荐OP的方法,铸造常数( (uint64_t) 1 << 43 )
对于 OP 的小例子,下面的 2可能会执行相同的操作。
uint64_t var = 1;
// OP solution)
var ^ ( (uint64_t) 1 << 43 )
// Others suggested answer
var ^ ( 1ULL << 43 )
以上结果具有相同的值,但类型不同。潜在的区别在于 C 中如何存在两种类型:uint64_t
以及unsigned long long
可能出现的情况。
uint64_t
精确范围为 0 到 2 64 -1。
unsigned long long
范围为 0 到至少2 64 -1。
如果unsigned long long
将始终是 64 位,因为它似乎在许多机器上,没有问题,但让我们展望未来,说这段代码是在unsigned long long
16 字节(0 到至少2 128 -1)。
下面是一个人为的例子: 的第一个结果^
是 a uint64_t
,当乘以 3 时,乘积仍然是uint64_t
,执行模 2 64,如果发生溢出,然后将结果分配给d1
。在下一种情况下,结果^
是 anunsigned long long
并且当乘以 3 时,乘积可能大于 2 64,然后分配给d2
。所以d1
并d2
有不同的答案。
double d1, d2;
d1 = 3*(var ^ ( (uint64_t) 1 << 43 ));
d2 = 3*(var ^ ( 1ULL << 43 ));
如果一个人想与 合作unit64_t
,请保持一致。不要假设unit64_t
和unsigned long long
是相同的。如果您的答案可以是 a unsigned long long
,那很好。但根据我的经验,如果开始使用固定大小的类型,例如uint64_t
,则不希望变体大小的类型弄乱计算。
var ^ ( 1ULL << 43 )
应该这样做。
拥有unit64_t
常量的一种可移植方式是使用UINT64_C
宏(来自stdint.h
):
UINT64_C(1) << 43
最有可能UINT64_C(c)
被定义为类似c ## ULL
.
来自 C 标准:
宏
INT
N_C(value)
应扩展为对应于类型int_least
N_t
的整数常量表达式。宏UINTN_
C(value)
应扩展为对应于类型uint_least
N_t
的整数常量表达式。例如,如果uint_least64_t
是类型的名称unsigned long long int
,则UINT64_C(0x123)
可能会扩展为整数常量0x123ULL
。
您的编译器不知道应该以 64 位进行移位。但是,对于这个特定代码的这个特定配置中的这个特定版本的编译器,两个错误发生在一个正确的地方。不要指望它。
假设这int
是您平台上的 32 位类型(很可能),其中的两个错误1 << 43
是:
x
是int
or类型unsigned int
,则x << 43
具有未定义的行为,与 n ≥ 32 的任何其他行为一样。x << 32
例如,也将具有未定义的行为。x << n
1u << 43
0x12345 << 16
具有未定义的行为,因为左操作数的类型是有符号类型int
但结果值不适合int
. 另一方面,0x12345u << 16
是明确定义的并且具有值0x23450000u
。“未定义行为”意味着编译器可以自由生成崩溃或返回错误结果的代码。碰巧在这种情况下你得到了想要的结果——这不是被禁止的,但是墨菲定律规定总有一天生成的代码不会做你想做的事。
为保证操作发生在 64 位类型上,您需要确保左操作数是 64 位类型 — 将结果分配给的变量类型无关紧要。float x = 1 / 2
这与导致包含 0 而不是 0.5的问题相同x
:只有操作数的类型对确定算术运算符的行为很重要。任何(uint64)1 << 43
或(long long)1 << 43
或(unsigned long long)1 << 43
或1ll << 43
或1ull << 43
会做。如果您使用有符号类型,则仅在没有溢出时才定义行为,因此如果您希望在溢出时截断,请务必使用无符号类型。通常建议使用无符号类型,即使不应该发生溢出,因为该行为是可重现的——如果您使用有符号类型,那么仅出于调试目的而打印出值的行为可能会改变行为(因为编译器喜欢利用未定义的行为来生成在微观层面上最有效的任何代码,这可能对寄存器分配压力等事情非常敏感)。
由于您希望结果是 type uint64_t
,因此使用该类型执行所有计算会更清楚。因此:
uint64_t var = 1;
… var ^ ((uint64_t)1 << 43) …