Сравнивает ли недопустимое целое число без знака с -1 хорошо определенным?

Рассмотрим следующее:

size_t r = 0;
r--;
const bool result = (r == -1);

Есть ли сравнение, чей результат инициализируется result иметь четко определенное поведение?
И это его результат trueкак я ожидала?


Это Q&А было написано, потому что я не был уверен в двух факторах, в частности.
Оба могут быть идентифицированы с использованием термина «критический [ly]» в моем ответе.

Этот пример вдохновлен подходом к условиям цикла, когда счетчик не подписан:
for (size_t r = m.size() - 1; r != -1; r--)

22

Решение

size_t r = 0;
r--;
const bool result = (r == -1);

Строго говоря, значение result определяется реализацией. На практике это почти наверняка true; Я был бы удивлен, если бы была реализация, где это false,

Значение r после r-- это значение SIZE_MAXмакрос, определенный в <stddef.h> / <cstddef>,

Для сравнения r == -1, обычные арифметические преобразования выполняются на обоих операндах. Первым шагом в обычных арифметических преобразованиях является применение интегральные акции на оба операнда.

r имеет тип size_tопределяемый реализацией целочисленный тип без знака. -1 это выражение типа int,

На большинстве систем size_t по крайней мере такой же ширины, как int, На таких системах интегральные продвижения вызывают значение r либо быть преобразованным в unsigned int или сохранить свой существующий тип (первый может произойти, если size_t имеет ту же ширину, что и int, но более низкий рейтинг конверсии). Теперь левый операнд (который не подписан) имеет по крайней мере ранг правого операнда (который подписан). Правый операнд преобразуется в тип левого операнда. Это преобразование дает то же значение, что и rи поэтому сравнение равенства дает true,

Это «нормальный» случай.

Предположим, у нас есть реализация, где size_t 16 бит (скажем, это typedef за unsigned short) а также int 32 бита. Так SIZE_MAX == 65535 а также INT_MAX == 2147483647, Или мы могли бы иметь 32-разрядный size_t и 64-битный int, Я сомневаюсь, что такая реализация существует, но ничто в стандарте не запрещает это (см. Ниже).

Теперь левая сторона сравнения имеет тип size_t и значение 65535, поскольку подписанный int может представлять все значения типа size_t, интегральные акции конвертируют значение в 65535 типа int. Обе стороны == оператор имеет тип intПоэтому обычные арифметические преобразования не имеют ничего общего. Выражение эквивалентно 65535 == -1что явно false,

Как я уже говорил, такого рода вещи вряд ли произойдут с выражением типа size_t — но это может легко случиться с более узкими беззнаковыми типами. Например, если r объявлен как unsigned shortили unsigned charили даже равнина char в системе, где этот тип подписан, значение result вероятно будет false, (Я говорю, вероятно, потому что short или даже unsigned char может иметь ту же ширину, что и int, в таком случае result будет true.)

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

const bool result = (r == (size_t)-1);

или же

const bool result = (r == SIZE_MAX);

Стандартные ссылки на C ++ 11:

  • 5.10 [expr.eq] Операторы равенства
  • 5.9 [expr.rel] Реляционные операторы (указывает, что выполняются обычные арифметические преобразования)
  • 5 [expr] Выражения, параграф 9: Обычные арифметические преобразования
  • 4.5 [conv.prom] Интегральные акции
  • 18.2 [support.types] size_t

18.2 пункты 6-7:

6 Тип size_t является определяемым реализацией целочисленным типом без знака
это достаточно большой, чтобы содержать размер в байтах любого объекта.

7 [ Замечания: Рекомендуется, чтобы реализации выбирали типы для
ptrdiff_t а также size_t чьи целочисленные ранги преобразования (4.13) не имеют
больше, чем у signed long int если больший размер не
Необходимо содержать все возможные значения. — конец примечания]

Так что нет запрета на создание size_t уже int, Я могу почти правдоподобно представить себе систему, в которой int 64 бита, но ни один объект не может быть больше 232-1 байт так size_t 32 бита.

20

Другие решения

Да, и результат — то, что вы ожидаете.

Давайте разберемся с этим.

Какова стоимость r с этой точки зрения? Ну, нижний предел четко определен и приводит к r принимая максимальное значение к моменту сравнения. std::size_t не имеет конкретных известных границ, но мы можем сделать разумные предположения о его диапазоне по сравнению с int:

std::size_t целочисленный тип без знака результата оператора sizeof. [..] std::size_t может хранить максимальный размер теоретически возможного объекта любого типа (включая массив).

И, просто чтобы убрать это с пути, выражение -1 одинарный - применяется к буквальному 1и имеет тип int в любой системе:

[C++11: 2.14.2/2]: Тип целочисленного литерала является первым из соответствующего списка в Таблице 6, в котором может быть представлено его значение. [..]

(Я не буду приводить весь текст, который описывает, как применять унарный - для int приводит к int, но это так.)

Более чем разумно предположить, что на большинстве систем int не сможет удержать std::numeric_limits<std::size_t>::max(),

Теперь, что происходит с этими операндами?

[C++11: 5.10/1]: == (равно) и тому != (не равно) операторы имеют те же семантические ограничения, преобразования и тип результата, что и реляционные операторы, за исключением их более низкого приоритета и истинностного результата. [..]

[C++11: 5.9/2]: Обычные арифметические преобразования выполняются над операндами арифметического или перечислимого типа. [..]

Давайте рассмотрим эти «обычные арифметические преобразования»:

[C++11: 5/9]: Многие бинарные операторы, которые ожидают операнды арифметического или перечислимого типа, вызывают преобразования и выдают типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата.

Эта модель называется обычные арифметические преобразования, которые определены следующим образом:

  • Если какой-либо из операндов имеет тип перечисления с ограничением (7.2), преобразования не выполняются; если другой
    операнд не имеет того же типа, выражение плохо сформировано.
  • Если один из операндов имеет тип long double, другой должен быть преобразован в long double`.
  • В противном случае, если любой из операндов doubleдругой должен быть преобразован в double,
  • В противном случае, если любой из операндов floatдругой должен быть преобразован в float,
  • В противном случае интегральные преобразования (4.5) должны выполняться для обоих операндов.59 Затем к повышенным операндам применяются следующие правила:
    • Если оба операнда имеют одинаковый тип, дальнейшее преобразование не требуется.
    • В противном случае, если оба операнда имеют целочисленные типы со знаком или оба имеют целочисленные типы без знака,
      операнд с типом меньшего целого ранга преобразования должен быть преобразован в тип
      операнд с большим рангом.
    • В противном случае, если операнд с целочисленным типом без знака имеет ранг больше или равен рангу типа другого операнда, операнд с целочисленным типом со знаком должен быть преобразован в
      тип операнда с целым типом без знака.
    • В противном случае, если тип операнда с целочисленным типом со знаком может представлять все значения типа операнда с целочисленным типом без знака, операнд с целочисленным типом без знака должен быть преобразован в тип операнда с целочисленным типом со знаком.
    • В противном случае оба операнда должны быть преобразованы в целочисленный тип без знака, соответствующий
      тип операнда со знаком целочисленного типа.

Я выделил отрывок, который вступает в силу здесь, а что касается Зачем:

[C++11: 4.13/1]: Каждый целочисленный тип имеет целое число определяется следующим образом

  • [..]
  • Звание long long int должно быть больше, чем ранг long int, который должен быть больше, чем ранг int, который должен быть больше, чем ранг short int, который должен быть больше, чем ранг signed char,
  • Ранг любого целого типа без знака должен совпадать с рангом соответствующего целого типа со знаком.
  • [..]

Все целочисленные типы, даже с фиксированной шириной, состоят из стандартных целочисленных типов; следовательно, логично, std::size_t должно быть unsigned long long, unsigned long, или же unsigned int,

  • Если std::size_t является unsigned long long, или же unsigned longтогда звание std::size_t больше, чем ранг unsigned int и, следовательно, также из int,

  • Если std::size_t является unsigned intзвание std::size_t равно званию unsigned int и, следовательно, также из int,

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

[C++11: 4.7/2]: Если тип назначения является беззнаковым, полученное значение является наименьшим целым числом без знака, соответствующим исходному целому числу (по модулю 2N где N количество битов, используемых для представления типа без знака). [ Заметка: В представлении дополнения до двух это преобразование является концептуальным, и в битовой комбинации нет изменений (если нет усечения). —Конечная записка]

[C++11: 4.7/3]: Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и ширине битового поля); в противном случае значение определяется реализацией.

Это означает, что std::size_t(-1) эквивалентно std::numeric_limits<std::size_t>::max(); важно, чтобы значение N в приведенном выше пункте относится к числу битов, используемых для представления неподписанный тип, а не тип источника. В противном случае мы будем делать std::size_t((unsigned int)-1), что совсем не одно и то же — оно может быть на много порядков меньше, чем желаемое нами значение!

Действительно, теперь, когда мы знаем, что все преобразования четко определены, мы можем проверить это значение:

std::cout << (std::size_t(-1) == std::numeric_limits<size_t>::max()) << '\n';
// "1"

И, чтобы проиллюстрировать мою точку зрения ранее, на моей 64-битной системе:

std::cout << std::is_same<unsigned long, std::size_t>::value << '\n';
std::cout << std::is_same<unsigned long, unsigned int>::value << '\n';
std::cout << std::hex << std::showbase
<< std::size_t(-1) << ' '
<< std::size_t(static_cast<unsigned int>(-1)) << '\n';
// "1"// "0"// "0xffffffffffffffff 0xffffffff"
13

По вопросам рекламы [email protected]