C语言 将有符号的 32 位存储在无符号的 64 位 int 中



基本上,我想要的是将有符号的 32 位 int "存储"在(最右边的 32 位(一个无符号的 64 位 int 中 - 因为我想将最左边的 32 位用于其他目的。

我现在正在做的是一个简单的演员和面具:

#define packInt32(X) ((uint64_t)X | INT_MASK)

但是这种方法有一个明显的问题:如果X是一个正整数(第一个位未设置(,一切正常。如果它是负面的,它就会变得混乱。


问题是:

如何以最快和最有效的方式实现上述支持负数?

你提到的"混乱"是因为你把一个小的有符号类型强制转换为一个大的无符号类型。 在此转换过程中,首先通过应用符号扩展来调整大小。这就是导致您麻烦的原因。

您可以先将(有符号(整数转换为相同大小的无符号类型。然后转换为 64 位不会触发符号扩展:

#define packInt32(X) ((uint64_t)(uint32_t)(X) | INT_MASK)

您需要屏蔽除低阶 32 位之外的任何位。 您可以使用按位 AND 来执行此操作:

#define packInt32(X) (((uint64_t)(X) & 0xFFFFFFFF) | INT_MASK)

负32 位整数将符号扩展为 64 位。

#include <stdint.h>
uint64_t movsx(int32_t X) { return X; }

x86-64 上的 movsx:

movsx:
movsx   rax, edi
ret

屏蔽较高的 32 位将删除导致它只是零扩展:

#include <stdint.h>
uint64_t mov(int32_t X) { return (uint64_t)X & 0xFFFFFFFF; }
//or uint64_t mov(int32_t X) { return (uint64_t)(uint32_t)X; }

x86-64 上的 MOV:

mov:
mov     eax, edi
ret

https://gcc.godbolt.org/z/fihCmt

这两种方法都不会丢失较低 32 位的任何信息,因此这两种方法都是将 32 位整数存储到 64 位整数的有效方法。

mov的 x86-64 代码短一个字节(3 个字节对 4 个字节(。我认为应该不会有太大的速度差异,但如果有,我希望普通mov会赢一点点。

一种选择是在读回符号扩展名和上限值时解开它,但这可能会很混乱。

另一种选择是用位打包的单词构造一个联合。然后,这会将问题推迟到编译器进行优化:

union {
int64_t merged;
struct {
int64_t field1:32,
field2:32;
};
};

第三种选择是自己处理符号位。存储 15 位绝对值和 1 位符号。不是超级高效,但如果遇到非 2 的补码处理器,其中负符号值无法安全地转换为无符号,则更有可能是合法的。它们很少像母鸡的牙齿一样,所以我自己不会担心这个。

假设对64 位值的唯一操作是将其转换回 32(并可能存储/显示它(,则无需应用掩码。编译器将在将 32 位属性转换为 64 位时对其进行签名扩展,并在将 64 位值转换回 32 位时选择最低的 32 位。

#define packInt32(X) ((uint64_t)(X))
#define unpackInt32(X) ((int)(X))

或者更好的是,使用(内联(函数:

inline uint64_t packInt32(int x) { return ((uint64_t) x) ; }
inline int unpackInt32(uint64_t x) { return ((int) x) ; }

最新更新