(В отношении этого вопроса и ответа .)
До стандарта C ++ 17 в [basic.compound] / 3 было включено следующее предложение :
Если объект типа T расположен по адресу A, говорят, что указатель типа cv T *, значением которого является адрес A, указывает на этот объект, независимо от того, как было получено значение.
Но начиная с C ++ 17 это предложение было удалено .
Например, я считаю, что это предложение определило этот пример кода, и что, начиная с C ++ 17, это поведение undefined:
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
это указатель за концом , поэтому он не является указателем на объект, и я считаю, что его нельзя разыменовать.
Это интерпретация данной модификации стандартного права или существуют другие правила, которые компенсируют удаление процитированного предложения?
int a, b = 0;
, вы не можете этого сделать,*(&a + 1) = 1;
даже если вы проверили&a + 1 == &b
. Если вы можете получить действительный указатель на объект, просто угадав его адрес, то даже сохранение локальных переменных в регистрах становится проблематичным.Ответы:
Да, такая интерпретация верна. Указатель за концом нельзя просто преобразовать в другое значение указателя, которое указывает на этот адрес.
В новом [basic.compound] / 3 говорится:
Это взаимоисключающие.
p1+1
является указателем за концом, а не указателем на объект.p1+1
указывает на гипотетическийx[1]
массив размера 1 наp1
, а не наp2
. Эти два объекта не взаимопреобразуемы по указателям.У нас также есть ненормативное примечание:
что проясняет намерение.
Как указывает TC в многочисленных комментариях (в частности, в этом ), это действительно частный случай проблемы, которая возникает при попытке реализовать
std::vector
- это[v.data(), v.data() + v.size())
должен быть допустимый диапазон и при этомvector
не создается объект массива, поэтому только арифметика с определенным указателем будет переходить от любого заданного объекта в векторе до конца его гипотетического одноразмерного массива. Для получения дополнительных ресурсов см. CWG 2182 , это стандартное обсуждение и две версии документа по этому вопросу: P0593R0 и P0593R1 (в частности, раздел 1.3).источник
vector
проблемы реализуемости». +1.p1+1
больше не будет производить пришедшие к торцевому указателю и вся дискуссия о пришедших к торцевым указателям спорна. Ваш конкретный двухэлементный особый случай может не быть UB до 17, но это тоже не очень интересно.В вашем примере это
*(p1 + 1) = 10;
должно быть UB, потому что это один за концом массива размера 1. Но мы находимся в очень особом случае здесь, потому что массив был динамически построен в большем массиве char.Создание динамических объектов описано в 4.5 Объектная модель C ++ [intro.object] , §3 черновика n4659 стандарта C ++:
Версия 3.3 кажется довольно неясной, но примеры ниже проясняют замысел:
Итак, в этом примере
buffer
массив предоставляет хранилище для*p1
и*p2
.Следующие пункты доказывают , что законченным объектом для обоих
*p1
и*p2
являетсяbuffer
:Как только это будет установлено, другой соответствующей частью проекта n4659 для C ++ 17 будет [basic.coumpound] §3 (выделите мой):
Примечание Указатель за концом ... здесь не применяется, потому что объекты, на которые указывает
p1
иp2
не являются несвязанными , но вложены в один и тот же полный объект, поэтому арифметика указателей имеет смысл внутри объекта, который обеспечивает хранилище:p2 - p1
определен и является(&buffer[sizeof(int)] - buffer]) / sizeof(int)
то есть 1.Так
p1 + 1
есть указатель*p2
, и*(p1 + 1) = 10;
имеет определенное поведение и устанавливает значение*p2
.Я также прочитал приложение C4 о совместимости между C ++ 14 и текущими стандартами (C ++ 17). Удаление возможности использовать арифметику указателей между объектами, динамически создаваемыми в едином символьном массиве, было бы важным изменением, на которое следует упомянуть IMHO, потому что это часто используемая функция. Поскольку на страницах совместимости ничего об этом не содержится, я думаю, что это подтверждает, что стандарт не имел намерения запрещать это.
В частности, это нарушило бы обычное динамическое построение массива объектов из класса без конструктора по умолчанию:
class T { ... public T(U initialization) { ... } }; ... unsigned char *mem = new unsigned char[N * sizeof(T)]; T * arr = reinterpret_cast<T*>(mem); // See the array as an array of N T for (i=0; i<N; i++) { U u(...); new(arr + i) T(u); }
arr
затем можно использовать как указатель на первый элемент массива ...источник
vector
не создается (и не может быть) объект массива, но есть интерфейс, который позволяет пользователю получить указатель, поддерживающий арифметику указателей (которая определена только для указателей на объекты массива).Чтобы расширить приведенные здесь ответы, приведу пример того, что, по моему мнению, исключает измененная формулировка:
Предупреждение: неопределенное поведение
#include <iostream> int main() { int A[1]{7}; int B[1]{10}; bool same{(B)==(A+1)}; std::cout<<B<< ' '<< A <<' '<<sizeof(*A)<<'\n'; std::cout<<(same?"same":"not same")<<'\n'; std::cout<<*(A+1)<<'\n';//!!!!! return 0; }
По причинам, полностью зависящим от реализации (и хрупким), возможный результат этой программы:
0x7fff1e4f2a64 0x7fff1e4f2a60 4 same 10
Эти выходные данные показывают, что два массива (в этом случае) хранятся в памяти так, что «один за концом» хранит
A
значение адреса первого элементаB
.Пересмотренная спецификация гарантирует, что независимо
A+1
никогда не будет действительным указателем наB
. Старая фраза «независимо от того, как получено значение» гласит, что если «A + 1» указывает на «B [0]», то это действительный указатель на «B [0]». Это не может быть добром, и уж точно не намерением.источник
-O0
в некоторых компиляторах) не рассматривает указатели как тривиальные типы. Компиляторы не относятся серьезно к требованиям std, равно как и люди, которые пишут std, мечтают о другом языке и делают всевозможные изобретения, прямо противоречащие основным принципам. Очевидно, что пользователи сбиты с толку и иногда плохо обращаются, когда жалуются на ошибки компилятора.