【问题标题】:interpret signed as unsigned将有符号解释为无符号
【发布时间】:2009-11-17 19:56:46
【问题描述】:

我有这样的价值:

int64_t s_val = SOME_SIGNED_VALUE;

我怎样才能得到一个

uint64_t u_val

s_val 具有完全相同的位模式,但被视为无符号?

这可能真的很简单,但是在查看 Stackoverflow 和其他地方之后,我还没有找到答案。

【问题讨论】:

  • 所有人都投赞成票;感谢您的输入。我认为 static_cast 实际上是正确的答案。作为记录,我首先尝试过,但由于其他地方的愚蠢错误,我认为它没有保留位模式。为了澄清这个问题,可以 s_val != u_val (如果 s_val

标签: c++


【解决方案1】:
int64_t s_val = SOME_SIGNED_VALUE;
uint64_t u_val = static_cast<uint64_t>(s_val);

C++ 标准 4.7/2 规定:

如果目标类型是无符号的,则结果值是与源整数一致的最小无符号整数(模 2n,其中 n 是用于表示无符号类型的位数)。 [注意:在二进制补码表示中,这种转换是概念性的,位模式没有变化(如果没有截断)。 ]

另一方面,标准表示“reinterpret_cast 执行的映射是实现定义的。[注意:它可能会或可能不会产生与原始值不同的表示。]”(5.2.10/ 3)。所以,我建议使用static_cast

【讨论】:

    【解决方案2】:

    请注意,您根本不需要演员表。对于所有关于演员是否会为负面表示而浪费比特的争论,有一件事已经丢失 - 演员完全没有必要。

    由于 C/C++ 将执行的转换(以及如何定义转换),因此:

    int64_t s_val = SOME_SIGNED_VALUE;
    uint64_t u_val = s_val;
    

    完全等同于:

    int64_t s_val = SOME_SIGNED_VALUE;
    uint64_t u_val = static_cast<uint64_t>(s_val);
    

    也就是说,您可能仍然需要演员表,因为它表明了意图。但是,我听说它认为您不应该使用不必要的强制转换,因为它可以在您可能想要警告的情况下使编译器静音。

    选择你的毒药。

    【讨论】:

    • 啊哈,这很好。就我而言,我认为最好发出意图信号,但我很高兴看到警告。
    【解决方案3】:

    我同意 static_cast 在这种情况下是合适的,但没有人提到过一个看起来非常相似的情况,即 static_cast 不会像预期的那样保留位。

    char x = -1; // 255
    unsigned int x2 = static_cast<unsigned int>(x); // 4294967295
    unsigned int x3 = static_cast<unsigned int>(static_cast<unsigned char>(x)); // 255
    

    当您从一个小的有符号值转换为一个大的无符号值时,请注意sign extension。可能其他组合也很容易受到攻击 - 我一直没有想到。

    【讨论】:

    • 有趣点;感谢您解释并提供签名扩展的链接。
    • 请注意,第二行也允许产生255,因为允许实现使char无符号。
    【解决方案4】:

    一般来说,使用static_cast&lt;int64_t&gt;reinterpret_cast&lt;int64_t&gt; 都没有关系。只要您在使用two's complement 表示负数的处理器上运行,结果是相同的。 (实际上所有现代处理器都使用它。)在二进制补码下,有符号整数中的正数在无符号整数中的表示方式相同。如果它是一个负数,它将被重新解释为一个无符号形式的大正数。

    基本上,您的转换所做的是告诉编译器在处理该值时生成不同的汇编指令。例如。有符号整数的乘法和除法有不同的指令。虽然加法和减法保持不变(阅读维基百科链接,你就会明白)。

    【讨论】:

    • 但是负值呢?
    • 规则是“如果新类型是无符号的,则在新类型能表示的最大值的基础上反复加减一,直到值在新类型”,它适用于 2 的补码,我认为是 1 的补码,但我不会发誓它为负数的更多比扎罗表示保持相同的位模式。
    • 以上来自 C99 标准,C++ 标准这样说:“结果值是与源整数一致的最小无符号整数(模 2n 其中 n 是用于表示无符号类型)。[注意:在二进制补码表示中,这种转换是概念性的,位模式没有变化(如果没有截断)。]"
    • 我相信几乎所有处理器都使用二进制补码。不过我会加进去的。
    • 毫无疑问,二进制补码是迄今为止最有可能用于负数的表示形式。我个人除了在大学里的一些 Univac(听起来像是科幻小说中的东西)之外,除了二进制补码机器之外没有做过任何其他工作。它有 36 位字要引导。
    【解决方案5】:

    逻辑位模式(值表示的位),即只有在原始有符号值非负时才能保留二进制数字的值,因为负值不能由无符号整数变量表示。您需要做的就是将您的签名值分配给您的无符号整数对象,然后您就完成了

    uint64_t u_val = s_val;
    

    显式转换不是必需的,但可用于抑制编译器警告。

    至于物理位模式(即您在原始内存中看到的、对象表示的位),您根本无法以这种方式“转换”它。 C++ 语言不提供任何可以保证保留物理位模式的转换方法。您所能做的就是重新解释已签名对象占用的内存为相同大小的未签名对象

    STATIC_ASSERT(sizeof(int64_t) == sizeof(uint64_t));
    uint64_t u_val = reinterpret_cast<uint64_t&>(s_val);
    

    同样,这不是转换,而是记忆的重新解释。这不能保证有效,而且通常是非法的。

    【讨论】:

    • 区分“转换”和“解释”的好点!
    • 怎么违法? “工作”是什么意思?你可以reinterpret回来,不是吗?
    • 这是非法的,因为 C++ 语言明确禁止将 T 类型的对象占用的内存作为不同类型的 U 对象访问(除了少数例外)。换句话说,读取重新解释的记忆几乎总是非法的。
    • 是的,关于“转换”和“解释”的观点很好。问题标题现在使用正确的术语。
    【解决方案6】:

    想分享这个 C++14 现代通用解决方案。原演示here

    template<class T> 
    auto as_unsigned(T t) 
    { 
        return std::make_unsigned_t<T>(t); 
    }
    

    可以如下使用:

    auto sx = int32_t{ 55 };
    auto ux = as_unsigned(sx);
    

    你可以在here看到它。

    【讨论】:

    • 我也希望有类似的指针。我目前必须为我需要的每个转换添加非泛型重载,例如 char *uint8_t *
    【解决方案7】:

    你也可以reinterpret_cast它,或者使用union

    union {
       int64_t i64;
       uint64_t ui64;
    } variable;
    
    variable.i64 = SOME_SIGNED_VALUE;
    uint64_t a_copy = variable.ui64;
    

    【讨论】:

    • static_cast 不会导致丢失位模式。
    • 我有点担心 static_cast。我还建议使用 reinterpret_cast 因为位模式语句。您确定 static_cast 会起作用吗(我认为它会起作用,但手边没有标准的副本)。但是 reinterpret_cast 也表明它是一个不安全的演员表。
    • @Martin:不安全可能是可以理解的,因为他关心的是位,而不是值。
    • 4.7/2 "<...>在二进制补码表示中,这种转换是概念性的,位模式没有变化"。 static_cast 使用 4.7/2 的转换。
    • 对这个确切问题的困惑是我喜欢使用引用来代替的原因。
    猜你喜欢
    • 2011-11-14
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 1970-01-01
    • 2011-08-09
    • 2011-06-14
    相关资源
    最近更新 更多