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

8

我正在开发一个嵌入式平台(ARM)并且在处理位模式时必须小心谨慎。假设这一行代码超出了我的控制范围:

uint8_t foo = 0xCE;          // 0b11001110

将其解释为无符号数,这将是206。但实际上它是有符号的,类似于-50。我如何继续使用该值作为有符号数?

int8_t bar = foo;            // doesn't work

也不这样做(导致所有输入值产生0x10或0x00)

int8_t bar = static_cast<int8_t>(foo);
int8_t bar = reinterpret_cast<int8_t&>(foo);

我希望比特位不受影响,即(bar == 0xCE)

反之,我想知道如何将表示负数的位模式传递到无符号变量中,而不会破坏位模式。我正在使用GCC。


1
将有符号数转换为无符号数将保留位模式,因此有符号字符-50将变为206无符号数。从无符号数转换为有符号数,如果它可以表示为有符号数,则是相同的数字,否则它是实现定义的。 - JohnPS
1
我的调试器骗了我,在我的平台上 int8_t bar = foo; 运行得很好。对此我感到抱歉,但仍然非常感谢它给我带来的启示。 - Sven-de
1
@Sven-de:是的,这可能是实现定义的,但大多数实现都会采取简单的方式(而不是将其饱和到最大值+127)。 - Jason S
@Sven-de:如果这个回答对您有用,请将其作为答案并接受它,或者接受Kerrek的非常相似的答案并附上评论? 当问题已经得到回答时,将其标记为尚未接受答案会令人困惑。谢谢! - Brooks Moses
5个回答

8

对于我来说,以下内容可以正常工作,尽管评论中指出这是实现定义的:

int x = (signed char)(foo);

在C++中,你也可以这样说:
int x = static_cast<signed char>(foo);

请注意,升级始终尝试在重新解释位模式之前保留。因此,您首先必须将其转换为与无符号类型大小相同的有符号类型,以强制进行有符号重新解释。
(当我尝试将 char 打印为十六进制数字对时,通常会遇到相反的问题。)

这从技术上讲是未定义行为,会导致溢出。 - Oliver Charlesworth
有趣 - 我认为这很明确...那么另一种情况呢,将有符号字符转换为无符号字符? - Kerrek SB
3
如果无法将一个无符号数表示为有符号数,则其转换结果是由实现定义的。有符号数转换为无符号数则变成“与源整数同余的最小无符号整数(模2^n)”。在二进制补码表示中,这只需保持相同的位模式即可。详见4.7整型转换章节。 - JohnPS
@JohnPS:我知道了,谢谢。我编辑了答案并说它是实现有关。 - Kerrek SB

6
uint8_t foo = 0xCE;          // 0b11001110
int8_t bar;
memcpy( &bar, &foo, 1 );

它甚至还有一个额外的好处,即99%的编译器会完全优化掉对memcpy的调用...


3

类似这样不太美观的东西?

int8_t bar = (foo > 127) ? ((int)foo - 256) : foo;

不依赖于行为未定义的转换。

我认为你可能是想说256而不是128,对吗?而且没有必要使用丑陋的C转换。 - Cheers and hth. - Alf
1
@Alf:确实,你可能是正确的,通常算术转换意味着强制转换(无论是C还是C++风格)是不必要的。但这些规则很难记住,所以我更喜欢明确地留下它。 - Oliver Charlesworth

0

使用GCC编译器,即使在嵌入式平台上,无符号值的机会也很大是二进制补码。

因此,8位数字0xCE表示为0xCE-256

因为二进制补码实际上只是模2 n,其中n是表示中的位数。

编辑:嗯,为了表述清晰起见,我最好给出一个具体的例子:

int8_t toInt8( uint8_t x )
{
    return (x >= 128? x - 256 : x);
}

编辑2:我没有看到最后一个问题,关于如何将位模式放入无符号变量中。这非常容易:只需赋值即可。结果由C++标准保证,即存储的值在模2 n下与分配的值同余(在时钟面上相等)。

祝好!


你是不是想说“有符号值是二进制补码”?当然,无符号值在位表示中没有符号概念和其他变化。 - underscore_d

0

您可以使用指针访问值的表示形式。通过重新解释指针类型而不是值类型,您应该能够复制表示形式。

uint8_t foo = 0xCE;          
int8_t bar = *reinterpret_cast<int8_t*>(&foo);

这违反了严格别名规则,所以可以被优化掉或以其他方式使其UB(未定义行为)。唯一标准的“重新解释”表示需要平凡可复制类型和memcpy到目标。希望编译器将其优化为始终所需的机器码重新解释,但是reinterpret_cast(也许令人遗憾)不能保证提供此功能。它是一个非常被误解的关键字,可能是因为严格别名使其与预期几乎无用。例外情况是对“char”类型进行reinterpret_cast以读取表示始终有效。 - underscore_d
@underscore_d 我认为这是允许的,因为它是“与对象的动态类型相对应的有符号或无符号类型”。不过可能只是我误读了该条款。 - D Krueger
是的,看来你是对的。感谢提醒有关有符号/无符号行的问题。然而,在这里使用reinterpret_cast并不必要,而且可能比简单的static_cast提供更低的成功保证:https://dev59.com/nXI-5IYBdhLWcg3wpaB1#1751368 ...或者我只是在搜索时失败了,试图找到有关reinterpret_cast如何在有符号和无符号之间转换的规范?如果没有指定,我会认为它会(A)任意做出决定...或者至少(B)像static_cast一样,这种情况下,就没有必要进行指针往返。 - underscore_d
@underscore_d 对指针进行static_cast会失败,因为uint8_tint8_t是不同的类型,所以这里需要使用reinterpret_cast。N3797说:“对象指针可以显式转换为不同类型的对象指针。”使用指针而不是值的原因是因为原始问题表明无符号和有符号值的位模式必须相同。对于二进制补码,这并不重要,但是对于其他表示,使用指针是必需的。 - D Krueger
根据我提供的答案,我所说的是在值之间进行static_cast,而不是指针。就其他表示而言,据我所知,标准只说reinterpret_cast执行的映射最多是实现定义的,这似乎并不能强烈保证OP想要的内容,只是可能由他们的实现提供的一个弱保证...目前为止。 - underscore_d
@underscore_d “映射”是指指针的实现定义表示。否则,它是明确定义的。 - D Krueger

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