Почему результаты целочисленного продвижения отличаются? - PullRequest
15 голосов
/ 10 октября 2011

Пожалуйста, посмотрите на мой тестовый код:

#include <stdlib.h>
#include <stdio.h>


#define PRINT_COMPARE_RESULT(a, b) \
    if (a > b) { \
        printf( #a " > " #b "\n"); \
    } \
    else if (a < b) { \
        printf( #a " < " #b "\n"); \
    } \
    else { \
        printf( #a " = " #b "\n" ); \
    }

int main()
{
    signed   int a = -1;
    unsigned int b = 2;
    signed   short c = -1;
    unsigned short d = 2;

    PRINT_COMPARE_RESULT(a,b);
    PRINT_COMPARE_RESULT(c,d);

    return 0;
}

Результат следующий:

a > b
c < d

Моя платформа - Linux, и моя версия gcc - 4.4.2.Я удивлен второй строкой вывода.Первая строка вывода вызвана целочисленным продвижением.Но почему результат второй строки отличается?

Следующие правила взяты из стандарта C99:

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

В противном случае, если операндимеет целочисленный тип без знака имеет ранг больше или равен рангу типа другого операнда, тогда операнд с целочисленным типом со знаком преобразуется в тип операнда с целочисленным типом без знака.

В противном случае, еслиТип операнда с целочисленным типом со знаком может представлять все значения типа операнда с целым типом без знака, тогда операнд с целочисленным типом без знака преобразуется в тип операнда с целочисленным типом со знаком.

В противном случае оба операнда преобразуются в целочисленный тип без знака, соответствующий типу операнда с целочисленным типом со знаком.

Я думаю, что оба эти сравнения должны принадлежать одному и тому же случаю, второй случайцелочисленного промотированияион.

Ответы [ 3 ]

20 голосов
/ 10 октября 2011

Когда вы используете арифметический оператор, операнды проходят два преобразования.

Целочисленные продвижения: Если int может представлять все значения типа, то операнд переводится в int. Это относится как к short, так и к unsigned short на большинстве платформ. Преобразование, выполненное на этом этапе, выполняется для каждого операнда отдельно, без учета другого операнда. (Есть еще правила, но это то, что применимо.)

Обычные арифметические преобразования: Если вы сравниваете unsigned int с signed int, поскольку ни один не включает весь диапазон другого, и оба имеют одинаковый ранг, то оба преобразуются в unsigned тип. Это преобразование выполняется после проверки типа обоих операндов.

Очевидно, что «обычные арифметические преобразования» не всегда применяются, если нет двух операндов. Вот почему существует два набора правил. Одна ошибка, например, заключается в том, что операторы сдвига << и >> не выполняют обычных арифметических преобразований, поскольку тип результата должен зависеть только от левого операнда (поэтому, если вы видите кого-то типа x << 5U, U означает «ненужный»).

Разбивка: Предположим, что типичная система с 32-битным int и 16-битным коротким.

int a = -1;         // "signed" is implied
unsigned b = 2;     // "int" is implied
if (a < b)
    puts("a < b");  // not printed
else
    puts("a >= b"); // printed
  1. Сначала добавляются два операнда. Так как оба int или unsigned int, рекламные акции не проводятся.
  2. Затем два операнда преобразуются в один и тот же тип. Поскольку int не может представлять все возможные значения unsigned, а unsigned не может представлять все возможные значения int, очевидного выбора нет. В этом случае оба преобразуются в unsigned.
  3. При преобразовании из знака в без знака 2 32 многократно добавляется к знаковому значению до тех пор, пока оно не окажется в диапазоне беззнакового значения. На самом деле это просто шумиха, что касается процессора.
  4. Таким образом, сравнение становится if (4294967295u < 2u), что неверно.

Теперь давайте попробуем это с short:

short c = -1;          // "signed" is implied
unsigned short d = 2;
if (c < d)
    puts("c < d");     // printed
else
    puts("c >= d");    // not printed
  1. Во-первых, два операнда повышаются. Поскольку оба могут быть достоверно представлены int, оба повышаются до int.
  2. Затем они преобразуются в тот же тип. Но они уже одного типа, int, поэтому ничего не делается.
  3. Таким образом, сравнение становится if (-1 < 2), что верно.

Написание хорошего кода: Есть простой способ поймать эти "ошибки" в вашем коде. Просто всегда компилируйте с включенными предупреждениями и исправляйте предупреждения. Я склонен писать такой код:

int x = ...;
unsigned y = ...;
if (x < 0 || (unsigned) x < y)
    ...;

Вы должны следить за тем, чтобы ни один код, который вы пишете, не попадал в другую подписанную и неподписанную информацию: переполнение со знаком. Например, следующий код:

int x = ..., y = ...;
if (x + 100 < y + 100)
    ...;
unsigned a = ..., b = ...;
if (a + 100 < b + 100)
    ...;

Некоторые популярные компиляторы оптимизируют (x + 100 < y + 100) до (x < y), но это история для другого дня. Только не переполняйте свои подписанные номера.

Сноска. Обратите внимание, что хотя signed подразумевается для int, short, long и long long, оно НЕ подразумевается для char. Вместо этого это зависит от платформы.

1 голос
/ 11 октября 2011

Взято из стандарта C ++:

4.5 Интегральные рекламные акции [conv.prom]1 Значение r типа char, char со знаком, char без знака, короткого int или short без знака int может быть преобразовано в значение типа int, если int может представлять все значения исходного типа;в противном случае исходное значение r может быть преобразовано в значение типа unsigned int.

На практике это означает, что все операции (для типов в списке) фактически выполняются для типа intесли он может охватывать весь набор значений, с которым вы имеете дело, в противном случае он выполняется на unsigned int.В первом случае значения сравниваются как unsigned int, потому что одно из них было unsigned int, и поэтому -1 «больше», чем 2. Во втором случае значения сравниваются как целые числа со знаком, так как int охватываетвся область как short, так и unsigned short и т. д. -1 меньше, чем 2.

(История вопроса: на самом деле, все это сложное определение, касающееся всех случаев таким образом, приводит к тому, что компиляторыможет фактически игнорировать фактический тип (!) :) и просто заботиться о размере данных.)

0 голосов
/ 10 августа 2016

Процесс преобразования для C ++ описывается как обычные арифметические преобразования . Тем не менее, я думаю, что наиболее релевантное правило содержится в разделе под ссылками conv.prom: Интегральные рекламные акции 4.6.1 :

Значение типа integer, отличное от bool, char16_t, char32_t или wchar_t, чей целочисленный рейтинг конверсии ([conv.rank]) меньше ранг int может быть преобразован в значение типа int, если int может представлять все значения типа источника; в противном случае источник prvalue может быть преобразовано в prvalue типа unsigned int.

Забавно, что здесь есть слово «can», которое, как мне кажется, предполагает, что это продвижение выполняется по усмотрению компилятора.

Я также нашел этот фрагмент кода C-spec, который намекает на упущение продвижения:

11   EXAMPLE 2       In executing the fragment
              char c1, c2;
              /* ... */
              c1 = c1 + c2;
     the ``integer promotions'' require that the abstract machine promote the value of each variable to int size
     and then add the two ints and truncate the sum. Provided the addition of two chars can be done without
     overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only
     produce the same result, possibly omitting the promotions.

Существует также определение «ранга» , которое следует рассмотреть. Список правил довольно длинный, но в отношении этого вопроса «ранг» прост:

Ранг любого целого типа без знака должен равняться рангу соответствующий целочисленный тип со знаком.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...