【问题标题】:Store signed 32-bit in unsigned 64-bit int将带符号的 32 位存储在无符号的 64 位 int 中
【发布时间】:2023-03-11 08:21:01
【问题描述】:

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

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

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

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


问题是:

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

【问题讨论】:

  • 我不知道这是否是最快最有效的方式,但一种方式应该是#define packInt32(X) (((uint64_t)X & UINT32_MAX) | INT_MASK)
  • 什么是INT_MASK?你应该如何处理按位或INT_MASK?也许您打算将按位 and 与 INT_MASK 一起使用?
  • @Someprogrammerdude 它设置了一些标识符位。
  • 那么从技术上讲,它并不是真正的面具。掩码是您按位执行的操作,用于删除不需要的位。这就是您想要的(删除可能符号扩展值的高 32 位)。
  • 旁白:您真的应该在宏替换文本中的 X 周围添加括号。

标签: c bit-manipulation int32


【解决方案1】:

假设对 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) ; }

【讨论】:

  • 问题中的| INT_MASK 如何适合您的解决方案?
  • (int) X 不能保证将X 转换为 32 位。即使使用(int32_t) X,转换也是实现定义的。 C 标准不保证它的计算结果与重新解释为 int32_t 的 X 的低 32 位相同。
【解决方案2】:

您需要屏蔽除低 32 位之外的任何位。您可以使用按位与来做到这一点:

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

【讨论】:

  • (uint32_t) X 足以代替((uint64_t)X & 0xFFFFFFFF),它更简单地表达了OP 的意图,即拥有X 的32 位。
【解决方案3】:

您提到的“混乱”是因为您将一个小的有符号类型转换为一个大的无符号类型。 在此转换期间,首先通过应用符号扩展来调整大小。这就是造成您麻烦的原因。

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

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

【讨论】:

    【解决方案4】:

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

    另一种选择是使用位压缩字构造联合。然后,这会将问题交给编译器进行优化:

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

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

    【讨论】:

      【解决方案5】:

      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     eax, edi
              ret
      

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

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

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

      【讨论】:

        猜你喜欢
        • 2013-05-03
        • 2011-01-17
        • 1970-01-01
        • 2012-08-30
        • 1970-01-01
        • 1970-01-01
        • 2011-11-12
        • 2013-10-19
        • 2013-06-21
        相关资源
        最近更新 更多