在C++中将无符号(long) int解释为有符号的

4
我想将一个unsigned long的32位重新解释为signed long。完全相同的位,只是被视为2的补码整数而不是无符号整数。我认为仅仅将其转换为long不会奏效。我错了吗?
或者也许有更好的方法。我正在使用一个无符号长整型作为计时器。偶尔我读取它的当前值并将其与先前的读数(两个unsigned long)进行比较,以查看经过了多长时间。我需要处理可能的溢出,这会导致当前值小于先前的值。将两个值都解释为有符号长整型并进行减法似乎可以得到正确的答案。
我尝试了这个:
return reinterpret_cast<long>(time4) - reinterpret_cast<long>(currTimeLo); // treat unsigned as 2's complement

但是我刚刚遇到了编译错误:
Arduino: 1.6.7 (Mac OS X), Board: "Arduino Nano, ATmega328"
invalid cast from type 'long unsigned int' to type 'long int'

在我看来,使用无符号值应该也可以达到同样的效果。你不能检查第二个值是否小于第一个值吗? - zneak
如果新值大于旧值,我可以简单地进行减法运算。但是当发生溢出时,新值小于旧值,简单的减法运算就不起作用了。以下是一个使用8位值的例子:假设旧值为0xFA(250十进制),新值为0x15(21十进制),因为计数器已经溢出。将它们视为无符号值,我们将从15中减去250。我不确定在使用无符号值时得到负结果会发生什么。将它们重新解释为有符号值,我们将从21中减去-6,得到正确的答案27。 - user3238181
抱歉,我的意思是“将它们视为无符号值,我们将从21中减去250。” - user3238181
至少有一个国际公认的C ++专家做过你在这里所做的事情,所以这不是只有新手才会犯的错误。我知道,因为我给他发了邮件,并要求他要求我们不要发布帖子(在clc++m中),他也这样做了。 :) 无论如何,reinterpret_cast只直接重新解释指针和引用,而不是整数值。在这种情况下,可以通过static_cast实现重新解释,但有一个警告:g ++以利用每个小形式漏洞来绊倒实用导向的程序员而闻名。因此,您可能需要测试并搜索相关选项。在Arduino上。 - Cheers and hth. - Alf
@Cheersandhth.-Alf "有一个警告:g++ 以利用每个形式漏洞来绊倒实践思维程序员而闻名。因此,您可能需要测试和搜索相关选项。在 Arduino 上。" 这正是我感觉下面答案缺少的内容。如果您想讨论,请随时加入 聊天室 - Baum mit Augen
显示剩余2条评论
3个回答

2

关于比较两个无符号计数器来检查经过了多长时间的更深层次/原始问题,其中可能只有一个单独的环绕:

只需使用无符号算术将最早时间从最近时间中减去。

假设您的currTimeLo是当前时间的计数器值,time4是一些更早的值,并且它们都是无符号类型(或有符号类型升级为另一个无符号类型),

return currTimeLo - time4;

这是因为C++保证无符号算术是在模2的n次幂下执行,其中n是无符号类型值表示中的位数。
如果发生了超过1次的包装,这种方法就不适用了。在这种情况下,您需要使用具有更大数字范围的类型。
关于问题标题中将无符号值解释为二进制补码有问题的问题:
首先注意这并不是必要的。它是X/Y问题中的Y。获取两个计数器之间的差异(其中最新计数器可能已经回绕)是原始的X,并且它有一个简单的解决方案(如上所述)。
但是,既然问题标题中提到了这个,那么:
据我所知,所有现有的C++实现都是针对使用二进制补码表示的有符号整数的体系结构。
The Holy Standard™把当原始值不能被表示为有符号整数类型时转换为有符号整数类型的结果留给了实现定义。任何合理的C++实现都会通过static_cast让您这样做。
return static_cast<long>(time4) - static_cast<long>(currTimeLo);

但是不能保证您在Arduino中使用的编译器对此事合理。

您需要检查并在必要时使用相关选项,假设如果默认行为不合理,则可以调整行为。

解决方法包括:

  • 通过reinterpret_cast进行指针或引用强制转换,

  • 通过例如memcpy复制字节,正式上安全但复杂且可能效率低下,

  • 使用正式UB联合成员访问,或者

  • 安全但复杂,将值分割并重新组合。

最后一点可以用一种几乎优雅的方式完成,这是之前在SO上回答这个问题的语言律师变体的人发表的。不幸的是我不记得那个技巧了,只知道它给我留下了深刻印象,因为它显然很明显,但我没有想到。但我建议使用简单的static_cast,并经过适当测试。


0

您想要使用static_cast,可能是这样的:

static_cast<signed long>(your_unsigned_long)

1
我不太明白这是否回答了它在Arduino上是否会做正确的事情。 - Baum mit Augen
1
@BaummitAugen,无论是在Arduino上还是其他地方,这就是你用C++的方法。但如果Arduino使用了一种它称之为C++的非C++语言,那么为什么问题要标记为C++而不是“那个Arduino称之为C++但实际上并不是C++的东西”呢? - Paul Evans
1
至少它可以编译。我不知道为什么“reinterpret_cast”不能。 - user3238181
2
因为在这种情况下reinterpret_cast不是正确的做法,应该使用static_cast。但是你最好知道自己在做什么,因为所有的类型转换都是危险的!如果你想要纯洁性,请重新设计你的代码,不要使用这些花招。 - Paul Evans
2
因为reinterpret_cast不适用于整数转换。如果您想将unsigned long用作字节序列,则可以将其地址转换为char* - Baum mit Augen
显示剩余14条评论

0
在二进制补码中,有符号和无符号之间的简单转换是可行的,因为它将值模2的n次方,即将相同的位模式视为另一种类型。例如,(long)0xFFFFFFFFu返回-1。
然而,问题在于加法和减法都会产生一个额外的进位/借位比特。这个额外的比特必须与低32位一起存储。因此,仅将值强制转换为signed并进行减法运算并不适用于所有情况,尽管对于彼此相差不大的值可以使用。尝试LONG_MAX - LONG_MINLONG_MIN - LONG_MAX,看看即使两个操作数都是long时结果也无法存储在long中。
为了解决这个问题,唯一的方法就是使用更宽的类型。
return static_cast<long long>(time4) - static_cast<long long>(currTimeLo);

或者手动处理大整数算术

unsigned long timeDiff;
if (time4 > rcurrTimeLo) // time hasn't overflowed and time4 is later than rcurrTimeLo
{
    timeDiff = time4 - rcurrTimeLo;
    // do something, for example set overflow/later flag:
    later = 1;
}
else
{
    timeDiff = rcurrTimeLo - time4;
    // set "earlier" flag
    later = 0;
}

如果您在一个函数中使用它,您必须返回溢出进位和低32位差异,因此第一种解决方案似乎在32位或64位计算机上更容易,而第二种解决方案将在像ATmega这样的8位MCU上更快。

如果您可以保证这两个操作数之间永远不会超过LONG_MAX,那么简单的static_cast到长整型就可以工作。

return static_cast<long>(time4) - static_cast<long>(currTimeLo);

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