14

考虑以下示例:

#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值都正确地“传播”到 (signed) long,而unsigned int不是?对此有什么参考或规则吗?

以下是相应的gdb(单词按小端顺序)的结果:

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

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

5 回答 5

12

这是由于整数提升如何应用于操作数以及一元减法的结果具有相同类型的要求。6.5.3.3 一元算术运算符部分对此进行了介绍,并说(强调我的未来):

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

和整数提升,在草案 c99 标准部分6.3 转换中涵盖,并说:

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

在前两种情况下,提升将是int并且结果将是int。在unsigned int的情况下,不需要提升,但结果需要转换回unsigned int

使用有符号和无符号整数部分中规定的规则将其-15转换为符号整数,该规则说:6.3.1.3

否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上重复加减一,直到该值在新类型的范围内。49)

所以我们最终得到-15 + (UMAX + 1)哪个结果导致UMAX - 14哪个结果是一个大的无符号值。这就是为什么您会看到代码使用-1转换为无符号值以获得类型的最大无符号值的原因,因为它最终总是是-1 + UMAX + 1which is UMAX

于 2014-06-02T12:28:25.157 回答
3

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

因此-a-b一元减法应用于int15 的值,它只是工作并产生 -15。然后将该值转换为long

-c是不同的。c不提升为 ,int因为它不小于int。一元减法的结果再次是 an ,unsigned int计算为 2 N -k (N 是位数)。kunsigned int

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

于 2014-06-02T12:40:48.673 回答
3

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

6.5.3.3/3:

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

6.2.5/9:

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

6.3.1.1/2:

以下可以用在表达式中,只要可以使用intor unsigned int

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

  • _Bool, int,signed int或类型的位域unsigned int

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

因此long x = -a;,由于操作数aan 的unsigned char转换等级小于 and 的等级intunsigned int并且所有unsigned char值都可以表示为int(在您的平台上),我们首先将其提升为 type int。消极的一面很简单:intwith value -15

unsigned short(在您的平台上)的相同逻辑。

unsigned int c不因促销而改变。所以 的值-c是使用模算术计算的,给出结果UINT_MAX-14

于 2014-06-02T12:42:34.617 回答
2

C 的整数提升规则之所以如此,是因为标准编写者希望允许各种各样的现有实现做不同的事情,在某些情况下,因为它们是在“标准”出现之前创建,继续做他们正在做的事情,而为比“随心所欲”更具体的新实现定义规则。不幸的是,所写的规则使得编写不依赖于编译器整数大小的代码变得极其困难。即使未来的处理器能够比 32 位更快地执行 64 位操作,如果超过 32 位,标准规定的规则也会导致大量代码中断int

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

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

The proper solution to solve this problem would be to allow code to specify that certain variables, return values, etc. should be regarded as ring types rather than numbers; an expression like -(ring16_t)2 should yield 65534 regardless of the size of int, rather than yielding 65534 on systems where int is 16 bits, and -2 on systems where it's larger. Likewise, (ring32)0xC0000001 * (ring32)0xC0000001 should yield (ring32)0x80000001 even if int happens to be 64 bits [note that if int is 64 bits, the compiler could legally do anything it likes if code tries to multiply two unsigned 32-bit values which equal 0xC0000001, since the result would be too large to represent in a 64-bit signed integer.

于 2014-06-02T17:59:35.817 回答
0

负数很棘手。特别是在涉及无符号值时。如果您查看 c 文档,您会注意到(与您的预期相反)无符号字符和短裤被提升为有符号整数进行计算,而无符号整数将被计算为无符号整数。

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

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

于 2014-06-02T12:28:21.487 回答