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

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

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

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

79 голосов | спросил Oliv 2 Jpm1000000pmTue, 02 Jan 2018 17:00:35 +030018 2018, 17:00:35

3 ответа


0
  

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

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

В новом [basic.compound] /3 говорится:

  

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

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

У нас также есть ненормативная справка:

  

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

, который разъясняет намерение.


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

ответил Barry 2 Jpm1000000pmTue, 02 Jan 2018 17:14:14 +030018 2018, 17:14:14
0

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

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

  

3 Если полный объект создан (8.3.4) в хранилище, связанном с другим объектом e типа «массив N   unsigned char »или типа« массив из 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; // 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) ...

Примечание Указатель за концом ... здесь не применяется, поскольку объекты, на которые указывает p1 и p2, а не не связанные , но вложенные в один и тот же завершенный объект, поэтому арифметика указателей имеет смысл внутри объекта, который обеспечивает хранение: p2 - p1 определен и является (&buffer[sizeof(int)] - buffer]) / sizeof(int) это 1.

Так что p1 + 1 является указателем на *p2 и*(p1 + 1) = 10; определило поведение и задает значение *p2.


Я также прочитал приложение C4 о совместимости между C ++ 14 и текущими (C ++ 17) стандартами. Удаление возможности использовать арифметику указателей между объектами, динамически создаваемыми в одном символьном массиве, было бы важным изменением, которое ИМХО следует приводить там, потому что это часто используемая функция. Так как на страницах совместимости ничего об этом не существует, я думаю, что это подтверждает, что стандарт не намеревался запрещать это.

В частности, было бы побеждено это общее динамическое построение массива объектов из класса без конструктора по умолчанию:

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 может затем использоваться как указатель на первый элемент массива ...

ответил Serge Ballesta 2 Jpm1000000pmTue, 02 Jan 2018 20:40:47 +030018 2018, 20:40:47
0

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

Предупреждение. Неопределенное поведение

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

ответил Persixty 3 Jpm1000000pmWed, 03 Jan 2018 14:23:38 +030018 2018, 14:23:38

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132