3

这里有两个非常简单的程序。我希望得到相同的输出,但我没有。我不知道为什么。第一个输出 251。第二个输出 -5。我可以理解为什么是 251。但是,我不明白为什么第二个程序给了我 -5。

方案 1:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b= -5;

c =  (a + b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

输出:

c hex: fb
c dec: 251

方案 2:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b=  5;

c =  (a - b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

输出:

c hex: fffffffb
c dec: -5
4

5 回答 5

12

在第一个程序中,b=-5;将 251 分配给b. (转换为无符号类型总是以一加目标类型的最大值为模减少值。)

在第二个程序中,b=5;简单地将 5 分配给b,然后由于默认提升c = (a - b);将减法 0-5作为类型int执行- 简单地说,“小于”类型在用作算术和位运算符的操作数之前int总是被提升为。int

编辑:我错过的一件事:由于c有 type unsigned int,第二个程序中的结果 -5 将在执行unsigned int分配时转换为. 这就是您使用说明符 to看到的内容。使用 打印时,您会得到未定义的行为,因为需要一个 (signed)参数,并且您传递的参数的值在普通 (signed) 中无法表示。cUINT_MAX-4%xprintfc%d%dintunsigned intint

于 2011-09-07T02:44:26.937 回答
2

您正在使用格式说明符%d。将参数视为带符号的十进制数(基本上是int)。

您从第一个程序中得到 251,因为(unsigned char)-5251 然后您将其打印为带符号的十进制数字。它被提升为 4 个字节而不是 1,并且这些位是0,所以数字看起来像0000...251(其中251是二进制的,我只是没有转换它)。

您从第二个程序中得到 -5,因为它的(unsigned int)-5值很大,但转换为int, 它是-5. 由于您使用的方式,它被视为 int printf

使用格式说明符%ud打印无符号十进制值。

于 2011-09-07T02:46:24.647 回答
2

这里有两个不同的问题。第一个事实是,对于看起来相同的操作,您会获得不同的十六进制值。您缺少的基本事实是chars 被提升为ints (就像shorts 一样)来进行算术运算。这是区别:

a = 0  //0x00
b = -5 //0xfb
c = (int)a + (int)b

在这里,a被扩展到0x00000000并被b扩展到0x000000fb不是符号扩展,因为它是一个无符号字符)。然后,执行加法,我们得到0x000000fb

a = 0  //0x00
b = 5  //0x05
c = (int)a - (int)b

在这里,a被延伸到0x00000000并且b被延伸到0x00000005。然后,执行减法,我们得到0xfffffffb

解决方案?坚持chars 或ints; 混合它们会导致您意想不到的事情。

第二个问题是 anunsigned int被打印为-5,显然是一个有符号的值。但是,在字符串中,您告诉printf打印它的第二个参数,解释为带符号的 int(这就是"%d"意思)。这里的技巧是它printf不知道你传入的变量的类型。它只是按照字符串告诉它的方式解释它们。这是一个示例,我们告诉printf将指针打印为 int:

int main()
{
    int a = 0;
    int *p = &a;
    printf("%d\n", p);
}

当我运行这个程序时,我每次都会得到一个不同的值,即 的内存位置a,转换为以 10 为底。你可能会注意到这种事情会导致警告。你应该阅读编译器给你的所有警告,并且只有在你完全确定你正在做你想做的事情时才忽略它们。

于 2011-09-07T05:40:15.373 回答
1

您所看到的是底层机器如何表示数字的结果,C 标准如何定义有符号到无符号类型转换(用于算术)以及底层机器如何表示数字(用于未定义行为的结果)结尾)。

当我最初写我的回复时,我假设 C 标准没有明确定义应如何将有符号值转换为无符号值,因为该标准没有定义应如何表示有符号值或如何将无符号值转换为有符号值当范围超出有符号类型的范围时

然而,事实证明,当从负符号转换为正无符号值时,标准确实明确定义了这一点。在整数的情况下,负符号值 x 将被转换为 UINT_MAX+1-x,就像它被存储为二进制补码中的有符号值然后解释为无符号值一样。

所以当你说:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0; 
b = -5;
c = a + b;

b 的值变为 251,因为使用 C 标准将 -5 转换为无符号类型的值 UCHAR_MAX-5+1 (255-5+1)。然后在转换之后进行添加。这使得 a+b 与 0 + 251 相同,然后存储在 c 中。然而,当你说:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0;
b = 5;
c = (a-b);

printf("c dec: %d\n", c);

在这种情况下,a 和 b 被提升为无符号整数,以与 c 匹配,因此它们的值保持为 0 和 5。但是,无符号整数数学中的 0 - 5 会导致下溢错误,该错误被定义为导致 UINT_MAX+1-5。如果这发生在促销之前,则该值将是 UCHAR_MAX+1-5(即再次为 251)。

但是,您在输出中看到 -5 的原因是无符号整数 UINT_MAX-4 和 -5 具有相同的精确二进制表示这一事实的组合,就像 -5 和 251 对单字节数据类型所做的那样,并且事实上,当您使用 "%d" 作为格式化字符串时,它告诉 printf 将 c 的值解释为有符号整数而不是无符号整数。

由于未定义从无符号值到无效值的有符号值的转换,因此结果变得特定于实现。在您的情况下,由于底层机器对有符号值使用二进制补码,结果是无符号值 UINT_MAX-4 变为有符号值 -5。

在第一个程序中没有发生这种情况的唯一原因是无符号整数和有符号整数都可以表示 251,因此两者之间的转换是明确定义的,使用“%d”或“%u”无关紧要。但是,在第二个程序中,它会导致未定义的行为并成为特定于实现的原因,因为您的 UINT_MAX-4 的值超出了有符号整数的范围。

引擎盖下发生了什么

仔细检查您认为正在发生的事情或实际发生的事情应该发生什么总是好的,所以现在让我们看看编译器的汇编语言输出,看看到底发生了什么。这是第一个程序的有意义的部分:

    mov     BYTE PTR [rbp-1], 0   ; a becomes 0
    mov     BYTE PTR [rbp-2], -5  ; b becomes -5, which as an unsigned char is also 251
    movzx   edx, BYTE PTR [rbp-1] ; promote a by zero-extending to an unsigned int, which is now 0
    movzx   eax, BYTE PTR [rbp-2] ; promote b by zero-extending to an unsigned int which is now 251
    add     eax, edx  ; add a and b, that is, 0 and 251

请注意,虽然我们在字节 b 中存储了 -5 的有符号值,但当编译器提升它时,它会通过对数字进行零扩展来提升它,这意味着它被解释为 11111011 表示的无符号值而不是有符号值。然后将提升的值加在一起成为c。这也是 C 标准以它的方式定义有符号到无符号转换的原因——在使用二进制补码作为有符号值的架构上实现转换很容易。

现在使用程序 2:

    mov     BYTE PTR [rbp-1], 0 ; a = 0
    mov     BYTE PTR [rbp-2], 5 ; b = 5
    movzx   edx, BYTE PTR [rbp-1] ; a is promoted to 32-bit integer with value 0
    movzx   eax, BYTE PTR [rbp-2] ; b is promoted to a 32-bit integer with value 5
    mov     ecx, edx 
    sub     ecx, eax ; a - b is now done as 32-bit integers resulting in -5, which is '4294967291' when interpreted as unsigned

我们看到 a 和 b 在任何算术之前再次被提升,所以我们最终减去两个无符号整数,由于下溢导致 UINT_MAX-4,它也是 -5 作为有符号值。因此,无论您将其解释为有符号减法还是无符号减法,由于机器使用二进制补码形式,结果与 C 标准匹配,无需任何额外转换。

于 2011-09-07T03:05:46.403 回答
-1

将负数分配给无符号变量基本上是违反规则的。你正在做的是将负数转换为一个大的正数。从技术上讲,您甚至不能保证从一个处理器到另一个处理器的转换是相同的——在 1 的补码系统(如果仍然存在的话)上,您会得到不同的值,例如。

所以你得到你得到的。你不能指望有符号代数仍然适用。

于 2011-09-07T02:49:07.433 回答