Интерконвертируемость указателя против того же адреса
рабочий проект стандарта N4659 гласит:
[basic.compound]
Если два объекта являются взаимно конвертируемыми, то они имеют один и тот же адрес
а затем отмечает, что
Объект массива и его первый элемент не являются взаимопереключателями, хотя они имеют один и тот же адрес
В чем обоснование для создания объекта массива и его первого элемента, не являющегося указателем-взаимообратимым? В более общем плане, в чем смысл разграничения понятия указатель-интерконвертируемости от понятия того же адреса? Разве там нет противоречия?
Казалось бы, учитывая эту последовательность утверждений
int a[10];
void* p1 = static_cast<void*>(&a[0]);
void* p2 = static_cast<void*>(&a);
int* i1 = static_cast<int*>(p1);
int* i2 = static_cast<int*>(p2);
имеем p1 == p2
, однако i1
корректно определен, и использование i2
приведет к UB.
Ответы
Ответ 1
По-видимому, существуют существующие реализации, которые оптимизируются на основе этого. Рассмотрим:
struct A {
double x[4];
int n;
};
void g(double* p);
int f() {
A a { {}, 42 };
g(&a.x[1]);
return a.n; // optimized to return 42;
// valid only if you can't validly obtain &a.n from &a.x[1]
}
Учитывая p = &a.x[1];
, g
может попытаться получить доступ к a.n
на reinterpret_cast<A*>(reinterpret_cast<double(*)[4]>(p - 1))->n
. Если внутренняя броска успешно дала указатель на a.x
, тогда внешняя трансляция даст указатель на a
, что даст определенному члену доступ к классу и, таким образом, запретит оптимизацию.
Ответ 2
В более общем смысле, что является обоснованием для того, чтобы отличить понятие взаимно-обратимости указателя от понятия того же адреса?
Трудно, если не невозможно ответить, почему определенные решения принимаются стандартом, но это мой прием.
Логически указатели указывают на объекты, а не на адреса. Адресами являются представления значений указателей. Это различие особенно важно при повторном использовании пространства объекта, содержащего const
членов
struct S {
const int i;
};
S s = {42};
auto ps = &s;
new (ps) S{420};
foo(ps->i); // UB, requires std::launder
Чтобы указатель с таким же представлением значения мог использоваться так, как если бы он был одним и тем же указателем, следует рассматривать как частный случай, а не наоборот.
Практически стандарт старается как можно меньше ограничить реализацию. Интерконвертируемость указателя - это условие, что указатели могут быть reinterpret_cast
и давать правильный результат. Увидев, как reinterpret_cast
предназначен для компиляции в ничто, это также означает, что указатели имеют одинаковое представление значений. Поскольку это налагает больше ограничений на реализации, условие не будет дано без веских причин.
Ответ 3
Поскольку комитет хочет дать понять, что массив представляет собой концепцию низкого уровня, а не объект первого класса: вы не можете вернуть массив и не назначать ему, например. Интерконвертируемость указателя подразумевает концепцию между объектами одного уровня: только стандартные классы макета или объединения.
Концепция редко используется во всей черновике: в [expr.static.cast], где она появляется как особый случай, в [class.mem], где отмечается, что для стандартных классов макета указывается объект и его первый подобъект является взаимозависимым, в [class.union], где указатели на объединение и его нестатические элементы данных также объявляются взаимообратимыми и в [ptr.launder].
Это последнее событие отделяет 2 варианта использования: либо указатели являются взаимообратимыми, либо один элемент является массивом. Это указано в замечании, а не в примечании, как в [basic.compound], поэтому он делает более понятным, что указатель-взаимная конвертация охотно не касается массивов.