Рассмотрим следующее†:
size_t r = 0;
r--;
const bool result = (r == -1);
Есть ли сравнение, чей результат инициализируется result
иметь четко определенное поведение?
И это его результат true
как я ожидала?
Это Q&А было написано, потому что я не был уверен в двух факторах, в частности.
Оба могут быть идентифицированы с использованием термина «критический [ly]» в моем ответе.
† Этот пример вдохновлен подходом к условиям цикла, когда счетчик не подписан:
for (size_t r = m.size() - 1; r != -1; r--)
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:
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 бита.
Да, и результат — то, что вы ожидаете.
Давайте разберемся с этим.
Какова стоимость 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"