20

在阅读了这个关于有符号/无符号比较的问题之后(我会说他们每隔几天出现一次):

我想知道为什么我们没有正确的有符号无符号比较,而是这个可怕的混乱?从这个小程序中获取输出:

#include <stdio.h>
#define C(T1,T2)\
 {signed   T1 a=-1;\
 unsigned T2 b=1;\
  printf("(signed %5s)%d < (unsigned %5s)%d = %d\n",#T1,(int)a,#T2,(int)b,(a<b));}\

 #define C1(T) printf("%s:%d\n",#T,(int)sizeof(T)); C(T,char);C(T,short);C(T,int);C(T,long);
int main()
{
 C1(char); C1(short); C1(int); C1(long); 
}

用我的标准编译器(gcc,64位)编译,我得到这个:

char:1
(signed  char)-1 < (unsigned  char)1 = 1
(signed  char)-1 < (unsigned short)1 = 1
(signed  char)-1 < (unsigned   int)1 = 0
(signed  char)-1 < (unsigned  long)1 = 0
short:2
(signed short)-1 < (unsigned  char)1 = 1
(signed short)-1 < (unsigned short)1 = 1
(signed short)-1 < (unsigned   int)1 = 0
(signed short)-1 < (unsigned  long)1 = 0
int:4
(signed   int)-1 < (unsigned  char)1 = 1
(signed   int)-1 < (unsigned short)1 = 1
(signed   int)-1 < (unsigned   int)1 = 0
(signed   int)-1 < (unsigned  long)1 = 0
long:8
(signed  long)-1 < (unsigned  char)1 = 1
(signed  long)-1 < (unsigned short)1 = 1
(signed  long)-1 < (unsigned   int)1 = 1
(signed  long)-1 < (unsigned  long)1 = 0

如果我编译为 32 位,结果是一样的,除了:

long:4
(signed  long)-1 < (unsigned   int)1 = 0

“怎么样?” 所有这些都很容易找到:只需转到 C99 标准的第 6.3 节或 C++ 的第 4 章,然后挖掘描述操作数如何转换为通用类型的子句,如果通用类型重新解释负值,这可能会中断。

但是“为什么?”呢?正如我们所见,'<' 在 50% 的情况下会失败,它还取决于类型的具体大小,因此它取决于平台。以下是需要考虑的几点:

  • 转换和比较过程并不是最小惊喜规则的主要示例

  • 我不相信那里有代码,它依赖于不是由恐怖分子编写的(short)-1 > (unsigned)1命题。

  • 当你在 C++ 中使用模板代码时,这一切都很糟糕,因为你需要类型特征魔法来编织一个正确的“<”。


毕竟,比较不同类型的有符号和无符号值容易实现:

signed X < unsigned Y -> (a<(X)0) || ((Z)a<(Z)b) where Z=X|Y 

如果可以静态证明 a>=0,则预检查很便宜,也可以由编译器优化掉。

所以这是我的问题:

如果我们添加安全的有符号/无符号比较 C/C++,它会破坏语言或现有代码吗?

(“它会破坏语言吗”意味着我们是否需要对语言的不同部分进行大量更改以适应这种变化)


更新: 我已经在我的旧 Turbo-C++ 3.0 上运行了这个并得到了这个输出:

char:1
(signed  char)-1 < (unsigned  char)1 = 0

为什么在(signed char)-1 < (unsigned char) == 0这里?

4

6 回答 6

12

我的答案仅适用于 C。

C 中没有类型可以容纳所有可能的整数类型的所有可能值。最接近的 C99 是intmax_tand uintmax_t,它们的交集只覆盖了它们各自范围的一半。

因此,您不能通过x <= y先将xand转换y为通用类型然后进行简单操作来实现诸如此类的数学值比较。这与操作员如何工作的一般原则大相径庭。它也打破了操作符对应于普通硬件中往往是单个指令的事物的直觉。

即使您将这种额外的复杂性添加到语言(以及实现编写者的额外负担),它也不会具有非常好的属性。例如,x <= y仍然不等于x - y <= 0. 如果你想要所有这些好的属性,你必须让任意大小的整数成为语言的一部分。

我敢肯定那里有很多旧的 unix 代码,可能有一些在你的机器上运行,假设(int)-1 > (unsigned)1. (好吧,也许它是由自由战士写的;-)

如果你想要 lisp/haskell/python/$favorite_language_with_bignums_built_in,你知道在哪里可以找到它......

于 2010-08-13T12:28:37.170 回答
8

是的,它会破坏语言/现有代码。正如您所指出的,该语言仔细指定了有符号和无符号操作数一起使用时的行为。比较运算符的这种行为对于一些重要的习语来说是必不可少的,例如:

if (x-'0' < 10U)

更不用说(平等比较)之类的事情了:

size_t l = mbrtowc(&wc, s, n, &state);
if (l==-1) ... /* Note that mbrtowc returns (size_t)-1 on failure */

顺便说一句,为混合有符号/无符号比较指定“自然”行为也会导致显着的性能损失,即使在目前以安全方式使用此类比较的程序中,由于输入限制,它们已经具有“自然”行为编译器将很难确定(或可能根本无法确定)。在编写自己的代码来处理这些测试时,我相信你已经看到了性能损失的样子,而且它并不漂亮。

于 2010-08-13T12:21:36.640 回答
7

我不认为它会破坏语言,但是是的,它可能会破坏一些现有的代码(并且在编译器级别可能很难检测到破坏)。

用 C 和 C++ 编写的代码比你我所能想象的要多得多(其中一些甚至可能是由恐怖分子编写的)。

依靠“命题(short)-1 > (unsigned)1”可能是某人无意中做出的。存在许多处理复杂位操作和类似事情的 C 代码。某些程序员很可能会在此类代码中使用当前的比较行为。(其他人已经提供了此类代码的不错示例,并且代码比我预期的还要简单)。

当前的解决方案是对此类比较发出警告,并将解决方案留给程序员,我认为这在精神上是 C 和 C++ 的工作方式。此外,在编译器级别解决它会导致性能损失,这是 C 和 C++ 程序员非常敏感的事情。两个测试而不是一个测试对您来说似乎是一个小问题,但可能有很多 C 代码会成为一个问题。例如,可以通过对公共数据类型使用显式强制转换来强制先前的行为来解决它——但这又需要程序员注意,因此它并不比一个简单的警告更好。

于 2010-08-13T12:11:12.597 回答
1

我认为 C++ 就像罗马帝国。它很大,而且过于成熟,无法修复将要摧毁它的东西。

c++0x - 和 boost - 是一种可怕的可怕语法的例子 - 只有它的父母才能爱的那种婴儿 - 与 10 年前简单优雅(但受到严重限制)的 c++ 相去甚远。

关键是,当人们“修复”一些像比较整数类型这样非常简单的事情时,已经有足够多的遗留和现有 c++ 代码被破坏,人们不妨称之为一种新语言。

一旦损坏,还有很多其他东西也有资格进行追溯修复。

于 2010-08-13T13:01:16.150 回答
0

当使用不同 C 语言类型的组合操作数时,一种语言定义可以接近于在运行时维护最小意外原则的规则的唯一方法是让编译器至少在某些上下文中禁止隐式类型转换(将“惊喜”转变为“为什么不编译?”并使其不太可能导致意外错误),为每种存储格式定义多种类型(例如,每种整数类型的包装和非包装变体), 或两者。

每种存储格式都有多种类型,例如有符号和无符号 16 位整数的包装和非包装版本,可以让编译器区分“我在这里使用 16 位值,以防它使事情更高效,但它永远不会超过 0-65535 的范围,如果它发生了我也不在乎)”和“我使用的 16 位值需要包装到 65535 它变为负数”。在后一种情况下,为这样的值使用 32 位寄存器的编译器必须在每次算术运算后屏蔽它,但在前一种情况下,编译器可以忽略它。关于您的特定愿望,非包装带符号长和非包装之间的比较的含义unsigned long 会很清楚,并且编译器生成实现它所需的多指令序列是合适的(因为将负数转换为非包装unsigned long将是未定义的行为,让编译器为这些类型的比较运算符不会与可能指定的任何其他内容冲突)。

不幸的是,除了让编译器为混合操作数比较生成警告之外,我真的没有看到 C 语言可以做很多事情,因为它存在而不像上面描述的那样添加新类型。虽然我认为增加这些新类型是一种改进,但我不会屏住呼吸。

于 2013-09-05T20:53:51.263 回答
0

如果整数类型之间的比较比较了实际的数学值,我希望整数和浮点之间的比较也发生同样的情况。比较任意 64 位整数和任意双精度浮点数的精确值是相当困难的。但是编译器可能会比我更擅长。

于 2014-06-30T09:27:29.573 回答