代码之家  ›  专栏  ›  技术社区  ›  Nordic Mainframe

如果我们将安全的签名/无符号的比较添加到C/C++中,它会破坏语言还是现有代码?

  •  20
  • Nordic Mainframe  · 技术社区  · 14 年前

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

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

    #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++中使用模板代码时,这是非常糟糕的,因为你需要类型特征魔术来编织一个正确的“& lt”。


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

    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 在这里?

    6 回复  |  直到 10 年前
        1
  •  8
  •   R.. GitHub STOP HELPING ICE    14 年前

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

    if (x-'0' < 10U)
    

    更不用说(平等比较):

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

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

        2
  •  11
  •   Gilles 'SO- stop being evil'    14 年前

    我只回答C。

    C中没有可容纳所有可能整数类型的所有可能值的类型。离这最近的C99是 intmax_t uintmax_t 它们的交叉点只覆盖它们各自范围的一半。

    因此,不能实现数学值比较,例如 x <= y 通过第一次转换 x y 然后执行一个简单的操作。这与操作员工作原理的一般原则有很大的不同。它还打破了操作员与普通硬件中倾向于单个指令的事物相对应的直觉。

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

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

    如果你想要lisp/haskell/python/$favorite_-language_和内置的_-bignums_,你知道在哪里找到它…

        3
  •  7
  •   Suma    14 年前

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

    在C和C++中,有很多代码比你和我一起想象的(有些甚至可以被恐怖分子写)。

    依靠“主张 (short)-1 > (unsigned)1 “可能是某人无意中做的。有很多C代码处理复杂的位操作和类似的事情。有些程序员很有可能在这种代码中使用当前的比较行为。(其他人已经提供了这些代码的好例子,而且代码比我预期的要简单)。

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

        4
  •  1
  •   Chris Becke    14 年前

    我认为C++就像罗马帝国一样。它太大了,太老套了,无法修复将要摧毁它的东西。

    C++0X和Boost是一个可怕的可怕语法的例子——这种类型的婴儿只有父母能爱——而且与10年前的简单优雅(但严重受限)C++有很长的距离。

    重点是,当一个人已经“固定”了一些非常简单的东西作为比较类型的积分,足够的遗产和现有的C++代码已经被打破,人们不妨称之为一种新的语言。

    一旦破裂,还有很多其他的问题也可以追溯修复。

        5
  •  0
  •   supercat    11 年前

    一种语言定义规则的唯一方法是,在使用不同C语言类型的操作数组合时,使编译器至少在某些上下文中禁止隐式类型转换(将“惊喜”转换为“为什么不编译?”并且使其不太可能在路上引起意外的错误),为每个存储格式定义多个类型(例如每个整数类型的包装和非包装变量),或者两者都定义。

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

    不幸的是,除了让编译器为混合操作数比较生成警告之外,我真的看不到用C语言可以做多少事情,因为它的存在不需要像上面所描述的那样向它添加新类型;尽管我认为添加这些新类型是一种改进,但我不会屏息以待。

        6
  •  0
  •   gnasher729    10 年前

    如果整数类型之间的比较比较比较了实际的数学值,我希望整数和浮点之间的比较也一样。比较任意64位整数和任意双精度浮点数的精确值是非常困难的。但是编译器可能比我更擅长它。