Подтвердить что ты не робот

Интерконвертируемость указателя против того же адреса

рабочий проект стандарта 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.

4b9b3361

Ответ 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], поэтому он делает более понятным, что указатель-взаимная конвертация охотно не касается массивов.