在阅读这一问题上的签署/unsigned比较(他们来了,每隔几天我就会说):

我不知道为什么我们没有适当签署的未签名的比较,而不是这可怕的乱七八糟的?采取的输出,从这个小型的程序:

#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); 
}

编我的标准编译器(海湾合作委员会,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

该"怎么样?"的这一切是很容易找到:刚转到第6.3节的C99标准或第4章C++和挖的条款描述了如何操作数都转换为一个共同的类型,这可以打破,如果常见的类型重新诠释负值。

但是什么有关"为什么?"。正如我们能看见,'<'失败在50%的所有情况下,也取决于具体大小的类型,所以它是平台的依赖。这里有几点需要考虑:

  • 的转换和比较过程并不是一个主要例子规则的少惊喜

  • 我不相信有代码,其依赖于这样的建议, (short)-1 > (unsigned)1 和是 写的恐怖分子。

  • 这都是可怕的,当你在C++与模板代码,因为你需要的类型特征的魔法来编织一个正确的"<".


毕竟,比较签署和未签名的价值的不同类型 易于实施:

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

预先检查价格便宜,还可以优化远编译器,如果一个>=0能够静态地被证实。

因此,这里是我的问题:

将它打破语言或现有的编码,如果我们增加安全签署/unsigned比较C/C++?

("将它打破语言"是指将我们需要作出巨大的变化对不同部分的语言,以适应这种变化)


更新: 我跑了这个我的好老涡轮C++3.0,并得到了这个输出:

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

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

有帮助吗?

解决方案

是的,它将破坏语言/现有代码。正如您已经指出的那样,该语言仔细指定了签名时的行为,并且使用未签名的操作数被一起使用。这种与比较操作员的行为对于某些重要的成语至关重要,例如:

if (x-'0' < 10U)

更不用说(平等比较)之类的东西:

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

顺便说一句,为混合签名/未签名的比较指定“自然”行为也会引起重大的绩效罚款,即使在目前以安全方式使用此类比较的程序中,由于输入的限制,他们已经具有“自然”行为编译器将很难确定(或根本无法确定)。在编写自己的代码来处理这些测试时,我敢肯定,您已经看到了性能罚款的外观,而且不漂亮。

其他提示

我的回答是C只。

没有在C类型,可以容纳所有可能的价值观的所有可能的整类型。最近的C99来到这 intmax_tuintmax_t, 和他们的交叉路口只占一半,它们各自范围内。

因此,无法实现一个数学价值的对比例如 x <= y 通过第一个转换 xy 一个常见类型,然后做一个简单的操作。这是一个重大背离了一般原则如何经营者的工作。它还断的直觉,即经营者对应的事情往往是单个指令在共同的硬件。

甚至如果你加入这个更加复杂的语言(和额外的负担向执行作家),它也不会非常好的性质。例如, x <= y 会仍然不能等同 x - y <= 0.如果你想要所有的这些漂亮的性质,你有做出任意大整数语言的一部分。

我确定那里有充足的老unix的代码,可能还有一些在您的机器上运行的,即假定 (int)-1 > (unsigned)1.(Ok,也许,它是由自由战士;-)

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

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

在C ++中编写的代码比您和我在一起可以想象的要多得多(其中一些甚至可能由恐怖分子写)。

依靠“命题 (short)-1 > (unsigned)1“某人可能会无意地完成。存在很多C代码,用于处理复杂的位操纵和类似的事情。很可能有些程序员可能正在使用此类代码中的当前比较行为(其他人已经提供了很好的示例这样的代码和A代码甚至比我预期的要简单)。

当前的解决方案是要警告这种比较,并将解决方案留给程序员,我认为这是C和C ++的工作方式。同样,在编译器级别上解决它将引起性能罚款,这是C和C ++程序员非常敏感的事情。对您来说,有两个测试而不是一个测试似乎是一个小问题,但是可能有很多C代码,这将是一个问题。可以通过使用明确的铸件来强迫先前的行为来解决它,但这再次需要程序员注意,因此,这不比简单的警告更好。

我认为C ++就像罗马帝国。它很大,并且已经建立了,无法修复将要破坏它的东西。

C ++ 0x和Boost-是一种可怕的恐怖语法的例子 - 只有父母才能爱的婴儿 - 与10年前的简单优雅(但受到严格限制)的C ++相距很长的路要走。

关键是,当人们“固定”类型的比较非常简单时,足够的旧版和现有的C ++代码已经被打破了,也可以将其称为新语言。

一旦破碎,还有很多其他也有资格进行追溯固定。

使用语言定义规则的唯一方法可以在运行时间内保持最小惊喜的原则,而使用不同的C语言类型的操作数将是使编译器禁止在至少某些情况下进行编译器隐式类型的转换(将“惊喜”转换为“为什么不编译?”,并且不太可能引起意外的错误,为每种存储格式定义多种类型(例如,包装和非包装变体的每种整数类型的变体), 或两者。

为每种存储格式具有多种类型,例如,包装版和非包装版本的签名和未签名的16位整数,可以使编译器区分“我在这里使用16位值,以防万一,以防万一它使事情更有效,但它永远不会超过0-65535的范围 而且我不在乎发生了什么事)“和“我使用的是16位值,需要包装至65535。它会变负”。在后一种情况下,使用32位寄存器的编译器为此值,每次算术都必须掩盖它操作,但在前一种情况下,编译器可以忽略这一点。关于您的特定愿望 非包裹 未签名的长期很清楚,并且编译器生成使其实现所必需的多指导顺序(因为将负数转换为无包裹)是合适的 unsigned long 将是不确定的行为,让编译器为这些类型的比较操作员定义行为不会与可能指定的其他任何内容相抵触)。

不幸的是,除了让编译器生成有关混合操作和比较的警告外,我真的看不到C语言可以做的太多,因为它存在,而没有如上所述添加新类型。尽管我将这种新类型的添加视为一种改进,但我不会屏住呼吸。

如果整数类型之间的比较比较了实际的数学值,我希望与整数和浮点点之间的比较发生同样的情况。并且比较任意64位整数的确切值和任意双重精确的浮点数非常困难。但是,编译器可能会比我更好。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top