Работало ли когда-либо подписанное / неподписанное правило псевдонимов как задумано? - PullRequest
0 голосов
/ 08 января 2019

Вот правило ([basic.lval] / 8) в форме C ++ 17, но оно выглядит аналогично в других стандартах («lvalue» вместо «glvalue» в C ++ 98):

8 Если программа пытается получить доступ к сохраненному значению объекта через glvalue, отличный от одного из следующих типов, поведение не определено:

(8.4) - тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта

Правило звучит как «У вас будет UB, если вы не сделаете X», но это не значит, что если вы сделаете X, вы не получите UB, как можно было ожидать! И действительно, выполнение X является условным или безусловным UB, в зависимости от версии стандарта.

Давайте посмотрим на следующий код:

int i = -1;
unsigned j = reinterpret_cast<unsigned&>(i);

Каково поведение этого кода?

C ++ 98 и C ++ 11

[expr.reinterpret.cast] / 10 (/ 11 в C ++ 11) (акцент мой):

Выражение lvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель» в T1 »можно явно преобразовать в тип« указатель на T2 »с помощью reinterpret_cast. Это ссылка приведение reinterpret_cast (x) имеет тот же эффект, что и преобразование * reinterpret_cast (& x) со встроенными операторами & и *. Результатом является lvalue, который относится к тому же объекту, что и источник lvalue , но с другим типом .

Итак, reinterpret_cast<unsigned&>(i) lvalue относится к int объекту i, но с типом usigned. Для инициализации необходимо значение инициализирующего выражения, что формально означает, что преобразование lvalue в rvalue применяется к lvalue.

* 1 033 * [conv.lval] / 1:

Значение l , не являющееся функцией, не являющееся массивом типа T, может быть преобразовано в значение . Если Т неполный типа, программа, которая требует этого преобразования, плохо сформирована. Если объект, на который ссылается lvalue, не является объект типа T и не является объектом типа, производного от T, или, если объект неинициализирован, программа что требует этого преобразования имеет неопределенное поведение .

Наше lvalue типа unsigned не относится к объекту типа unsigned, что означает, что поведение не определено.

C ++ 14 и C ++ 17

В этих стандартах ситуация немного сложнее, но правила немного смягчены. [expr.reinterpret.cast] / 11 говорит то же самое:

Результат ссылается на тот же объект, что и источник glvalue, но с указанным типом.

Формулировка об UB была удалена из [conv.lval] / 1:

Значение без функции, не массив типа T может быть преобразовано в значение . Если T - неполный тип, программа, которая требует этого преобразования, плохо сформирована. Если T - это не относящийся к классу тип, типом prvalue является cv-неквалифицированная версия T . В противном случае тип значения - T.

Но какое значение читает преобразование L-в-R? [conv.lval] / (2.6) (/(3.4) в C ++ 17) отвечает на этот вопрос:

… значение, содержащееся в объекте, указанном в glvalue, является результатом prvalue

unsigned lvalue reinterpret_cast<unsigned&>(i) указывает на объект i int со значением -1, а значение, полученное в результате преобразования L-в-R, имеет тип unsigned. [expr] / 4 говорит:

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

-1 определенно не входит в диапазон представимых значений для типа unsigned выражения prvalue, поэтому поведение не определено.

Поведение было бы определено, если бы объект i содержал значение из диапазона [0, INT_MAX].

тот же гeasoning применяется в случае, когда к объекту unsigned обращаются через значение int glvalue. Это UB в C ++ 98 и C ++ 11 и UB в C ++ 14 и C ++ 17, если только значение объекта не находится в диапазоне [0, INT_MAX].

Таким образом, в отличие от распространенного мнения о том, что это правило наложения имен позволяет интерпретировать объект как содержащий значение соответствующего типа со знаком / без знака, оно не допускает этого. Для значений в диапазоне [0, INT_MAX] объекты со знаком и без знака имеют одинаковое представление (" Диапазон неотрицательных значений целого типа со знаком - это поддиапазон соответствующего целого типа без знака, представление одно и то же значение в каждом из этих двух типов одинаково", - говорит [basic.fundamental] / 3 в C ++ 17). Трудно назвать такой доступ «реинтерпретацией», не говоря уже о том, что это был безусловный UB до C ++ 14.

Какова цель правила ([basic.lval] / (8.4)) тогда?

1 Ответ

0 голосов
/ 08 января 2019

Это было предметом сообщения о дефекте 2214 , в котором говорится:

Раздел: 6.9.1 [basic.fundamental] Статус: C ++ 17 Автор: Ричард Смит Дата: 2015-12-15

[Принята на совещании в феврале / марте 2017 года.]

В соответствии с пунктом 6.9.1 [basic.fundamental],

Диапазон неотрицательных значений целочисленного типа со знаком является поддиапазоном соответствующего целочисленного типа без знака, и представление значения каждого соответствующего типа со знаком / без знака должно быть одинаковым. (Это формулировка в версиях C ++ 11 и C ++ 14, хотя номера абзацев могут быть разными - n.m.)

Соответствующая формулировка из C11:

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

Формулировка C, возможно, более ясна, но она теряет смысл формулировки C ++ о том, что знаковый бит типа со знаком является частью представления значения соответствующего типа без знака.

Предлагаемое решение (январь 2017 г.):

Изменить пункт 6.9.1 [basic.fundamental] следующим образом:

... Стандартные и расширенные целочисленные типы без знака вместе называются целочисленными типами без знака. Диапазон неотрицательных значений целочисленного типа со знаком - это поддиапазон соответствующего целочисленного типа без знака, представление одного и того же значения в каждом из двух типов одинаково , а представление значения каждого соответствующий тип со знаком / без знака должен быть таким же. Стандартные целочисленные типы со знаком ...

Так что это, очевидно, было намерением все время. C ++ 17 только что исправил формулировку.

Стандарты C и C ++ никогда не предназначались для того, чтобы разрешить повторную интерпретацию отрицательных значений как беззнаковых или наоборот. Существует несколько целочисленных представлений со знаком (например, одно дополнение, дополнение двух, знак и величина), и стандарт не предписывает ни одно из них, поэтому он не может предписывать эффекты такой реинтерпретации. Они могли бы быть заданными реализацией, но, учитывая возможность представления ловушек, в этом нет реальной выгоды. «Определенный реализацией результат или ловушка» так же хорош, как и «неопределенный».

...