【问题标题】:Adding unsigned integers in C在 C 中添加无符号整数
【发布时间】:2011-09-07 02:40:10
【问题描述】:

这里有两个非常简单的程序。我希望得到相同的输出,但我没有。我不知道为什么。第一个输出 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

【问题讨论】:

  • 您的两个程序都尝试使用%d 格式说明符打印unsigned int 值。这是非法的,并且行为未定义。为了有意义地 printfunsigned int 值,您需要 %u 或任何其他需要 unsigned int 参数的说明符。 %x 很好,因为它需要 unsigned int。但是%d 是完全不能接受的。这就是您得到奇怪结果的部分原因。
  • 真的吗?仍然应该解释为什么我得到了我得到的。
  • 正如 AndreyT 解释的那样,行为是“未定义的”——这已经足够解释了。如果您研究二进制机器算术,您可以弄清楚为什么它会以这种方式工作,但是从技术上讲,您看到的结果不是必需的——机器可以合法地打印“potato”而不是“-5”,因为这种行为是“未定义”。
  • @user678392:真的,真的。正如我所说,您的代码会产生未定义的行为。对于所有有意义的手段和目的,它的行为本质上是随机的,或者充其量是特定于实现的。虽然肯定有可能对你“得到”的东西提出一个确定性的解释,但它没有任何用处。这是浪费时间。

标签: c math unsigned-integer integer-promotion


【解决方案1】:

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

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

编辑:我错过了一件事情:由于c 的类型为unsigned int,当分配给c 时,第二个程序中的结果-5 将转换为unsigned int执行,导致UINT_MAX-4。这就是您使用 %x 说明符到 printf 时看到的内容。当使用%d 打印c 时,您会得到未定义的行为,因为%d 需要一个(签名的)int 参数,并且您传递了一个unsigned int 参数,其值不能用普通(签名)int 表示.

【讨论】:

  • 在汇编级别,问题可能是使用扩展符号位的指令执行对int 的提升,这就是0xfb 变为0xfffffffb 的原因。如果在没有扩展符号位的情况下完成转换,那么您将得到0x000000fb,即十进制的 251。
  • @R.. 好像您在说 c = (a - b) 与 c = (a + -b) 不同。
  • @aroth 为什么转换是在一种情况下而不是另一种情况下完成?
  • 而且这些都不是“任意的”,如果任意你的意思是“取决于编译器”。这一切都在 C 语言规范中严格/准确地指定。
  • 这个答案是正确的。在表达式c = (a - b);中,a的值提升为int类型; b 的值提升为 int 类型;减法在int 类型中完成,结果为-5;然后将-5 转换为unsigned int,结果为UINT_MAX + 1 - 5
【解决方案2】:

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

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

这里,a 扩展为0x00000000b 扩展为0x000000fb不是符号扩展,因为它是一个无符号字符) .然后进行加法,得到0x000000fb

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

这里,a 扩展为 0x00000000b 扩展为 0x00000005。然后进行减法运算,得到0xfffffffb

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

    • 为什么第二个程序有 (unsigned int) 而第一个程序有 unsigned char?
    • @user 因为那是他们的类型。
    • 卡伦吉啊? a 和 b 都是无符号字符。 c 是一个无符号整数。我真的很困惑你从哪里得到你的数据类型。
    【解决方案4】:

    您所看到的是 底层机器如何表示数字 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标准,无需任何额外转换。

    【讨论】:

    • 谢谢。但是,我仍然想知道为什么在第二种情况下将 a 和 b 提升为 unsigned int 而不是第一种?
    • 他们仍然在第一种情况下被提升。但是,由于第一个程序中的 unsigned char 赋值,值 '-5' 已经转换为 251,并且值 251 的 unsigned char 提升为 unsigned int 仍然具有值 251,因此 a+b 也是 251。
    • 实际上,考虑到我没有费心查看编译器生成的汇编代码,我可能在细节上还差得很远,但是这一切都归结为 -5 作为 unsigned char变为 251,因为两者都是 11111011,但是当从 unsigned char 提升为 unsigned int 时,将转换为 00000000 00000000 00000000 11111011,而当提升为 int 时,-5 从 11111011 变为 11111111 11111111 11111111 11111011(在我的机器上),这是 -5 有符号和 4294967291 无符号。换句话说,一旦它成为无符号类型,promotion 就会扩展 0 而不是 1。
    • 现在我很困惑。当我减去 a - b 时,为什么不等于 0 +(251 的位模式)?如果是,那么为什么传播 1 而不是 0?
    • 促销优先。使用 a - b,首先 a 变为 00000000 00000000 00000000 00000000 然后 b 变为 00000000 00000000 00000000 00000101 由于升级。然后发生减法,由于下溢导致11111111 11111111 11111111 11111011
    【解决方案5】:

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

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

    【讨论】:

    • 所以基本上你不知道为什么我得到我得到的东西。
    • 把位写出来,你自己就能弄明白。
    • 我已经写出了这些位。问题归结为数据类型转换,而不仅仅是位模式。
    • 这个答案是绝对错误的。转换为无符号类型的结果总是定义明确的;它是减少模一加目标类型的最大值,到目标类型的范围内。
    • @R.. -- 在一个补码机器上不是这样。并且目标类型的范围不固定。
    猜你喜欢
    • 2015-02-24
    • 2013-10-27
    • 2019-04-26
    • 1970-01-01
    • 2013-02-04
    • 2018-08-18
    • 1970-01-01
    • 2012-08-13
    相关资源
    最近更新 更多