将有符号数解释为无符号数。

33

我有一个像这样的值:

int64_t s_val = SOME_SIGNED_VALUE;

我该怎么获取

uint64_t u_val

如何得到一个无符号整数,它与s_val具有相同的二进制模式,但被视为无符号数?

这可能非常简单,但是在 Stackoverflow 和其他地方搜索之后,我没有找到答案。


1
给大家点赞,感谢你们的参与。我认为 static_cast 实际上是正确答案。为了记录,我先尝试过了,但由于其他地方的错误,我认为它没有保持位模式。为了澄清问题,s_val != u_val 是可以的(如果 s_val < 0 的话),位才是最重要的。 - bbg
7个回答

38
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


10

请注意,你根本不需要进行强制转换。尽管人们一直争论强制转换是否会对负数表示混淆位,但有一点已经被忽略了——强制转换是完全不必要的。

由于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);

话虽如此,您可能仍然希望进行强制类型转换,因为它表明了意图。 然而,有人认为您不应该使用不必要的强制类型转换,因为它会在某些情况下使编译器保持沉默,而您可能想要一个警告。

选择你的毒药。


1
啊哈,这是一个很好的观点。在我的情况下,我认为最好的方法是表明意图,但我很高兴看到这个警告。 - bbg

7

我同意在这种情况下使用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

在将小的有符号值转换为大的无符号值时,要注意符号扩展。可能还有其他组合也存在漏洞——我还没有完全考虑清楚。


有趣的观点;感谢您的解释并提供符号扩展的链接。 - bbg
3
请注意,第二行也可以产生 255,因为实现允许使 char 无符号。 - mtraceur

7

一般来说,使用static_cast<int64_t>reinterpret_cast<int64_t>都无所谓。只要您在运行使用二进制补码表示负数的处理器上,结果是相同的。(现代几乎所有处理器都使用这种方式)。在二进制补码下,有符号整数中的正数和无符号整数中的正数表示方式相同;如果是负数,则会在无符号形式下重新解释为一个大的正数。

基本上,您的类型转换让编译器在处理该值时生成不同的汇编指令。例如,对于有符号整数,乘法和除法有不同的指令。尽管加法和减法保持不变(阅读维基百科链接,您将理解)。


但是负值呢? - Martin York
规则是:“如果新类型是无符号的,则通过反复添加或减去比新类型中可以表示的最大值多一个的值,直到该值在新类型的范围内为止进行转换”,这适用于2的补码,我认为也适用于1的补码,但我不能保证它对于更奇怪的负数表示方式能够保持相同的位模式。 - Michael Burr
以上内容来自C99标准,C++标准表示:“结果值是与源整数同余的最小无符号整数(模2n,其中n是用于表示无符号类型的位数)。[注意:在二进制补码表示中,此转换是概念性的,如果没有截断,则位模式不会改变。]” - Michael Burr
我相信几乎所有的处理器都使用二进制补码。但我会添加它。 - int3
毫无疑问,二进制补码是负数最常用的表示方法。除了大学时使用过一些类似于科幻小说中的 Univac 36 位字机器之外,我个人从未使用过其他表示方法。 - Michael Burr
很不幸的是,直到标准规定整数以二进制补码表示之前,上述保证对我来说并不足够可靠。从历史上看,有太多程序因为像这样从未再次验证的假设而出现错误,在将代码移植到另一个平台时问题难以追踪,而且修复这个 bug 几乎是不可能的。 - Martin York

6

我想分享这个基于C++14的现代通用解决方案。原始演示可以在这里找到。

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);

您可以在这里看到它的实际应用。


我也希望指针能有类似的东西。目前,我需要为每个需要转换的类型添加非泛型重载,例如 char *uint8_t * - user2394284

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);

需要说明的是,这不是一种转换,而是一种内存重新解释。这种方法不能保证可行,通常是非法的。


很好的点来区分“转换”和“解释”! - xtofl
它为什么是非法的?'工作'是什么意思?你可以重新解释回去,不是吗? - xtofl
这是非法的,因为C++语言明确禁止将类型为T的对象占用的内存作为不同类型U的对象进行访问(有少数例外)。换句话说,重新解释内存的读取几乎总是非法的。 - AnT stands with Russia
是的,“转换”和“解释”方面的观点很好。现在问题的标题使用了正确的术语。 - bbg

2

您还可以使用reinterpret_cast或使用union

union {
   int64_t i64;
   uint64_t ui64;
} variable;

variable.i64 = SOME_SIGNED_VALUE;
uint64_t a_copy = variable.ui64;

1
static_cast 不会导致位模式丢失。 - Kirill V. Lyadvinsky
1
我有些担心static_cast<>。因为位模式语句,我还建议使用reinterpret_cast<>。你确定static_cast<>能够工作吗?(我认为它可以,但手头没有标准的副本)。但reinterpret_cast<>也表明这是一种不安全的转换方式。 - Martin York
@Martin:可能是因为他关注的是位而不是值,所以他对不安全性感到担忧。 - xtofl
4.7/2 "<...> 在二进制补码表示法中,这种转换是概念性的,位模式不会发生改变"。static_cast 使用来自4.7/2的转换。 - Kirill V. Lyadvinsky
对于这个确切问题的困惑,这就是为什么我喜欢进行引用转换。 - T.E.D.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接