为什么C++不强制使用二进制补码?

30
新的C++标准仍然没有规定整数类型的二进制表示方式。这是因为有使用非2的补码算术的C++实现么?我认为这很难相信。还是委员会担心未来硬件的进步会使“位”这个概念过时?同样也很难相信。有人能否阐述一下这个问题?
背景:在评论串(Benjamin Lindley回答这个问题)中,我被piotr的评论和James Kanze的评论两次惊呆了。
第一次是从piotr的评论中得知:
“对于有符号类型进行右移位是未定义的行为”。
第二次是从James Kanze的评论中得知:
“当将值赋给long型变量时,如果该值不能适应long型变量,则结果是实现定义的”。
在我查看标准之前,我不得不查明它们的意思。它们存在的唯一原因是为了适应非2的补码整数表示方式。为什么?

9
你为什么要编写依赖于使用二进制补码表示的有符号值的代码,这才是更好的问题。 - yan
14
因为我知道它将在一个二进制补码处理器上运行。别傻了。 - TonyK
8
高级语言的好处在于不需要关心具体的实现细节。数字就是数字,它的实现方式并不重要。查看任何高级语言规范,都没有对实现细节做出任何要求。 - user395760
4
@delnan: 不,有很多问题需要位操作。你是否编写过国际象棋程序?或者任意精度浮点库?如果无法依赖处理器使用二进制补码算术,这两个都会更难编写。它们也会慢得多。 - TonyK
3
@delnan说的完全不正确。无符号整数类型的表示受到许多要求的限制,唯一的实现自由是添加填充位。有符号类型的表示略微少些,但仍然只允许3种表示方式(二进制补码、反码和原码)。如果真的没有人对内部工作有任何要求,那么为什么会有这个列表呢? - Steve Jessop
显示剩余25条评论
4个回答

28
(编辑:C++20现在强制要求使用2的补码表示法,注意有符号算术溢出仍然是未定义的,并且移位在某些情况下仍具有未定义和实现定义的行为。) 主要问题在于定义不存在的事物时,编译器是建立在假定此操作是未定义的基础上的。改变标准不会改变编译器,而找出假设所做的地方则是一项困难的任务。
即使在2的补码机器上,你可能拥有比你想象的更多选择。两个例子:有些计算机没有保持符号的右移操作,只提供插入零的右移操作;DSP中常用的特性是饱和算术,在那里分配一个超过范围的值将被剪辑到最大值,而不仅仅是删除高阶位。

2
+1,解释了OP的说法“它们的唯一原因是为了适应非2的补码”,这是错误的,这可能是这个问题最接近明确答案的地方,即使只是作为补充背景 :-) - Steve Jessop
1
这是对我的问题最有帮助的回答!但是还有一个问题:是否有适用于这些架构的*C ++*实现?并且是否可能会有这样的实现? - TonyK
1
@TonyK:不要忘记,即使在今天,尽管它没有采用所有的C99特性,C++0x的目标之一是与C有一定程度的“兼容性”。当然,对于某些具有饱和算术的DSP,可能存在C实现,而且考虑到GCC或LLVM的灵活性,也肯定可以有C++实现而不需要太多麻烦。因此,超出范围赋值是实现定义的事实确实具有实际后果,我认为这很重要。 - Steve Jessop
针对什么样的架构?非2的补码?--我不这么认为,据我所知,唯一还在使用的是由Unisys构建的,具有C编译器但没有C++编译器。饱和算术?我不会感到惊讶,如果它存在于常见架构的向量扩展中。 - AProgrammer
1
关于像 long 转换为 short 这样的缩小转换:这从来不是未定义行为,但当目标是有符号数时,它是实现定义的,因此您所举的饱和转换示例是可能的(对于定义该行为始终发生而不是截断的实现)。 从超出范围的 floatintunsigned 的转换是未定义行为,但是整数类型之间的转换保证不会是 UB。 - Peter Cordes
显示剩余2条评论

5
我认为这是因为标准在 3.9.1[basic.fundamental]/7 中说道:

该国际标准允许整数类型使用2的补码、1的补码和有符号的幅度表示法。

我愿意打赌,这来自C编程语言,在 6.2.6.2/2 中列出了 符号和幅值二进制补码一的补码 作为唯一允许的表示方法。当C广泛传播时,确实存在1的补码系统:UNIVAC是最常被提及的。

1
因为根本没有理由这样做。 - Michael Foukarakis
1
@delnan - 禁止特定种类的硬件有什么好处?如果您只想编写适用于2的补码的代码,那么您可以自由地这样做。为什么要禁止其他人做其他选择呢? - Bo Persson
@Bo @Michael:那是针对OP的回答,不是针对我 ;) - user395760
3
@Bo:标准确实禁止了特定类型的硬件,或者更确切地说,它要求它们的 C++ 实现的行为像其他类型的硬件一样,即使这样做效率低下。如果我想在我的 C++ 实现中使用 8 位 excess-127 字节,则我会很不幸,因为标准禁止了这种情况。 - Steve Jessop
1
@Sebastian 对,我知道,但我回答的问题是“标准从[不利于不同硬件的事情,如使2s补码行为成为标准]中获得了什么好处?”而C语言,特别是老派和可移植的C语言,就是一个很好的例子,没有这些好处。至于C++的可预测性...这实际上取决于你正在比较什么,以及我们要预测什么事情,但在本评论部分讨论这些问题似乎超出了范围。 - mtraceur
显示剩余2条评论

4

在我看来,即使是今天,在编写一个广泛适用的C++库时,不能假设使用2's complement。C++被广泛使用,不应该做出这样的假设。

然而,大多数人并不编写此类库,因此如果您想依赖于2's complement,则应该继续前进。


但正如AProgrammer所指出的那样,即使是二进制补码机器也不能保证TonyK所询问的行为。 赋值超出范围的值可能会饱和而不是丢弃高位,并且有符号右移可能会向零舍入(将移出的最高位加回去),而不是向下舍入(丢弃移出的位)。 这些行为在二进制补码硬件上是完全可能的。 - Ben Voigt

1
许多语言标准的方面都是因为标准委员会非常不愿意禁止编译器以现有代码可能依赖的方式运行。如果存在依赖于补码行为的代码,则要求编译器表现出底层硬件使用二进制补码的行为将使旧代码无法在新编译器中运行。
解决方案,标准委员会遗憾地尚未看到实施,将允许代码以独立于机器字长或硬件特性的方式指定所需的语义。如果支持依赖于一补数行为的代码被认为很重要,则设计一种方法,使代码可以明确要求一补数行为,而不管底层硬件平台如何。如果需要,为了避免过于复杂化每个编译器,规定标准的某些方面是可选的,但符合规范的编译器必须记录它们支持哪些方面。这样的设计将允许一补数机器的编译器根据程序的需要同时支持二进制补码行为和一补数行为。此外,它将使得可以将代码移植到包括一补数支持的二进制补码机器上的编译器中。

我不确定标准委员会为什么到目前为止还没有允许任何方式通过代码独立于底层架构和字长指定行为(这样代码就不会使一些机器在比较时使用有符号语义,而其他机器则使用无符号语义),但出于某种原因他们尚未这样做。对于补码表示的支持只是其中的一部分。


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