Объясните целочисленное сравнение с продвижением

Я пытаюсь понять, как работает целочисленное продвижение и сравнение в приложении c++.

#include <cstdint>

int main(void)
{
    uint32_t foo  = 20;
    uint8_t a = 2;
    uint8_t b = 1;
    uint8_t c = 5;

    if(foo == b*c) {}

    if(foo == a) {}

    if(foo == a + c) {}

    if(foo == a + b*c) {}

    return 0;
}

Только для последнего сравнения я получаю предупреждение компилятора: "сравнение между целочисленными выражениями со знаком и без знака [-Wsign-compare]".

Почему это происходит только в последнем случае, а не в других?

Ответы

Ответ 1

Так как тип операндов различен, происходит неявное преобразование для достижения общего типа.

Для бинарных операторов (кроме сдвигов), если повышенные операнды имеют разные типы, применяется дополнительный набор неявных преобразований, известных как обычные арифметические преобразования, с целью создания общего типа (также доступного через черту типа std :: common_type)

из-за целочисленных типов здесь применяются интегральные преобразования:

  • Если один из операндов имеет тип перечисления с областью действия, преобразование не выполняется: другой операнд и возвращаемый тип должны иметь одинаковые
    тип
    • В противном случае, если один из операндов long long, другой операнд преобразуется в long double
    • В противном случае, если один из операндов является двойным, другой операнд преобразуется в двойной
    • В противном случае, если один из операндов является float, другой операнд преобразуется в float
    • В противном случае операнд имеет целочисленный тип (потому что перечисление bool, char, char8_t, char16_t, char32_t, wchar_t и unscoped перечислено в этой точке), и интегральные преобразования применяются для получения общего типа следующим образом:
    • Если оба операнда подписаны или оба без знака, операнд с меньшим рангом преобразования преобразуется в операнд с большим целым рангом преобразования
    • В противном случае, если ранг преобразования беззнакового операнда больше или равен рангу преобразования подписанного операнда, подписанный операнд преобразуется в беззнаковый
      тип операнда.
    • В противном случае, если тип операнда со знаком может представлять все значения беззнакового операнда, беззнаковый операнд преобразуется в тип операнда со знаком. В противном случае оба операнда преобразуются в беззнаковый аналог типа операнда со знаком.

Те же арифметические преобразования применимы и к операторам сравнения.

из всего этого можно сделать вывод, что все rhs равны uint8_t общий тип будет int, а затем, поскольку rhs равен uint32_t общий тип оператора == будет uint32_t. но по какой-то причине я понятия не имею, gcc не выполняет последнее преобразование, пока clang делает это. смотрите преобразование типа gcc для оператора + в godblot. Также может случиться, что предупреждение является ложным предупреждением, и преобразование произошло, как это произошло для оператора +. Посмотрите, как clang видит последнее if (cppinsights):

if(foo == static_cast<unsigned int>(static_cast<int>(a) + (static_cast<int> 
(b) * static_cast<int>(c))))

Обновить:

Я не смог найти разницы в сборке, сгенерированной двумя компиляторами, и согласился бы с @MM, так что, IMO, это ошибка gcc.

Ответ 2

Это "ошибка" компилятора. Чтобы уточнить это:

  • В общем, сравнение между знаковым и беззнаковым зависит от заданных реализацией величин (размеров/диапазонов типов). Например, USHRT_MAX == -1 имеет значение true в распространенных 16-разрядных системах и false в распространенных 32-разрядных системах. Ответ "забвения" входит в более технические детали об этом.

  • Все ваши примеры кода четко определены и ведут себя одинаково на всех (соответствующих) системах.

Цель этого предупреждения двояка:

  1. предупредить вас о коде, который может вести себя по-другому в других системах.
  2. предупредить вас о коде, который может вести себя не так, как задумал кодер.

Однако в целом. Для статического анализа компилятора не так-то просто разобраться в первом случае, не говоря уже о втором, довольно субъективном.

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

Лично я не включаю это предупреждение: я знаком с правилами сравнения со знаком без знака и предпочитаю избегать искажения моего кода для подавления предупреждения.

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

GCC имеет тенденцию слишком часто ошибаться в предупреждении, но они находятся в ситуации, когда не могут угодить всем.