C语言中,有符号整数和无符号整数如何存储负数?

5
这是一个例子:
#include <stdio.h>

int main()
{
    int x=35;
    int y=-35;
    unsigned int z=35;
    unsigned int p=-35;
    signed int q=-35;
    printf("Int(35d)=%d\n\
Int(-35d)=%d\n\
UInt(35u)=%u\n\
UInt(-35u)=%u\n\
UInt(-35d)=%d\n\
SInt(-35u)=%u\n",x,y,z,p,p,q);

    return 0;
}

输出:

Int(35d)=35
Int(-35d)=-35
UInt(35u)=35
UInt(-35u)=4294967261
UInt(-35d)=-35
SInt(-35u)=4294967261

如果我将值声明为有符号或无符号 int,真的有关系吗?因为,C 实际上只关心我如何从内存中读取值。请帮助我理解这一点,我希望你能证明我是错误的。


1
不是C语言,而是特定的计算机存储数字。C99标准仅记录它们行为的一组属性。 - Basile Starynkevitch
5个回答

8

有符号整数的表示 取决于底层平台,而不是C语言本身。 语言定义在大多数情况下对带符号整数表示方式持中立态度。 二进制补码 可能是最常见的,但还有其他表示方法,例如反码原码

在二进制补码系统中,通过反转位并加1来取反一个值。 要从5变为-5,您需要执行以下操作:

5 == 0101 => 1010 + 1 == 1011 == -5

要从-5回到5,您需要遵循相同的步骤:
-5 == 1011 => 0100 + 1 == 0101 == 5

声明值为有符号或无符号整型是否真的很重要吗?

是的,原因如下:

  1. 它影响您可以表示的值:无符号整数可以表示从02N-1的值,而有符号整数可以表示-2N-12N-1-1(二进制补码)之间的值。

  2. 对于无符号整数,溢出是被定义好的;UINT_MAX + 1将“回绕”回0。对于有符号整数,溢出是未定义的INT_MAX + 1 可能“回绕”到INT_MIN,也可能不会。

  3. 由于1和2,它会影响算术结果,特别是如果在同一表达式中混合使用有符号和无符号变量时(在这种情况下,如果存在溢出,则结果可能未被定义)。


4
一个无符号整数(unsigned int)和一个有符号整数(signed int)在内存中占用相同的字节数。它们可以存储相同的字节值。但是,根据它们是带符号还是无符号,数据将被不同地处理。
参见 http://en.wikipedia.org/wiki/Two%27s_complement 以了解表示整数值的最常见方法的说明。
由于在C中可以进行类型转换,因此您可以有效地强制编译器将无符号整数视为有符号整数,反之亦然,但请注意,这并不意味着它将执行您认为的操作或表示将是正确的。(在C中,溢出有符号整数会引发未定义行为。)
(如评论所指出的那样,还有其他表示整数的方式,但是在桌面计算机上,二进制补码是最常见的方式。)

这可能是 C 语言的做法……但只有在某些情况下。这种说法具有误导性和不完整性。 - Deduplicator
问题在于这并不是C语言的标准方式,而是可能偶尔会这样。有时它会以不同的方式完成任务,产生不同的结果。 - Deduplicator
1
一台计算机可能具有整数的补码表示,并具有C99标准实现。但这非常不寻常!而且C99实现甚至不需要任何计算机(你可以使用一群人类奴隶,但那是不道德、低效和脆弱的!)。 - Basile Starynkevitch
@Deduplicator 我稍微修改了一下,让我的意思更加明显。我并不是想暗示转换既不安全也不正确,只是在说明有时候可以强制编译器执行某些操作,无论它们有多么愚蠢。希望现在更清楚了,以避免混淆。 - Jite
@mafso 在 C 语言中,有符号整数的溢出是未定义行为。但是,这是不可能在没有进行操作的情况下发生的。可以将单个无符号/有符号 int 变量的字节值视为有符号 int 或无符号 int,而不会发生补码的情况。 - Jite
显示剩余3条评论

3

声明值为有符号或无符号int真的很重要吗?

是的。

例如,请看下面的示例:

#include <stdio.h>

int main()
{
    int a = -4;
    int b = -3;
    unsigned int c = -4;
    unsigned int d = -3;
    printf("%f\n%f\n%f\n%f\n", 1.0 * a/b, 1.0 * c/d, 1.0*a/d, 1.*c/b);
}

以及它的输出

1.333333
1.000000
-0.000000
-1431655764.000000

这明显表明,如果我将相同的字节表示解释为有符号或无符号,会产生很大差异。


有没有一种方法可以读取二进制表示以准确地了解其中发生的情况? - A6SE
@Deduplicator 谢谢,你知道无符号字符的说明符吗?我通常使用c来表示字符,但我不确定如何读取无符号字符。 - A6SE
@A6Tech:您将int变量读取为一系列sizeof unsigned char的连续项。但是将这些组成的unsigned char打印为整数。(为了达到最佳效果,请使用2位十六进制:%02X) - Deduplicator
@Deduplicator 但是,为什么glglgl的例子会给出不同的结果呢? - A6SE
1
@A6Tech:因为无论表达式是有符号还是无符号,的解释方式都不同。 - John Bode
显示剩余5条评论

1
#include <stdio.h>

int main(){
    int x = 35, y = -35;
    unsigned int z = 35, p = -35;
    signed int q = -35;

    printf("x=%d\tx=%u\ty=%d\ty=%u\tz=%d\tz=%u\tp=%d\tp=%u\tq=%d\tq=%u\t",x,x,y,y,z,z,p,p,q,q);
}

结果是:x=35 x=35 y=-35 y=4294967261 z=35 z=35 p=-35 p=4294967261 q=-35 q=4294967261

整数在内存中以补码形式存储,存储方式并不不同。

我可以使用0X…将35表示为0X00000023,将-35表示为0Xffffffdd,无论您使用有符号还是无符号,都没有区别。它只是以不同的方式输出。对于正数,%d和%u没有区别,但是对于负数,第一个位置是符号。如果您使用%u输出,则0Xffffffdd等于4294967261,但是使用%d时,0Xffffffdd可以表示为- 0X00000023,相当于-35。


这就是为什么我问无符号整数用于什么...但是glglgl的例子表明,将相同的二进制数字作为浮点数读取会产生不同的算术结果。但我不明白为什么... - A6SE
glglgl的示例展示了浮点数除法,该示例不是整数除法,数字以IEEE 754(http://en.wikipedia.org/wiki/IEEE_754-1985)格式存储在内存中,浮点除法有点复杂,您可以查看一些相关信息并区分符号或无符号。 - 丁东阳

-1
变量的类型定义最基本的是它在内存中存储的方式(即读取和写入)以及位的解释方式,因此您的声明可以被认为是“有效”的。
您还可以使用转换来查看问题。当您将带符号且为负的值存储在无符号变量中时,它会被转换为无符号变量。恰好发生的是,这种转换是可逆的,因此带符号的-35转换为无符号的4294967261,而当您要求时,可以将其转换为带符号的-35。这就是二进制补码编码(请参见其他答案中的链接)的工作原理。

那么,既然我可以将所有数字存储为int并根据需要以有符号或无符号方式读取它们,那么为什么还需要unsigned int呢? :D - A6SE
@A6Tech 如果一个词有两个意思,你如何知道该选择哪一个? - Jite
你也可以使用原始内存(void*)和原始缓冲区,并在各处进行类型转换,但那是C语言,而不是汇编语言(; - Freddie Chopin
1
二进制补码不是契约性的。 - Deduplicator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接