Повышение в целочисленных арифметических выражениях

У меня два связанных вопроса:

  1. Что говорит стандарт и что делают разные компиляторы, когда речь идет о сравнении арифметического выражения вида x * y == z (или x + y == z), где x * y слишком велико для любого x или y, чтобы удерживать, но не больше, чем z.

  2. Как насчет сравнения между знаковыми и беззнаковыми целыми числами одинаковой ширины с одним и тем же базовым двоичным значением?

Пример ниже может пояснить, что я имею в виду

#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main (void)
{
    uint8_t x = 250;
    uint8_t y = 5;
    uint16_t z = x*y;
    uint8_t w = x*y;

    if (x * y == z)            // true
        puts ("x*y = z");
    if ((uint16_t)x * y == z)  // true
        puts ("(uint16_t)x*y = z");
    if (x * y == (uint8_t)z)   // false
        puts ("x*y = (uint8_t)z");
    if (x * y == w)            // false
        puts ("x*y = w");
    if ((uint8_t)(x * y) == w) // true
        puts ("(uint8_t)x*y = w");
    if (x * y == (uint16_t)w)  // false
        puts ("x*y = (uint16_t)w");

    int8_t X = x;
    if (x == X)                // false
        puts ("x = X");
    if (x == (uint8_t)X)       // true
        puts ("x = (uint8_t)X");
    if ((int8_t)x == X)        // true
        puts ("(int8_t)x = X");
    if (memcmp (&x, &X, 1) == 0) // true
        puts ("memcmp: x = X");
}

Первая часть меня не удивляет: как объяснено в Какие переменные следует приводить к типу при выполнении математических операций в C/C++? компилятор неявно переводит более короткие целые числа в более длинные во время арифметических операций (и я полагаю, что это относится к операторам сравнения). Это гарантированное стандартное поведение?

Но ответ на этот вопрос, а также ответ на сравнения со знаком/без знака говорят, что целые числа со знаком следует повысить до неподписанного. Я ожидал, что x == X выше будет правдой, поскольку они содержат одни и те же данные (см. memcmp). Вместо этого, похоже, происходит то, что оба переводятся в более широкие целые числа, а затем происходит преобразование знакового в беззнаковое (или наоборот).

РЕДАКТИРОВАТЬ 2:

В частности, меня интересуют случаи, когда, скажем, функция возвращает int, которая будет равна -1 в случае ошибки, в противном случае будет представлять, например, число записанных байтов, которое всегда должно быть положительным. Стандартные функции этого типа возвращают ssize_t, что, если я не ошибаюсь, на большинстве платформ такое же, как int64_t, но количество записанных байтов может достигать UINT64_MAX. Итак, если я хочу сравнить возвращенное int или ssize_t с беззнаковым значением для ожидаемых записанных байтов, необходимо явное приведение к unsigned int или size_t (если я не ошибаюсь, такой же ширины, как ssize_t, но без знака)?


РЕДАКТИРОВАТЬ 1:

Я не могу понять следующее:

#include <stdio.h>
#include <stdint.h>

int main (void)
{
    int8_t ssi = UINT8_MAX;
    uint8_t ssu = ssi;
    printf ("ssi = %hhd\n", ssi); // -1
    printf ("ssu = %hhu\n", ssu); // 255
    if (ssi == ssu) // false
        puts ("ssi == ssu");

    puts ("");
    int16_t si = UINT16_MAX;
    uint16_t su = si;
    printf ("si = %hd\n", si); // -1
    printf ("su = %hu\n", su); // 65535
    if (si == su) // false
        puts ("si == su");

    puts ("");
    int32_t i = UINT32_MAX;
    uint32_t u = i;
    printf ("i = %d\n", i); // -1
    printf ("u = %u\n", u); // 4294967295
    if (i == u)   // true????
        puts ("i == u");

    puts ("");
    int64_t li = UINT64_MAX;
    uint64_t lu = li;
    printf ("li = %ld\n", li); // -1
    printf ("lu = %lu\n", lu); // 18446744073709551615
    if (li == lu) // true
        puts ("li == lu");
}

В то время как 64-битный пример можно объяснить тем, что нет более широкого целого числа для перехода, 32-битный пример противоречит здравому смыслу. Разве это не должно быть таким же, как 8- и 16-битные случаи?


person Aayla Secura    schedule 23.11.2017    source источник
comment
Колеса уже отвалились на int8_t X = x;. Вы пытаетесь вставить беззнаковое восьмибитное значение 250 во что-то, что выдает 127. Противоположное (вставление знака в беззнаковое) имеет четкие правила преобразования (добавляйте max-type-val +1, пока он не попадет в диапазон) , а вы делаете наоборот.   -  person WhozCraig    schedule 23.11.2017
comment
Почему он не определен? У меня сложилось впечатление, что 250 будет преобразовано в int8_t, что будет равно -6. печать значения X (со знаком) действительно показывает -6.   -  person Aayla Secura    schedule 23.11.2017
comment
Не неопределенный, но определяемый реализацией.   -  person ad absurdum    schedule 23.11.2017
comment
Итак, ответ на вопрос «Что говорит стандарт?» ничего? И нужно ли использовать явное приведение по обе стороны от сравнения в X == x?   -  person Aayla Secura    schedule 23.11.2017
comment
Я не уверен, где вы ничего не взяли. Стандарту есть что сказать; но этот вопрос может быть слишком широким. В любом случае обратите внимание, что целочисленных промо-акций не избежать. С, например. (uint16_t)x * y, и предполагая 32-битный int, умножение по-прежнему выполняется с int типами, а сравнение в (uint16_t)x * y == z также выполняется с int типами.   -  person ad absurdum    schedule 23.11.2017
comment
Опять же, с int8_t ssi = UINT8_MAX; у вас есть поведение, определяемое реализацией. Смотрите ссылку в моем первом комментарии.   -  person ad absurdum    schedule 23.11.2017
comment
@DavidBowling Я думаю, что это может объяснить редактирование, которое я только что опубликовал. Итак, когда мы сравниваем uint32_t (который имеет тот же размер, что и int) и int32_t (такой же, как int на моей платформе), результат отличается, и требуется явное приведение?   -  person Aayla Secura    schedule 23.11.2017
comment
При сравнении между uint32_t и int32_t знаковый операнд преобразуется в беззнаковый тип, но сначала применяются целочисленные расширения. Если int имеет тот же размер, что и int32_t, оба операнда будут преобразованы в int, если это будет содержать оба значения, иначе в unsigned int, если это необходимо. Вы можете привести к более крупному типу, если хотите.   -  person ad absurdum    schedule 23.11.2017
comment
@DavidBowling Да, это проясняет ситуацию. Я думаю, вы изначально спросили, какова цель приведения, см. мою правку № 2 выше.   -  person Aayla Secura    schedule 23.11.2017
comment
Изменить 2 должен быть новый вопрос. Но ваш код будет выглядеть как ssize_t x = foo(); if ( x < 0 ) { errorhandling } else if ( x == expected_unsigned_value )...   -  person M.M    schedule 23.11.2017


Ответы (1)


Помните, что предыдущий код для comapre, такого как uint8_t x = 250; ... int8_t X = x;, определяется реализацией @David Bowling

  1. сравнение между арифметическим выражением формы x * y == z (или x + y == z), где x * y слишком велико для x или y, но не больше z.

Произведение x * y вычисляется без учета z. Тип продукта определяется 1) как x, так и y проходят через целочисленные рекламные акции до int или unsigned. 2) Если типы различаются, то меньший ранг подвергается обычным арифметическим преобразованиям в больший, после чего вычисляется произведение. Если этот продукт численно переполняет целевой тип, тогда неопределенное поведение в случае подписанных типов и перенос в случае неподписанных типов.

После расчета продукта сравнение происходит по тем же правилам обычных целочисленных рекламных акций, а затем с более высоким рейтингом.

  1. Как насчет сравнения между знаковыми и беззнаковыми целыми числами одинаковой ширины с одним и тем же базовым двоичным значением?

Два аргумента независимо проходят через целочисленные повышения. Если они различаются по знаку, то знаковый преобразуется в соответствующий беззнаковый тип. Затем значения сравниваются.

Любые 2 одинаковых двоичных значения всегда будут сравниваться одинаково. После __целочисленных преобразований_ любые 2 одинаковых двоичных битовых шаблона (не считая редких битов заполнения) одинаковой ширины будут сравниваться одинаково, если подписанный тип является обычным дополнением до 2. .

int8_t i = -1;
uint8_t u = i;         // u =  255
if (i == u) --> false  // both i,x promoted to int and -1 != 255)

int i = -1;
unsigned u = i;        // u = UINT_MAX  
if (i == u) --> true   // i promoted to unsigned and value UINT_MAX 
person chux - Reinstate Monica    schedule 23.11.2017
comment
Присутствуют целочисленные преобразования и обычные арифметические преобразования - person M.M; 23.11.2017
comment
@M.M Да, это звучит правильно. Ответ изменен. Отредактируйте, если вы видите другие недостатки. - person chux - Reinstate Monica; 23.11.2017