Преобразование целого числа в указатель всегда хорошо определено? - PullRequest
7 голосов
/ 10 февраля 2020

Является ли это действительным C ++?

int main() {
    int *p;
    p = reinterpret_cast<int*>(42);
}

Предполагая, что я никогда не разыменовываю p.

Если посмотреть стандарт C ++, у нас есть

C ++ 17 §6.9.2 / 3 [basi c .compound]

3 Каждое значение типа указателя является одним из следующих:

  • указатель на объект или функцию (говорят, что указатель указывает на объект или функцию), или
  • указатель после конца объекта ([expr.add]), или
  • значение нулевого указателя ([conv.ptr]) для этого типа или
  • недопустимое значение указателя.

Значение типа указателя, которое указатель на конец объекта или после него представляет адрес первого байта в памяти ([intro.memory]), занятого объектом, или первого байта в памяти после окончания памяти, занимаемой объектом, соответственно. [Примечание: указатель за концом объекта ([expr.add]), как полагают, не указывает на не связанный объект типа объекта, который может быть расположен по этому адресу. Значение указателя становится недействительным, когда хранилище, которое он обозначает, достигает конца своего срока хранения; см. [basi c .stc]. - примечание конца] В целях арифметики указателя ([expr.add]) и сравнения ([expr.rel], [expr.eq]) указатель после конца последнего элемента массива x из n элементов считается эквивалентно указателю на гипотетический элемент массива n из x и объект типа T, который не является элементом массива, считается принадлежащим массиву с одним элементом типа T.

p = reinterpret_cast<int*>(42); не вписывается в список возможных значений. И:

C ++ 17 §8.2.10 / 5 [expr.reinterpret.cast]

Значение целочисленного типа или типа перечисления может быть явно преобразуется в указатель. Указатель, преобразованный в целое число достаточного размера (если таковое существует в реализации) и обратно в тот же тип указателя, будет иметь свое первоначальное значение; Отображения между указателями и целыми числами определяются реализацией. [Примечание: за исключением случаев, описанных в 6.7.4.3, результатом такого преобразования не будет безопасно полученное значение указателя. - примечание конца]

Стандарт C ++, кажется, не говорит больше о преобразовании целых чисел в указатели. Поиск стандарта C17:

C17 §6.3.2.3 / 5 (выделено мной)

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

и

C17 §6.2.6.1 / 5

Некоторые представления объектов не обязательно должны представлять значение типа объекта. Если сохраненное значение объекта имеет такое представление и читается выражением lvalue, которое не имеет символьного типа, поведение не определено. Если такое представление создается побочным эффектом, который изменяет весь или любую часть объекта выражением lvalue, которое не имеет символьного типа, поведение не определено. 50) Такое представление называется представлением ловушек.

Мне кажется, что любое значение, которое не помещается в список в [basi c .compound], является представлением ловушки, поэтому p = reinterpret_cast<int*>(42); - это UB. Я прав? Что-то еще делает p = reinterpret_cast<int*>(42); неопределенным?

Ответы [ 3 ]

5 голосов
/ 10 февраля 2020

Это не UB, а определяется реализацией, и вы уже упоминали почему (§8.2.10 / 5 [expr.reinterpret.cast]). Если указатель имеет недопустимое значение указателя, это не обязательно означает, что он имеет представление ловушки. Он может иметь представление прерывания, и компилятор должен это документировать. Все, что у вас есть, это не безопасно полученный указатель.

Обратите внимание, что мы постоянно генерируем указатели с недопустимым значением указателя: если объект освобождается с помощью delete, все указатели, которые указали на этот объект имеют недопустимое значение указателя.

Использование результирующего указателя также определяет реализацию (не UB):

[...] если объект, к которому ссылка glvalue содержит недопустимое значение указателя ([basi c .st c .dynami c .deallocation], [basi c .st c .dynami c .safety]), поведение определяется реализацией.

2 голосов
/ 10 февраля 2020

Показанный пример действителен c++. На некоторых платформах именно так вы получаете доступ к «аппаратным ресурсам» (и если они недействительны, вы нашли ошибку / ошибку в стандартном тексте).

См. Также этот ответ для лучшего объяснения. .


Обновление: первое предложение reinterpret_cast, когда вы цитируете себя:

Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель.

Я рекомендую вам прекратить чтение и отдохнуть на этом этапе. Остальные подробности, включая возможное поведение, заданное реализацией, et c. Это не делает его UB / недействительным.

0 голосов
/ 08 марта 2020

Представления ловушек

Что: Как указано в [C17 §6.2.6.1 / 5], представление ловушек не является значением. Это битовый шаблон, который заполняет пространство, выделенное для объекта данного типа, но этот шаблон не соответствует значению этого типа. Это специальный шаблон, который можно распознать с целью запуска поведения, определенного реализацией. То есть поведение не охватывается стандартом, что означает, что оно попадает под баннер «неопределенного поведения». Стандарт устанавливает возможности для того, когда ловушка может быть (не должна быть) запущена, но он не пытается ограничить то, что ловушка может делать. Для получения дополнительной информации см. A: представление ловушки .

Неопределенное поведение, связанное с представлением ловушки, интересно тем, что реализация должна его проверять. Более распространенные случаи неопределенного поведения были оставлены неопределенными, поэтому реализации не должны проверять их. Необходимость проверки представлений прерываний является хорошей причиной для того, чтобы в эффективной реализации требовалось несколько представлений прерываний.

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

Почему: Теоретически, представление ловушек может использоваться в качестве средства отладки. Например, реализация может объявить, что 0xDDDD является представлением ловушек для типов указателей, а затем выбрать инициализацию всех неинициализированных указателей в противном случае для этого битового шаблона. Чтение этого шаблона битов может вызвать ловушку, которая предупреждает программиста об использовании неинициализированного указателя. (Без ловушки, cra sh может не произойти до тех пор, пока не усложнит процесс отладки. Иногда раннее обнаружение является ключевым.) В любом случае представление ловушки требует какой-то ловушки, чтобы служить цели. Реализация не будет определять представление ловушек, не определяя также его ловушку.

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

Значения указателя

C ++ 17 §6.9.2 / 3 [basi c .compound]

В этом разделе определяется недопустимое значение указателя. В нем говорится «Каждое значение типа указателя является одним из следующих» перед перечислением четырех возможностей. Это означает, что если у вас есть значение указателя, то это одна из четырех возможностей. Первые три полностью определены (указатель на объект или функцию, указатель за концом и нулевой указатель). Последняя возможность (неверное значение указателя) в другом месте не указана полностью, поэтому она становится универсальной записью «все остальное» в списке (она является «подстановочным знаком», чтобы заимствовать терминологию из комментариев ). Следовательно, этот раздел определяет «недопустимое значение указателя», чтобы обозначить значение указателя, которое не указывает на что-то, не указывает на конец чего-либо и не является нулевым. Если у вас есть значение указателя, которое не соответствует ни одной из этих трех категорий, оно недопустимо.

В частности, если мы согласны с тем, что reinterpret_cast<int*>(42) не указывает на что-то, не указывает на конец чего-либо и не является нулевым, то мы должны заключить, что это недопустимое значение указателя. (Правда, можно предположить, что результатом приведения является представление ловушки для указателей в некоторой реализации. В этом случае, да, оно не вписывается в список возможных значений указателя, потому что это не было бы значением указателя, следовательно, это представление ловушек. Однако это циклическая логика c. Кроме того, основываясь на N2091 , немногие реализации определяют какие-либо представления ловушек для указателей, поэтому предположение, скорее всего, необоснованно.)

[Примечание: [...] значение указателя становится недействительным, когда хранилище, которое он обозначает, достигает конца своего срока хранения; см. [basi c .stc]. - конец примечания]

Сначала я должен подтвердить, что это примечание. Это объясняет и разъясняет без добавления нового вещества. Не следует ожидать никаких определений в заметке.

В этой заметке приведен пример неверного значения указателя. В нем поясняется, что указатель может (возможно, удивительно) измениться с «точек на объект» на «недопустимое значение указателя» без изменения его значения. Если посмотреть на это с формальной точки зрения логики c, то это примечание подразумевает: "если [что-то], то [неверный указатель]" . Просмотр этого как определения «неверного указателя» является ошибочным ; это всего лишь пример одного из способов получения неверного указателя.

Приведение

C ++ 17 §8.2.10 / 5 [ expr.reinterpret.cast]

Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель.

Это явно разрешает reinterpret_cast<int*>(42). Поэтому поведение определено.

Чтобы быть точным, следует убедиться, что в стандарте нет ничего, что делает 42 "ошибочными данными" до такой степени, что неопределенное поведение является результатом приведения. Остальная часть [§8.2.10 / 5] не делает этого, и:

Стандарт C ++, кажется, не говорит больше о преобразовании целых в указатель.

Это действительный C ++?

Да.

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