Является ли указатель с правильным адресом и типом все еще всегда действительным указателем с C ++ 17?

(В отношении этого вопроса и ответа .)

До стандарта C ++ 17 в [basic.compound] / 3 было включено следующее предложение:

Если объект типа T расположен по адресу A, указатель типа cv T *, значение которого является адресом A, как говорят, указывает на этот объект, независимо от того, как это значение было получено.

Но поскольку C ++ 17, это предложение было удалено .

Например, я считаю, что это предложение определило этот пример кода и что с C ++ 17 это неопределенное поведение:

alignas(int) unsigned char buffer[2*sizeof(int)]; auto p1=new(buffer) int{}; auto p2=new(p1+1) int{}; *(p1+1)=10; 

Перед C ++ 17, p1+1 имеет адрес в *p2 и имеет правильный тип, поэтому *(p1+1) является указателем на *p2 . В C ++ 17 p1+1 – это указатель, проходящий мимо конца , поэтому он не является указателем на объект, и я считаю, что он не является неразрешимым.

Является ли это толкование этой модификации стандартного права или существуют другие правила, которые компенсируют удаление цитируемого предложения?

Является ли это толкование этой модификации стандартного права или существуют другие правила, которые компенсируют исключение этого цитируемого предложения?

Да, эта интерпретация верна. Указатель мимо конца не просто конвертируется в другое значение указателя, которое указывает на этот адрес.

Новый [basic.compound] / 3 говорит:

Каждое значение типа указателя является одним из следующих:
(3.1) указатель на объект или функцию (указатель, как говорят, указывает на объект или функцию), или
(3.2) указатель мимо конца объекта ([expr.add]) или

Это взаимоисключающие. p1+1 – это указатель за концом, а не указатель на объект. p1+1 указывает на гипотетический x[1] массива size-1 в p1 , а не p2 . Эти два объекта не являются взаимно конвертируемыми.

У нас также есть ненормативная нота:

[Примечание. Указатель за конец объекта ([expr.add]) не считается указывающим на несвязанный объект типа объекта, который может быть расположен по этому адресу. […]

который уточняет намерение.


Поскольку TC указывает на многочисленные комментарии (в частности, на этот ), это действительно частный случай проблемы, возникающей при попытке реализовать std::vector – это то, что [v.data(), v.data() + v.size()) должен быть допустимым диапазоном, и все же vector не создает объект массива, поэтому единственная определенная арифметика указателя будет проходить от любого заданного объекта в векторе до конца его гипотетического одномерного размера массив. Больше ресурсов, см. CWG 2182 , это обсуждение std и два пересмотра статьи по этому вопросу: P0593R0 и P0593R1 (раздел 1.3 конкретно).

В вашем примере *(p1 + 1) = 10; должен быть UB, потому что он один за концом массива размера 1. Но мы здесь в очень особенном случае, потому что массив был динамически сконструирован в большем массиве char.

Создание динамического объекта описано в 4.5. Объектная модель C ++ [intro.object] , § 3 проекта n4659 стандарта C ++:

3 Если в хранилище, связанном с другим объектом e типа «массив N unsigned char» или типа «массив из N std :: byte» (21.2.1), создается полный объект (8.3.4), этот массив обеспечивает хранение для созданного объекта, если:
(3.1) – время жизни e началось и не закончилось, и
(3.2) – хранилище для нового объекта полностью вписывается в e и
(3.3) – нет меньшего объекта массива, который удовлетворяет этим ограничениям.

3.3 кажется довольно неясным, но приведенные ниже примеры делают цель более ясной:

 struct A { unsigned char a[32]; }; struct B { unsigned char b[16]; }; A a; B *b = new (aa + 8) B; // aa provides storage for *b int *p = new (b->b + 4) int; // b->b provides storage for *p // aa does not provide storage for *p (directly), // but *p is nested within a (see below) 

Таким образом, в этом примере buffer массив обеспечивает хранение как для *p1 и для *p2 .

Следующие параграфы доказывают, что полный объект для обоих *p1 и *p2 является buffer :

4 Объект a вложен в другой объект b, если:
(4.1) – а является подобъектом Ь, или
(4.2) – b обеспечивает хранение для или
(4.3) – существует объект c, где a вложен в c, а c вложен в b.

5 Для каждого объекта x существует некоторый объект, называемый полным объектом x, определяемый следующим образом:
(5.1). Если x – полный объект, то полный объект x сам.
(5.2). В противном случае полный объект x является полным объектом (уникального) объекта, содержащего x.

Как только это будет установлено, другая соответствующая часть проекта n4659 для C ++ 17 – [basic.coumpound] §3 (подчеркните мой):

3 … Каждое значение типа указателя является одним из следующих:
(3.1) – указатель на объект или функцию (указывается, что указатель указывает на объект или функцию) или
(3.2) – указатель мимо конца объекта (8.7) или
(3.3) – значение нулевого указателя (7.11) для этого типа или
(3.4) – недопустимое значение указателя.

Значение типа указателя, которое является указателем на конец объекта или мимо него, представляет адрес первого байта в памяти (4.4), занятый объектом или первым байтом в памяти после окончания хранения, занимаемого объектом , соответственно. [Примечание. Указатель за конец объекта (8.7) не считается направленным на несвязанный объект типа объекта, который может быть расположен по этому адресу. Значение указателя становится недействительным, когда хранящееся хранилище заканчивается его длительностью хранения; см. 6.7. -End note] Для целей арифметики указателя (8.7) и сравнения (8.9, 8.10) указатель мимо конца последнего элемента массива x из n элементов считается эквивалентным указателю на гипотетический элемент x [ п]. Представление значений типов указателей определяется реализацией. Указатели на совместимые с макетами типы должны иметь одинаковые требования к представлению и выравниванию значений (6.11) …

Примечание . Указатель мимо конца … здесь не применяется, потому что объекты, на которые указывают p1 и p2 и не связаны друг с другом , но вложены в один и тот же полный объект, поэтому арифметика указателя имеет смысл внутри объекта, который обеспечивает хранение: p2 - p1 определяется и является (&buffer[sizeof(int)] - buffer]) / sizeof(int) который равен 1.

Таким образом, p1 + 1 является указателем на *p2 и *(p1 + 1) = 10; определил поведение и задает значение *p2 .


Я также прочитал приложение C4 о совместимости между стандартами C ++ 14 и current (C ++ 17). Удаление возможности использования арифметики указателя между объектами, динамически создаваемыми в одном массиве символов, было бы важным изменением, которое следует указывать в IMHO, потому что это обычно используемая функция. Поскольку ничего не существует на страницах совместимости, я думаю, что он подтверждает, что это не было целью стандарта запретить его.

В частности, это приведет к поражению общей динамической конструкции массива объектов из classа без конструктора по умолчанию:

 class T { ... public T(U initialization) { ... } }; ... unsigned char *mem = new unsigned char[N * sizeof(T)]; T * arr = reinterpret_cast(mem); // See the array as an array of NT for (i=0; i 

arr может затем использоваться как указатель на первый элемент массива ...

Расширение ответов, приведенных здесь, является примером того, что, по моему мнению, пересмотренная формулировка исключает:

Предупреждение: неопределенное поведение

 #include  int main() { int A[1]{7}; int B[1]{10}; bool same{(B)==(A+1)}; std::cout< 

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

 0x7fff1e4f2a64 0x7fff1e4f2a60 4 same 10 

Этот вывод показывает, что два массива (в этом случае) хранятся в памяти таким образом, что «один за конец» A происходит, чтобы удерживать значение адреса первого элемента B

Пересмотренная спецификация гарантирует, что независимо от того, как A+1 никогда не является допустимым указателем на B Старая фраза «независимо от того, как получается значение» говорит, что если «A + 1» означает «B [0]», то это действительный указатель на «B [0]». Это не может быть хорошим и, конечно же, никогда не намерением.

  • Почему cudaMalloc () использует указатель на указатель?
  • Почему std :: cout конвертирует изменчивые указатели в bool?
  • Постоянный указатель vs Указатель на константу
  • В чем разница между NULL, '\ 0' и 0
  • Должен ли я хранить целые объекты или указатели на объекты в контейнерах?
  • C Программирование: malloc () внутри другой функции
  • C указатель на двумерный массив
  • Что означает «имя массива» в случае массива указателей на символы?
  • Регулярное литье против static_cast vs. dynamic_cast
  • Функция не меняет пройденный указатель C ++
  • Ошибка сегментации при изменении строки с помощью указателей?
  • Interesting Posts

    Намерение запустить приложение часов на Android

    В Linux, какие точечные файлы и каталоги следует создать резервную копию?

    получить текущую дату с , но установить время до 10:00

    разрешение std :: chrono :: high_resolution_clock не соответствует измерениям

    String.Replace () vs. StringBuilder.Replace ()

    ПК в качестве динамиков Bluetooth

    pathPattern для соответствия расширению файла не работает, если существует какой-либо период в другом месте имени файла?

    Зацикливание результатов `ls` в скрипте оболочки bash

    Почему я не могу установить Windows 8 на машине UEFI с USB-диска на базе MBR?

    Получение команды diskutil для работы в режиме OSX Mavericks Single User

    Как слить скаляр в вектор без компилятора, теряющего инструкцию обнуления верхних элементов? Ограничение дизайна в встроенных средах Intel?

    Когда язык считается языком сценариев?

    Неплохо ли использовать if-инструкцию без фигурных скобок?

    Android – Использование пользовательского шрифта

    REST API 404: плохой URI или отсутствующий ресурс?

    Давайте будем гением компьютера.