在减法中,16位int机器和32位int机器之间的结果不同

时间:2017-05-20 02:23:31

标签: c arm embedded msp430 cortex-m

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

#include <stdint.h>

uint16_t   u16c;
int32_t    s32;

int main()
{
    u16c = 100U;
    s32  = 10 - u16c;
}

我的理解是10 - u16c将隐式类型提升转换为unsigned int。数学上10 - u16c等于-90。但是如何将负数表示为无符号整数?

当-90被提升为unsigned int时,是否意味着忽略了数字的符号?

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

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

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

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

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

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

enter image description here

3 个答案:

答案 0 :(得分:5)

  

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

这取决于10int的类型的表示形式。您的理解对某些系统来说是正确的,我们将首先介绍,但正如您在本回答中稍后会看到的那样,您错过了很大一部分内容。图片。

Section 5.2.4, Environmental limits指定类型int的值范围为-32767到32767;此范围可以由实现自行决定,但int必须能够表示此范围。

uint16_t但是,如果存在 延伸;要求范围正好 [0..65535](因此不需要存在此类型的原因)。

Section 6.3.1.3, Signed and unsigned integers告诉我们来回转换。我无法更好地解释它,所以这里是直接引用:

  

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

     

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

     

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

当且仅当 int是16位类型时,这一切都支持您的理论,即uint16_t值10将转换为int 但是,应首先>应用section 6.3.1.8, usual arithmetic conversion规则来决定上述三种转化中的哪一种发生,因为这些规则会改变您在{转换{{}时查看转化的方式{1}}大于16位

  

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

     

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

     

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

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

     

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

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

  

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

根据Annex H.2.2

  

C的无符号整数类型是&#39;&#39; modulo&#39;&#39;在LIA-1意义上,溢出或越界结果会无声地包裹。

换句话说,如果将uint16_t转换为10并执行减法,则结果将为数字,在这种情况下您可以看到通过显式地将两个操作数(即转换它们)转换为uint16_t来编号。您可以通过使用无符号整数常量(例如uint16_t)来查看类似的效果。这在很大程度上受到前面6.3.1.3引用的规则#2的支持。

  

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

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

  

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

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

答案 1 :(得分:3)

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

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

拿这个:

#include <stdio.h>

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机器上得到的...

答案 2 :(得分:1)

简短回答:

  • 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