Какие реализации "C" не реализуют арифметику по модулю c для целых чисел со знаком? - PullRequest
0 голосов
/ 26 апреля 2020

В отношении C11 черновик, раздел 3.4.3 и C11 черновик, раздел H.2.2 , я ищу "C" реализации, которые реализуют поведение, отличное от по модулю arithmeti c для целых чисел со знаком.

В частности, я ищу случаи, когда это поведение по умолчанию, возможно, из-за базовой архитектуры машины.

Вот пример кода и сеанс терминала который иллюстрирует поведение 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

Ответы [ 2 ]

5 голосов
/ 26 апреля 2020

Даже со «знакомым» компилятором, таким как g cc, на «знакомой» платформе, такой как x86, переполнение целых чисел со знаком может делать что-то иное, чем «очевидное» поведение с двойным дополнением.

One Забавным (или, возможно, ужасающим) примером является следующий ( см. на Годболте ):

#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
...

, продолжающийся бесконечно. Арифметика c является двойным дополнением, все в порядке, но, похоже, что-то пошло не так при сравнении в условии l oop.

На самом деле, если вы посмотрите на вывод сборки, вы Вы увидите, что g cc полностью пропустил сравнение и сделал l oop безусловно бесконечным. Он может сделать вывод, что если бы не было переполнения, то l oop никогда не мог бы завершиться, и, поскольку переполнение со знаком является неопределенным поведением, в этом случае также свободно разрешать l oop. Поэтому самый простой и «самый эффективный» юридический кодекс заключается в том, чтобы никогда не завершаться вообще, поскольку это позволяет избежать «ненужного» сравнения и условного перехода.

Вы можете считать это либо клевым, либо извращенным, в зависимости от вашей точки зрения. .

(Для дополнительного кредита: посмотрите, что icc -O2 делает и попытайтесь объяснить это.)

0 голосов
/ 02 мая 2020

На многих платформах требование, чтобы компилятор выполнял точное усечение целого размера, приводило бы к тому, что многие конструкции выполнялись менее эффективно, чем это было бы возможно, если бы им было позволено использовать более свободную семантику усечения. Например, учитывая int muldiv(int x, ind y) { return x*y/60; }, компилятор, которому было разрешено использовать свободную целочисленную семантику, мог бы заменить muldiv(x,240); на x<<2, но тот, который требовался для использования точной семантики, должен был бы фактически выполнять умножение и деление. Такая оптимизация полезна и, как правило, не создаст проблем, если операторы приведения используются в случаях, когда программам требуется арифметика с уменьшенным числом модов c, а компиляторы обрабатывают приведение к определенному размеру, что подразумевает усечение до этого размера.

Даже при использовании значений без знака присутствие приведения в (uint32_t)(uint32a-uint32b) > uint32c сделает намерение программиста более ясным, и будет необходимо гарантировать, что код будет работать так же в системах с 64-битной int и в системах с 32 -bit int, поэтому, если кто-то захочет проверить на целочисленный перенос, даже на компиляторе, который определит поведение, я бы посчитал (int)(x+someUnsignedChar) < x превосходящим `x + someUnsignedChar

Большая проблема заключается в том, что некоторые компиляторы склонны генерировать код, который ведет себя бессмысленно в случае целочисленного переполнения. Даже такая конструкция, как unsigned mul_mod_65536(unsigned short x, unsigned short y) { return (x*y) & 0xFFFFu; }, которую авторы Стандарта ожидали, что обычные реализации будут обрабатывать как неотличимые от математики без знака, иногда заставит g cc генерировать бессмысленный код в случаях, когда x будет превышать INT_MAX/y.

...