У вашому прикладі *(p1 + 1) = 10;
має бути UB, тому що це один кінець масиву розміром 1. Але ми тут у дуже особливому випадку, оскільки масив динамічно будувався у більшому масиві char.
Динамічне створення об'єкта описано в 4.5 Об'єктна модель C ++ [intro.object] , §3 проекту n4659 проекту стандарту C ++:
3 Якщо в пам’яті створюється повний об’єкт (8.3.4), пов’язаний з іншим об’єктом e типу „масив N беззнакового символу” або типу „масив N std :: byte” (21.2.1), цей масив забезпечує зберігання для створеного об'єкта, якщо:
(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 (a.a + 8) B;
int *p = new (b->b + 4) int;
Отже, у прикладі buffer
масив забезпечує сховище для обох *p1
та *p2
.
Наступні параграфи доводять, що повним об’єктом для обох *p1
і *p2
є buffer
:
4 Об'єкт a вкладений в інший об'єкт b, якщо:
(4.1) - a є суб'єктом b, або
(4.2) - b забезпечує зберігання для a, або
(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. —Кінцева примітка] Для арифметики вказівника (8.7) та порівняння (8.9, 8.10) вказівник, що минає кінець останнього елемента масиву x з n елементів, вважається еквівалентом вказівника на гіпотетичний елемент x [ n]. Представлення значень типів покажчиків визначається реалізацією. Вказівники на типи, сумісні з макетом, повинні мати однакові вимоги щодо подання значення та вирівнювання (6.11) ...
Примітка Покажчик Минуле кінця ... тут не діє , так як об'єкти , на який вказує 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);
for (i=0; i<N; i++) {
U u(...);
new(arr + i) T(u);
}
arr
потім може використовуватися як вказівник на перший елемент масиву ...