16位int机器(MSP430)和32位int机器(ARM CORTEX)的减法结果不同

当下面的代码针对像MSP430微控制器这样的16位整数机器运行时, s32产生65446。

 #include  uint16_t u16c; int32_t s32; int main() { u16c = 100U; s32 = 10 - u16c; } 

我的理解是10-u16c将隐式类型提升为unsigned int。 数学上10-u16c等于-90。 但是如何将负数表示为unsigned int呢? 当-90被提升为unsigned int时,是否意味着忽略了数字的符号?

让我们假设,数字的符号被忽略。
90的二进制表示是00000000 01011010 。 当这被分配给32位宽的有符号整数变量s32 ,转换是如何发生的?

为了使s32等于65446,90必须采用2的补码。 那将是00000000 10100110

我不太了解s32成为65446的过程。

在像ARM这样的32位宽整数机器中, s32是-90,这是正确的。

要在16位整数机器中修复这种情况, (int16_t)需要(int16_t)的类型转换。 这如何解决这个问题?

添加了s32六进制数据表示,如IAR Workbench(右下角)所示。 结果表明s32变为0x0000FFA6 。 因此对于MSP430,从无符号16位转换为有符号32位的机器实现,它只是预先设置16 0位。

在此处输入图像描述

我的理解是10-u16c将隐式类型提升为unsigned int

这取决于10的类型的表示( int ,就像它)。 对于某些系统 ,您的理解是正确的 ,我们将首先介绍它,但正如您稍后将在本答案中看到的那样,您错过了大部分图片。

第5.2.4节,环境限制规定int类型的值可以在-32767到32767之间; 此范围可以由实现自行决定扩展,但int必须能够表示此范围。

但是, uint16_t如果它存在 (它不是必需的 )的范围是0到65535.实现不能扩展它; 要求范围精确地为 [0..65535] (因此不需要存在此类型的原因)。

第6.3.1.3节,有符号和无符号整数告诉我们来回转换。 我不能更好地解释它,所以这是一个直接的引用:

1当具有整数类型的值转换为除_Bool之外的另一个整数类型时,如果该值可以由新类型表示,则它将保持不变。

2否则,如果新类型是无符号的,则通过重复加或减一个可以在新类型中表示的最大值来转换该值,直到该值在新类型的范围内。 60)

3否则,新类型已签名且值无法在其中表示; 结果是实现定义的,或者引发实现定义的信号。

这一切都支持你的理论,即当且仅当 int是16位类型时, int值10将被转换为uint16_t但是, 第6.3.1.8节中,应首先应用通常的算术转换规则来决定上述三种转换中的哪一种发生,因为当int大于16位时,这些规则会改变您查看转换的方式

如果两个操作数具有相同的类型,则不需要进一步转换。

否则,如果两个操作数都具有有符号整数类型或两者都具有无符号整数类型,则具有较小整数转换等级类型的操作数将转换为具有更高等级的操作数的类型。

否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的等级,则具有有符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。

否则,如果带有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数将转换为带有符号整数类型的操作数的类型。

否则,两个操作数都转换为无符号整数类型,对应于带有符号整数类型的操作数的类型。


因此,正如您所看到的,表达式10-u16c的类型可能因系统而异。 在int为16位的系统上,该表达式将为uint16_t

数学上10-u16c等于-90。 但是如何将负数表示为unsigned int呢? 当-90被提升为unsigned int时,是否意味着忽略了数字的符号?

根据附件H.2.2 :

在LIA-1意义上,C的无符号整数类型是“模数”,因为溢出或越界结果会无声地换行。

换句话说,如果将10转换为uint16_t并执行减法,则结果将是一个数字,在这种情况下,您可以通过显式将两个操作数(即转换它们)转换为uint16_t来查看该数字。 您可以通过使用无符号整数常量(如-90U来查看类似的效果。 这主要得到前面6.3.1.3引用的规则#2的支持。


当这被分配给32位宽的有符号整数变量s32时,转换是如何发生的?

表达式10-u16c根据6.3.1.3中的规则#1(上面引用)转换为int32_t值并存储为该值。


要在16位整数机器中修复这种情况, (int16_t)需要(int16_t)的类型转换。 这如何解决这个问题?

类型转换没有为此讨论添加有用的信息。 也许您正在使用不合规(错误)编译器。 我怀疑手册可能会对此有所了解,但由于我不知道你使用的是哪种编译器,我无法阅读它…

 100 = 0x0064 0x000A - 0x0064 = 0x000A + 0xFF9B + 1 = 0xFFA6 = 65446. 

请注意,以上都不是有符号或无符号的,加法和减法对这些事情都是盲目的。 现在16位数学运算完成后,它可以用符号扩展名提升为0xFFFFFF9B。 在两种情况下0xFF9B和0xFFFFFF9B,如果将这些位解释为有符号,则答案为-90,如果将这些位解释为无符号,则为65446,另一个为4294967206。

拿着这个:

 #include  int main() { unsigned int ra,rb; ra=0x00000005; for(rb=0;rb<10;rb++) { ra--; printf("0x%08X\n",ra); } return(0); } 

你懂了

 0x00000004 0x00000003 0x00000002 0x00000001 0x00000000 0xFFFFFFFF 0xFFFFFFFE 0xFFFFFFFD 0xFFFFFFFC 0xFFFFFFFB 

这正是你所期望的,你从全零中减去一个并得到所有零,与有符号或无符号无关。 从10减去100就像做了100次循环。

你期待看到:

 0x00000004 0x00000003 0x00000002 0x00000001 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 

对于上述计划? 那会准确吗? 那会有意义吗? 没有。

代码中唯一奇怪的部分是:

 s32 = 0xFFA6; 

现在实际上注释中的人可以直接进入,但标准是否说你的u16c从无符号(0x0064)转换为有符号(0x0064)或者它是否保持无符号而10(0x000A)被认为是无符号的? 基本上我们得到0x000A - 0x0064 = 0xFFA6作为有符号数学或无符号(我的猜测是无符号的,因为在该操作中声明的一件事是无符号的)。 然后将无符号位模式提升为有符号,你采用16位模式并将符号扩展为32位0xFFA6变为0xFFFFFFA6,这是我在带有gcc的桌面linux机器上得到的...

简短回答:

  • s32的类型与计算无关。
  • 整数常量10的类型为int
  • 如果int是16位,则不会发生整数提升。 通常的算术转换将10操作数转换为类型uint16_t (unsigned int)。 该操作在16位无符号类型上执行。
  • 无符号环绕给出10u - 100u = 65445u 。 = 0xFFA5。 这个结果可以放在int32_t 。 由于操作中涉及的类型是无符号的,因此不适用二进制补码。
  • 如果int是32位,则参数u16c是整数提升为int类型。 由于两个操作数在整数提升后都是int类型,因此不会进行进一步的算术转换。 该操作在32位有符号类型上执行,结果为-90

便携式代码应写为:

 10 - (int32_t)u16c; // signed arithmetic intended 

或者作为

 10u - u16c; // unsigned wrap-around intended