我有一个问题。
uint64_t var = 1; // this is 000000...00001 right?
在我的代码中,这有效:
var ^ (1 << 43)
但它怎么知道1应该是64位?我不应该写这个吗?
var ^ ( (uint64_t) 1 << 43 )
答案 0 :(得分:34)
如你所愿,1是一个普通的签名int
(可能在你的平台上的2位补码算法是32位宽),所以是43,所以任何机会1<<43
都会导致溢出:事实上,如果两个参数都是int
类型,那么运算符规则也要求结果为int
。
但是,在C签名中,整数溢出是未定义的行为,因此根据原则,任何事情都可能发生。在您的情况下,可能编译器发出代码以在64位寄存器中执行该移位,因此运气它似乎起作用;要获得保证正确的结果,您应该使用您编写的第二个表单,或者使用1
后缀(unsigned long long
指定ull
作为unsigned long long
文字。保证至少 64位)。
var ^ ( 1ULL << 43 )
答案 1 :(得分:11)
我推荐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 -1unsigned long long
的范围为0到至少 2 64 -1。
如果unsigned long long
始终总是是64位,因为它似乎在很多机器上都存在,那就没有问题,但让我们展望未来并说出这段代码在unsigned long long
为16字节的机器上运行(0到至少 2 128 -1)。
以下设计示例:^
的第一个结果是uint64_t
,当乘以3时,乘积仍为uint64_t
,执行模2 64 ,如果发生溢出,则结果将分配给d1
。在下一种情况下,^
的结果为unsigned long long
,当乘以3时,乘积可能大于2 64 ,然后将其分配给{{1} }。因此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
,那很好。但根据我的经验,如果一个人开始使用固定大小的类型,如unsigned long long
,则不希望变体大小类型弄乱计算。
答案 2 :(得分:4)
var ^ ( 1ULL << 43 )
应该这样做。
答案 3 :(得分:4)
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
Ñ_t
。例如,如果uint_least64_t
是该类型的名称unsigned long long int
,然后UINT64_C(0x123)
可能扩展到。{1}} 整数常量0x123ULL
。
答案 4 :(得分:3)
您的编译器不知道应该以64位完成移位。但是,对于此特定代码的特定配置中的这个特定版本的编译器,两个错误恰好是正确的。不要指望它。
假设int
在您的平台上是32位类型(非常可能),1 << 43
中的两个错误是:
x
属于int
或unsigned int
,则x << 43
具有未定义的行为,x << 32
或任何其他x << n
也是如此 n ≥32。例如,1u << 43
也会有未定义的行为。0x12345 << 16
具有未定义的行为,因为左操作数的类型是带符号的类型int
,但结果值不适合int
。另一方面,0x12345u << 16
定义明确,值为0x23450000u
。“未定义的行为”意味着编译器可以自由地生成崩溃或返回错误结果的代码。碰巧在这种情况下你获得了理想的结果 - 这是不被禁止的,但墨菲定律规定,有一天生成的代码将无法达到你想要的效果。
为了保证操作发生在64位类型上,您需要确保左操作数是64位类型 - 您分配结果的变量类型无关紧要。这与float x = 1 / 2
的问题相同,导致x
包含0而不是0.5:只有操作数的类型才能确定算术运算符的行为。 (uint64)1 << 43
或(long long)1 << 43
或(unsigned long long)1 << 43
或1ll << 43
或1ull << 43
中的任何一个都可以。如果使用带符号类型,则仅在没有溢出时定义行为,因此如果您希望在溢出时截断,请确保使用无符号类型。通常建议使用无符号类型,即使由于行为是可重现的而不应发生溢出 - 如果使用带符号类型,那么仅仅为了调试目的而打印输出值的行为可能会改变行为(因为编译器喜欢利用未定义的行为生成任何代码在微观层面上最有效,这对诸如寄存器分配压力之类的事情非常敏感。
由于您希望结果为uint64_t
类型,因此使用该类型执行所有计算会更清楚。因此:
uint64_t var = 1;
… var ^ ((uint64_t)1 << 43) …