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


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

до стандарта C++17, следующее предложение было включено в [basic.соединение]/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 это указатель прошлое-в-конец, так это не указатель на объект и я считаю, что это не уникальным.

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

3 77

3 ответа:

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

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

новая [basic.соединение]/3 говорит:

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

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

мы также имеем ненормативное Примечание:

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

что проясняет намерение.


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

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

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

3 Если полный объект создан (8.3.4) в хранилище, связанном с другой объект e типа " массив N unsigned char "или типа" array of N std:: byte " (21.2.1), этот массив обеспечивает хранение для созданного объект, если:
(3.1) - время жизни е началось и не закончилось, а
(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; // a.a provides storage for *b
int *p = new (b->b + 4) int; // b->b provides storage for *p
// a.a 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) - 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)...

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

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

Внимание: Неопределенное Поведение

#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]". Это не может быть хорошо и, конечно, не намерение.