哪个 "C "实现没有实现有符号整数的模数运算?

问题描述 投票:-1回答:1

关于 C11草案,第3.4.3节。C11草案,H.2.2节。我在寻找那些对有符号整数实现了模数运算以外的行为的 "C "实现。

具体来说,我正在寻找可能由于底层机器架构的原因而使其成为默认行为的实例。

这里是一个代码示例和终端会话,说明了有符号整数的模数运算行为。

overflow.c:

#include <stdio.h>
#include <limits.h>

int main(int argc, char *argv[])
{
    int a, b;
    printf ( "INT_MAX = %d\n", INT_MAX );
    if ( argc == 2 && sscanf(argv[1], "%d,%d", &a, &b) == 2 ) {
        int c = a + b;
        printf ( "%d + %d = %d\n", a, b, c );
    }
    return 0;
}

终端会话。

$ ./overflow 2000000000,2000000000
INT_MAX = 2147483647
2000000000 + 2000000000 = -294967296
c undefined-behavior modulo integer-overflow
1个回答
5
投票

即使使用像gcc这样 "熟悉 "的编译器,在像x86这样 "熟悉 "的平台上,有符号整数溢出也可以做一些 "明显的 "双补包围行为以外的事情。

一个有趣的(或者可能是可怕的)例子是下面的(看在眼里):

#include <stdio.h>

int main(void) {
    for (int i = 0; i >= 0; i += 1000000000) {
        printf("%d\n", i);
    }
    printf("done\n");
    return 0;
}

天真地讲,你会期望这个输出

0
1000000000
2000000000
done

而随着 gcc -O0 你是对的。 但对于 gcc -O2 你得到

0
1000000000
2000000000
-1294967296
-294967296
705032704
...

继续无限期地进行。 算术是双补包围,没错,但循环条件中的比较似乎出了问题。

事实上,如果你看一下汇编输出,你会发现gcc完全省略了比较,并使循环无条件地无限延长。 它能够推断出,如果没有溢出,循环永远不可能终止,由于符号整数溢出是未定义的行为,所以在这种情况下,它也可以自由地让循环不终止。 因此,最简单也是 "最有效 "的合法代码就是永远不终止,因为这样可以避免 "不必要的 "比较和条件跳转。

你可能会认为这要么很酷,要么很反常,这取决于你的观点。

(为了加分。看看 icc -O2 是否 并试图解释它)。)


0
投票

在许多平台上,要求编译器执行精确的整数大小截断会导致许多构造物的运行效率低于允许它们使用更宽松的截断语义的情况。 例如,给定 int muldiv(int x, ind y) { return x*y/60; }的编译器,允许使用松散的整数语义的编译器可以取代 muldiv(x,240);x<<2但如果需要使用精确的语义,则需要实际执行乘除。 这样的优化是有用的,如果在程序需要模减算术的情况下使用投值运算符,并且编译器将一个投值处理成特定大小意味着截断到那个大小,那么这样的优化一般不会造成问题。

即使在使用无符号值的情况下,在 (uint32_t)(uint32a-uint32b) > uint32c 将使程序员的意图更加明确,并且将是必要的,以确保代码在64位的系统上的操作是一样的。int 和32位的产品一样 int因此,如果要测试整数包围,即使是在编译器上定义行为,我也会将它视为 (int)(x+someUnsignedChar) < x 优于`x+someUnsignedChar < x,因为这样做会让人类读者知道代码故意把值当作正常数学整数以外的东西。

最大的问题是,一些编译器很容易生成在整数溢出时表现为无意义的代码。 甚至像 unsigned mul_mod_65536(unsigned short x, unsigned short y) { return (x*y) & 0xFFFFu; } 标准的作者希望普通的实现能够以一种与无符号数学无法区分的方式来处理,这有时会导致gcc在以下情况下生成无意义的代码,即 x 将超过 INT_MAX/y.

© www.soinside.com 2019 - 2024. All rights reserved.