为啥 unsigned int 0xFFFFFFFF 等于 int -1?
Posted
技术标签:
【中文标题】为啥 unsigned int 0xFFFFFFFF 等于 int -1?【英文标题】:Why unsigned int 0xFFFFFFFF is equal to int -1?为什么 unsigned int 0xFFFFFFFF 等于 int -1? 【发布时间】:2010-12-24 04:54:48 【问题描述】:在 C 或 C++ 中,据说 size_t(无符号 int 数据类型)可以容纳的最大数量与将 -1 转换为该数据类型相同。例如见Invalid Value for size_t
为什么?
我的意思是,(谈论 32 位整数)AFAIK 最高有效位以有符号数据类型保存符号(即位 0x80000000 形成负数)。那么,1 是 0x00000001.. 0x7FFFFFFFF 是 int 数据类型可以容纳的最大正数。
然后,AFAIK -1 int 的二进制表示应该是 0x80000001(也许我错了)。为什么/如何在将整数转换为无符号时将此二进制值转换为完全不同的值(0xFFFFFFFF)?或者..怎么可能从0xFFFFFFFF中形成二进制-1?
我毫不怀疑在 C 中:((unsigned int)-1) == 0xFFFFFFFF 或 ((int)0xFFFFFFFF) == -1 与 1 + 1 == 2 一样正确,我只是想知道为什么.
【问题讨论】:
在 Wikipedia 上阅读“Two的补码”;这是用二进制编码负数的最常用方法。 en.wikipedia.org/wiki/Two%27s_complement 您会注意到,就像无符号数一样,将可能的最高数加 1 会得到可能的最低数。 负一的二进制表示必须是加一时产生零的表示。这是0xFFFFFFFF
。
【参考方案1】:
C 和 C++ 可以在许多不同的架构和机器类型上运行。因此,它们可以有不同的数字表示:二进制补码,而二进制补码是最常见的。通常,您不应依赖程序中的特定表示。
对于无符号整数类型(size_t
是其中之一),C 标准(我认为也是 C++ 标准)指定了精确的溢出规则。总之,如果SIZE_MAX
是size_t
类型的最大值,那么表达式
(size_t) (SIZE_MAX + 1)
保证为0
,因此,您可以确定(size_t) -1
等于SIZE_MAX
。其他无符号类型也是如此。
请注意,上述情况成立:
对于所有无符号类型, 即使底层机器不代表二进制补码中的数字。在这种情况下,编译器必须确保恒等式成立。此外,上述内容意味着您不能依赖 signed 类型的特定表示。
编辑:为了回答一些cmets:
假设我们有一个类似 sn-p 的代码:
int i = -1;
long j = i;
对j
的赋值存在类型转换。假设int
和long
具有不同的大小(大多数[全部?] 64 位系统),i
和j
的内存位置的位模式将会不同,因为它们具有不同的大小.编译器确保i
和j
的值 是-1
。
同样,当我们这样做时:
size_t s = (size_t) -1
正在进行类型转换。 -1
的类型为 int
。它有一个位模式,但这与本示例无关,因为当由于强制转换而转换为size_t
时,编译器将根据类型规则转换 value ( size_t
在这种情况下)。因此,即使int
和size_t
的大小不同,标准也保证上面s
中存储的值将是size_t
可以取的最大值。
如果我们这样做:
long j = LONG_MAX;
int i = j;
如果LONG_MAX
大于INT_MAX
,则i
中的值是实现定义的(C89,第 3.2.1.2 节)。
【讨论】:
投票是因为你是第一个注意到(size_t)-1
是因为 C 为无符号数指定的算术规则,而不是因为底层表示。 (顺便说一下,SIZE_MAX
是宏)。
谢谢!我不想使用SIZE_MAX
,因为它不在C89 中,也因为我试图提出一个一般性观点。不过,我想我可以提到它。
标记:“无符号整数应遵守算术模 2**n 的定律,其中 n 是该特定整数大小的值表示中的位数。” [3.9.1/4, C++03]
@Alok,您的编辑更好地回答了我的问题。简单来说,不管二进制数的内部表示是什么;它与 C 及其整数算术规则无关。总之,鉴于负整数有不同的硬件表示,没有办法在 C 中的位级别上操作它们以从它们的位中“生成”负数,这是正确的吗?
关于“大多数(全部?)32 位系统”,我会说“很少有 32 位系统”。通常int
和long
在这些系统上都是32 位的。您会在 16 位系统 (16/32) 或某些 64 位系统 (32/64) 上得到不匹配。【参考方案2】:
这叫做补码。为负数,将所有位取反后加 1。所以要将 1 转换为 -1,将其取反为 0xFFFFFFFE,然后加 1 为 0xFFFFFFFF。
至于为什么会这样,Wikipedia 说:
二进制补码系统的优点是不需要加法和减法电路检查操作数的符号以确定是加还是减。此属性使系统更易于实现,并且能够轻松处理更高精度的算术。
【讨论】:
P.S.我曾经在补码机上工作过。这很奇怪,既有正零又有负零。 我从来没有想过浮点数是负零,但我看你是对的:en.wikipedia.org/wiki/Signed_zero 旧的 Control Data Cyber 系列使用了补码。机器语言很奇怪:标准比较指令将 +0 视为大于 -0,标准相等指令将它们视为不相等,但有一个“它是零吗?”指令本质上对 -0 和 +0 返回 true,对其他任何内容返回 false。幸运的是,我从来不需要处理太多。同样幸运的是,我不必进行低级文本处理,因为它可以将 6 位字符 10 与机器字匹配,并且小写由 6 位和 12 位混合表示处理。 即使你在一个人的补码机器上,或者任何其他奇怪的机器上,答案也不会改变。详情请看我的回答。作为一个小问题,它是“一个人的补码”,而不是“一个人的补码”。来自 Knuth:二进制补码是针对 2 的单次幂进行补码的,而一个补码是针对一长串 1 进行补码的。实际上,还有一个“二进制补码表示法”,它具有基数 3 和关于 (2...22)_3 的补码。 @David:恭喜你猜到我第一次学习装配的机器。 @Alok:我不知道标准做出了这样的保证,我从未在任何不使用二进制补码的机器上使用 C 或 C++。我猜它们非常罕见。【参考方案3】:你的第一个问题,关于为什么(unsigned)-1
给出最大可能的无符号值只是偶然地与二进制补码相关。将 -1 强制转换为无符号类型的原因是因为标准规定无符号类型“遵循算术模 2n 的规律,其中 n 是该特定整数大小的值表示。"
现在,对于 2 的补码,最大可能的无符号值和 -1 的表示恰好是相同的 - 但即使硬件使用另一种表示(例如 1 的补码或符号/大小),将 -1 转换为无符号类型仍然必须为该类型产生最大可能的值。
【讨论】:
【参考方案4】:Two's complement 非常适合做减法,就像加法一样:)
11111110(254 或 -2) +00000001 ( 1) --------- 11111111(255 或 -1) 11111111(255 或 -1) +00000001 ( 1) --------- 100000000 ( 0 + 256)【讨论】:
【参考方案5】:即two's complement编码。
主要的好处是,无论您使用的是无符号整数还是有符号整数,都可以获得相同的编码。如果从 0 中减去 1,则整数会简单地环绕。所以1小于0就是0xFFFFFFFF。
【讨论】:
【参考方案6】:因为 int 的位模式 -1 是十六进制无符号的 FFFFFFFF。 1111111111111111111111111111111 二进制无符号。 但在 int 中,第一位表示它是否为负。 但是在 unsigned int 中,第一位只是额外的数字,因为 unsigned int 不能为负数。因此,额外的位使 unsigned int 能够存储更大的数字。 与无符号整数一样,11111111111111111111111111111111(二进制)或 FFFFFFFF(十六进制)是 uint 可以存储的最大数字。 不建议使用无符号整数,因为如果它们为负数,则会溢出并达到最大数。
【讨论】:
您刚刚重申了 OP 的观察结果,他在问 为什么 int 的位模式是这样的;还有 why (unsigned int)-1 给出0xFFFFFFFF
(你的答案没有清楚地回答)。此外,这是一个有争议的建议,不推荐使用无符号整数。它们非常常用。有符号整数和无符号整数都有缺陷。我的观点是无符号整数比有符号整数有更少的陷阱,所以我更喜欢使用它们,除非我知道需要负值。以上是关于为啥 unsigned int 0xFFFFFFFF 等于 int -1?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 unsigned int 0xFFFFFFFF 等于 int -1?
为啥 C 和 C++ for 循环使用 int 而不是 unsigned int?