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

Nor*_*ame 20 c c++ comparison unsigned signed

在阅读了有关签名/未签名比较的问题之后(我每隔几天就会说出来):

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

#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); 
}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

如果我编译为32位,结果是相同的,除了:

long:4
(signed  long)-1 < (unsigned   int)1 = 0
Run Code Online (Sandbox Code Playgroud)

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

但是"为什么?"呢?正如我们所看到的,'<'在50%的情况下失败,也取决于类型的具体大小,因此它取决于平台.以下是需要考虑的一些要点:

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

  • 我不相信有代码在那里,这依赖于这一命题(short)-1 > (unsigned)1,并没有被恐怖分子编写.

  • 当你在C++中使用模板代码时,这一切都很糟糕,因为你需要使用type trait magic来编写正确的"<".


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

signed X < unsigned Y -> (a<(X)0) || ((Z)a<(Z)b) where Z=X|Y 
Run Code Online (Sandbox Code Playgroud)

预检是便宜的,如果可以静态证明> = 0,编译器也可以对其进行优化.

所以这是我的问题:

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

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


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

char:1
(signed  char)-1 < (unsigned  char)1 = 0
Run Code Online (Sandbox Code Playgroud)

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

Gil*_*il' 11

我的答案仅适用于C.

C中没有类型可以容纳所有可能的整数类型的所有可能值.最接近的C99是intmax_tuintmax_t,它们的交叉点只覆盖它们各自范围的一半.

因此,你无法实现如数学值比较x <= y,首先转换xy为通用类型,然后做一个简单的操作.这与运营商如何运作的一般原则背道而驰.它还打破了操作员对应于普通硬件中往往是单指令的事物的直觉.

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

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

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

  • *"就数学而言,问题在于天真的编码者期望<是一个数学顺序关系,而他们所处的环不是整数而是整数mod 2 ^ n."* - 我们是只要我们保持在无符号数字内,只需使用mod 2 ^ n.int*具有*此订单关系(禁止UB).我要问的是,当涉及**混合**符号算术时,如果确实需要完全不一致的顺序(这取决于操作数的大小). (2认同)

R..*_*R.. 8

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

if (x-'0' < 10U)
Run Code Online (Sandbox Code Playgroud)

更不用说(等式比较):

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

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


Sum*_*uma 7

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

用C和C++编写的代码比你和我一起想象的要多得多(有些甚至可能是恐怖分子编写的).

依靠"命题(short)-1 > (unsigned)1"可能是某人无意中完成的.存在许多处理复杂位操作和类似事物的C代码.一些程序员很可能在这样的代码中使用当前的比较行为.(其他人已经提供了很好的代码示例,代码甚至比我预期的更简单).

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

  • 当然有 - 语言不应该无条件地从版本到版本中断.当您使用的库期望一个应用程序期望另一个行为时,您会怎么做?(特别是如果库代码的一部分是标题中的宏或内联函数?)当然有时候会有轻微的破坏,但是永远不应该触及一些基本的东西! (4认同)