С ++ Неожиданное целочисленное продвижение - PullRequest
7 голосов
/ 10 мая 2019

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

#include <cstdint>
#include <limits>

int main()
{
    std::uint8_t a, b;
    a = std::numeric_limits<std::uint8_t>::max();
    b = a;

    a = a + 1;

    if (a != b + 1)
        return 1;
    else
        return 0;
}

Удивительно, но эта программа возвращает 1. Некоторая отладка и догадка показали, что b + 1 в условном выражении фактически возвращало 256, тогда как a + 1 в присваивании выдает ожидаемое значение 0.

Раздел 8.10.6 (об операторах равенства / несоблюдения) проекта C ++ 17 гласит, что

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

Что такое "обычные арифметические преобразования" и где они определены в стандарте? Я предполагаю, что они неявно повышают меньшие целые числа до int или unsigned int для определенных операторов (что также подтверждается тем фактом, что замена std::uint8_t на unsigned int приводит к 0, и, кроме того, в операторе присваивания отсутствует " "обычные арифметические преобразования" оговорка).

Ответы [ 2 ]

2 голосов
/ 10 мая 2019

Ваше предположение верно. Операнды для многих операторов в C ++ (например, двоичные арифметические операции и операторы сравнения) подвергаются обычным арифметическим преобразованиям. В C ++ 17 обычные арифметические преобразования определены в [expr] / 11 . Я не собираюсь цитировать весь абзац здесь, потому что он довольно большой (вы можете просто щелкнуть ссылку), но для целочисленных типов обычные арифметические преобразования сводятся к применяемым интегральным продвижениям, а затем к некоторому более эффективному продвижению в смысле что если типы двух операндов после начальных интегральных повышений не совпадают, меньший тип преобразуется в больший из двух. Интегральные продвижения в основном означают, что любой тип, меньший int, будет повышен до int или unsigned int, в зависимости от того, какое из двух значений может представлять все возможные значения исходного типа, что в основном и является причиной поведения в вашем пример.

Как вы уже выяснили, в вашем коде обычные арифметические преобразования происходят в a = a + 1; и, что наиболее заметно, в состоянии вашего if

if (a != b + 1)
    …

, где они вызывают повышение b до int, в результате чего b + 1 будет иметь тип int, а a - int и !=, таким образом, происходит со значениями типа int, что делает условие истинным, а не ложным & hellip;

2 голосов
/ 10 мая 2019

Что такое "обычные арифметические преобразования" и где они определены в стандарте?

[expr.arith.conv] / 1

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

  • (1.1) Если любой из операндов имеет тип нумерации с ограничением , преобразования не выполняются;если другой операнд не имеет того же типа, выражение неправильно сформировано.

  • (1.2) Если один из операндов имеет тип long double, другой должен быть преобразован в long double.

  • (1.3) В противном случае, если один из операндов является двойным, другой должен быть преобразован в двойной.

  • (1.4) В противном случае, еслиодин из операндов является плавающим, другой должен быть преобразован в плавающее.

  • (1.5) В противном случае интегральное повышение ( [conv.prom] ) должно выполнятьсяоба операнда. 59 Тогда к повышенным операндам должны применяться следующие правила:

    • (1.5.1) Если оба операнда имеюттого же типа, дальнейшее преобразование не требуется.

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

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

    • (1.5.4) В противном случае, если тип операнда с целым типом со знаком может представлять все значения типа операнда сцелочисленный тип без знака, операнд с целым типом без знака должен быть преобразован в тип операнда с целым типом со знаком.

    • (1.5.5) В противном случае оба операнда должны быть преобразованы вЦелочисленный тип без знака, соответствующий типу операнда с целочисленным типом со знаком.

59) Как следствие, операнды типа bool, char8_t, char16_t, char32_t, wchar_t или перечислимого типа преобразуются в некоторый целочисленный тип.

Для uint8_t против int (для operator+ и operator!= латэ-э), применяется # 1.5, uint8_t будет повышен до int, а результат operator+ тоже равен int.

С другой стороны, для unsigned int против int (для operator+), применяется # 1.5.3, int будет преобразовано в unsigned int, а результат operator+ будет unsigned int.

...