无符号和较大签名类型之间隐式转换的不一致行为

时间:2014-06-02 12:12:24

标签: c c99 implicit-conversion c89 integer-promotion

请考虑以下示例:

#include <stdio.h>

int main(void)
{
    unsigned char a  = 15; /* one byte */
    unsigned short b = 15; /* two bytes */
    unsigned int c   = 15; /* four bytes */

    long x = -a; /* eight bytes */
    printf("%ld\n", x);

    x = -b;
    printf("%ld\n", x);

    x = -c;
    printf("%ld\n", x);

    return 0;
}

编译我正在使用GCC 4.4.7(它没有给我任何警告):

gcc -g -std=c99 -pedantic-errors -Wall -W check.c

我的结果是:

-15
-15
4294967281

问题是为什么unsigned charunsigned short值都正确地“传播”到(签名)long,而unsigned int不是?对此有任何参考或规则吗?

以下是来自gdb的结果(单词以小端顺序排列):

(gdb) x/2w &x
0x7fffffffe168: 11111111111111111111111111110001    11111111111111111111111111111111 

(gdb) x/2w &x
0x7fffffffe168: 11111111111111111111111111110001    00000000000000000000000000000000

5 个答案:

答案 0 :(得分:11)

这是由于整数提升如何应用于操作数以及一元减号的结果具有相同类型的要求。这将在6.5.3.3 一元算术运算符一节中介绍,并说(强调我的前进):

  

一元运算符的结果是其(提升的)操作数的否定。 对操作数执行整数提升,结果具有提升类型

和整数提升,草案c99标准部分6.3 转化并说:

  

如果int可以表示原始类型的所有值,则该值将转换为int;否则,它将转换为unsigned int。这些被称为整数促销。 48)所有其他类型的整数促销都没有改变。

在前两种情况下,促销将是 int ,结果将是 int 。对于 unsigned int ,不需要升级,但结果将需要转换回 unsigned int

-15使用6.3.1.3 签名和无符号整数中规定的规则转换为 unsigned int ,其中包含:

  

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

因此,我们最终得到-15 + (UMAX + 1),结果为UMAX - 14,从而产生较大的无符号值。有时,您会看到代码使用-1转换为无符号值以获取类型的最大无符号值,因为它总是最终为-1 + UMAX + 1 UMAX。< / p>

答案 1 :(得分:3)

int很特别。在算术运算中,小于int的所有内容都会被提升为int

因此,-a-b是一元减去int值15的应用程序,它只能工作并生成-15。然后将此值转换为long

-c不同。 c未升级为int,因为它不小于int。应用于unsigned int k值的一元减号的结果再次为unsigned int,计算为2 N -k(N是位数)

现在这个unsigned int值正常转换为long

答案 2 :(得分:3)

这种行为是正确的。报价来自C 9899:TC2。

6.5.3.3/3:

  

一元-运算符的结果是其(提升的)操作数的否定。整数提升在操作数上执行,结果具有提升类型。

6.2.5 / 9:

  

涉及无符号操作数的计算永远不会溢出,因为无法用结果无符号整数类型表示的结果将以比结果类型可以表示的最大值大1的数量为模。

6.3.1.1/2:

  

如果可以使用intunsigned int,则可以在表达式中使用以下内容:

     
      
  • 具有整数类型的对象或表达式,其整数转换等级小于或等于intunsigned int的等级。

  •   
  • 类型为_Boolintsigned intunsigned int的位字段。

  •   
     

如果int可以表示原始类型的所有值,则该值将转换为int;否则,它将转换为unsigned int。这些被称为整数促销。所有其他类型都不会被整数促销更改。

因此对于long x = -a;,因为操作数aunsigned char的转化排名低于intunsigned int的排名,所有{ {1}}值可以表示为unsigned char(在您的平台上),我们首先提升为int。否定的一点很简单:int的值为int

-15(在您的平台上)的逻辑相同。

促销不会更改unsigned short。所以unsigned int c的值是使用模运算计算的,给出结果-c

答案 3 :(得分:2)

C的整数提升规则就是它们的原因,因为标准编写者希望允许各种各样的现有实现执行不同的操作,在某些情况下,因为它们是在之前创建的 &#34;标准&#34;,继续做他们正在做的事情,同时为新的实施定义规则,而不是&#34;做任何你想做的事情&#34;。遗憾的是,编写的规则使得编写不依赖于编译器整数大小的代码变得非常困难。即使未来的处理器能够以比32位更快的速度执行64位操作,但如果int超过32位,标准规定的规则将导致大量代码中断。

回想起来可能会更好地处理&#34;奇怪的&#34;编译器通过明确地识别C的多种方言的存在,并建议编译器实现以一致的方式处理各种事物的方言,但是假设它们也可以实现以不同方式处理它们的方言。这样的方法最终可能最终成为int超过32位的唯一方式,但我甚至没有听说过有人这样做过。

我认为无符号整数类型问题的根源在于它们有时用于表示数值的事实,有时用于表示包装抽象代数环的成员。在不涉及类型提升的情况下,无符号类型的行为与抽象代数环一致。。将一元减号应用于环的一个成员应该(并且确实)产生同一环的成员,当添加到原始环时,将产生零[即加法逆]。只有一种方法可以将整数量映射到环元素,但存在多种方法将环元素映射回整数。因此,将环元素添加到整数量应该产生相同环的元素,而不管整数的大小,并且从环到整数的转换应该要求代码指定转换应该如何执行。不幸的是,在环的大小小于默认整数类型的情况下,或者当操作使用具有更大类型的整数的环成员时,C隐式地将环转换为整数。

解决这个问题的正确解决方案是允许代码指定某些变量,返回值等应该被视为环类型而不是数字;无论-(ring16_t)2的大小如何,int这样的表达式都应该产生65534,而不是在int为16位的系统上产生65534,而在系统中大于(ring32)0xC0000001 * (ring32)0xC0000001时产生的是。同样,即使(ring32)0x80000001恰好是64位,int也应该产生int [注意,如果{{1}}是64位,编译器可以合法地执行任何它喜欢的任何代码尝试乘以两个等于0xC0000001的无符号32位值,因为结果太大而无法用64位有符号整数表示。

答案 4 :(得分:0)

否定是棘手的。特别是在无符号值时。如果你看一下c文档,你会注意到(与你期望的相反)unsigned chars和short被提升为有符号的计算内存,而unsigned int将被计算为unsigned int。

当你计算-c时,c被视为一个int,它变为-15,然后存储在x中,(它仍然认为它是一个UNSIGNED int)并且存储为这样。

澄清 - 当“否定”未签名时,不进行实际推广。当您为任何类型的int指定负数(或取负数)时,将使用2的数字补码。由于无符号值和有符号值之间唯一的实际区别在于MSB充当符号标志,因此它被视为非常大的正数而不是负数。