![](/img/trans.png)
[英]When targeting 64-bit platforms in C, is it better to use 64-bit variables for array references?
[英]Declaring 64-bit variables in C
我有个问题。
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位寄存器中执行该移位,所以幸运的是它似乎可行; 要获得保证正确的结果,您应该使用您编写的第二种形式,或者,替代地,使用ull
后缀将unsigned long long
文本指定为1
( unsigned long long
保证至少为 64位)。
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)。
下面一个人为的例子:的第一个结果^
是一个uint64_t
,当乘以3,产品仍会被uint64_t
,执行模2 64,应该发生溢出 ,则该结果被分配到d1
。 在下一种情况下, ^
的结果是unsigned 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
是相同的。 如果你的回答是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
或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
或1ull << 43
都可以。 如果使用带符号类型,则仅在没有溢出时定义行为,因此如果您希望在溢出时截断,请确保使用无符号类型。 通常建议使用无符号类型,即使由于行为是可重现的而不应发生溢出 - 如果使用带符号类型,那么仅仅为了调试目的而打印输出值的行为可能会改变行为(因为编译器喜欢利用未定义的行为生成任何代码在微观层面上最有效,这对诸如寄存器分配压力之类的事情非常敏感。
由于您希望结果为uint64_t
类型,因此使用该类型执行所有计算会更清楚。 从而:
uint64_t var = 1;
… var ^ ((uint64_t)1 << 43) …
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.