uint64_t 的安全低 32 位掩码

Safe low 32 bits masking of uint64_t

假设以下代码:

uint64_t g_global_var;

....
....

void foo(void)
{
    uint64_t local_32bit_low = g_global_var & 0xFFFFFFFF;
    ....
}

使用当前的工具链,此代码按预期工作,local_32bit_low 确实包含 g_global_var 的低 32 位。

我想知道标准C是否保证这段代码总是按预期工作? 我担心的是,编译器可能会将 0xFFFFFFFF 视为 -1 的整数值,当提升为 uint64_t 时,它将变为 0xFFFFFFFFFFFFFFFF。

P.S.

我知道为了安全起见,在这种情况下最好使用 0xFFFFFFFFULL。关键是我在遗留代码中看到它,我想知道它是否值得修复。

没有问题。整数常量 0xFFFFFFFF 具有能够按原样存储值的类型。

根据 C 标准(6.4.4.1 整数常量)

5 The type of an integer constant is the first of the corresponding list in which its value can be represented

所以这个值存储为正值。

如果类型 unsigned int 是 32 位整数类型,则常量的类型将是 unsigned int.

否则它将具有可以存储该值的类型之一。

long int
unsigned long int
long long int
unsigned long long int 

由于表达式中通常的算术转换

g_global_var & 0xFFFFFFFF;

它被提升为

0x00000000FFFFFFFF

注意C中没有负整数常量。例如像

这样的表达式
-10

由两个子表达式组成:主表达式 10 和带有一元运算符 - -19 的与完整表达式重合的子表达式。

0xffffffff 永远不会是 -1。如果您将其转换或强制(例如通过赋值)为带符号的 32 位类型,它可能会转换为 -1,但 C 中的整数文字始终具有其数学值,除非它们溢出。

对于十进制文字,类型是可以表示值的最窄的有符号类型。对于十六进制文字,在进入下一个更宽的有符号类型之前使用无符号类型。因此,在 int 为 32 位的常见情况下,0xffffffff 的类型为 unsigned int。如果您将其写成十进制,它将具有类型 long(如果 long 是 64 位)或 long long(如果 long 只是 32 位)。

无后缀的十六进制或八进制常量的类型是以下列表中第一个可以表示其值的类型:

int
unsigned int
long int
unsigned long int
long long int
unsigned long long int

(对于无后缀的十进制常量,从上面的列表中删除 unsigned 类型。)

十六进制常量0xFFFFFFFF肯定可以用unsigned long int表示,所以它的类型会是intunsigned intlong intlong int中的第一个unsigned long int 可以代表它的价值。

请注意,尽管 0xFFFFFFFF > 0 的计算结果始终为 1(真),但在不同的实现中,0xFFFFFFFF > -1 的计算结果可能为 0(假)或 1(真)。所以在比较整数常量或与其他整数类型的对象时需要小心。

其他人已经回答了这个问题,只是一个建议,下次(如果你是C11)你可以使用_Generic

自己检查表达式的类型
#include <stdio.h>
#include <stdint.h>

#define print_type(x) _Generic((x), \
    int64_t:  puts("int64_t"),      \
    uint64_t: puts("uint64_t"),     \
    default:  puts("unknown")       \
)

uint64_t g_global_var;

int main(void)
{
    print_type(g_global_var & 0xFFFFFFFF);
    return 0;
}

输出是

uint64_t