UB при разыменовании массива объединений

Какие из них являются неопределенным поведением:

template <class T> struct Struct { T t; };

template <class T> union Union { T t; };

template <class T> void function() {
  Struct aS[10];
  Union aU[10];

  // do something with aS[9].t and aU[9].t including initialization

  T *aSP = reinterpret_cast<T *>(aS);
  T *aUP = reinterpret_cast<T *>(aU);

  // so here is this undefined behaviour?
  T valueS = aSP[9];
  // use valueS in whatever way

  // so here is this undefined behaviour?
  T valueU = aUP[9];
  // use valueU in whatever way

  // now is accessing aS[9].t or aU[9].t now UB?
}

Так что да, какая из последних 3 операций является UB?

(Мои рассуждения: я не знаю о структуре, если есть какое-либо требование, чтобы ее размер был таким же, как и ее единственный элемент, но, насколько я знаю, объединение должно быть того же размера, что и элемент. Требования к выравниванию Я не знаю для союза, но я предполагаю, что это одно и то же. Для структуры я понятия не имею. В случае союза я бы предположил, что это не UB, но, как я уже сказал, я действительно действительно не уверен. структура, которую я на самом деле понятия не имею)

Что ты считает UB, почему и почему вы не уверены?

Scott Hunter 23.04.2019 18:03

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

Lightness Races in Orbit 23.04.2019 18:03

См. также stackoverflow.com/a/25377970/560648 и en.cppreference.com/w/cpp/types/is_standard_layout. А что такое T? Вы не создавали никаких T, но в зависимости от того, что такое T, это может не иметь значения... но вы должны предоставить всю необходимую информацию и контекст.

Lightness Races in Orbit 23.04.2019 18:05

Как указано, обе последние 2 операции являются UB.

Eljay 23.04.2019 19:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
233
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я нашел c++ - Является ли sizeof (T) == sizeof (int). Это указывает, что структуры не обязательно должны иметь тот же размер, что и их элементы (вздох). Что касается союзов, то, вероятно, применимо то же самое (после прочтения ответов я пришел к выводу, что так). Это само по себе необходимо, чтобы сделать эту ситуацию UB. Однако, если sizeof(Struct) == sizeof(T) и «Установлено, что» в https://stackoverflow.com/a/21515546, указатель на aSP[9] будет в том же месте, что и у aS[9] (по крайней мере, я так думаю), и переинтерпретация, которая гарантируется по стандарту (согласно цитате в https://stackoverflow.com/a/21509729).

Обновлено: Это на самом деле неправильно. Правильный ответ: здесь.

Итак, если мы посмотрим на документ reinterpret_cast (здесь)

5) Any object pointer type T1* can be converted to another object pointer type cv T2*. This is exactly equivalent to static_cast(static_cast(expression)) (which implies that if T2's alignment requirement is not stricter than T1's, the value of the pointer does not change and conversion of the resulting pointer back to its original type yields the original value). In any case, the resulting pointer may only be dereferenced safely if allowed by the type aliasing rules (see below)

Теперь что говорят правила псевдонимов?

Whenever an attempt is made to read or modify the stored value of an object of type DynamicType through a glvalue of type AliasedType, the behavior is undefined unless one of the following is true:

  1. AliasedType and DynamicType are similar.
  2. AliasedType is the (possibly cv-qualified) signed or unsigned variant of DynamicType.
  3. AliasedType is std::byte, (since C++17)char, or unsigned char: this permits examination of the object representation of any object as an array of bytes.

Так что это не 2 и не 3. Может быть 1?

Аналогичный:

Informally, two types are similar if, ignoring top-level cv-qualification:

  1. they are the same type; or
  2. they are both pointers, and the pointed-to types are similar; or
  3. they are both pointers to member of the same class, and the types of the pointed-to members are similar; or
  4. they are both arrays of the same size or both arrays of unknown bound, and the array element types are similar.

И, из черновика C++17:

Two objects a and b are pointer-interconvertible if:

  • they are the same object, or
  • one is a union object and the other is a non-static data member of that object ([class.union]), or
  • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, any base class subobject of that object ([class.mem]), or
  • there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.

If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a reinterpret_­cast. [ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note]

Итак, для меня:

T *aSP = reinterpret_cast<T *>(aS); // Is OK
T *aUP = reinterpret_cast<T *>(aU); // Is OK. 
Ответ принят как подходящий

Вкратце: последние два оператора в приведенном выше коде всегда будут вызывать неопределенное поведение, простое приведение указателя на объединение к указателю на один из его типов членов, как правило, нормально, потому что на самом деле оно ничего не делает (в худшем случае оно не указано). , но никогда не неопределенное поведение; примечание: мы говорим только о самом приведении, использование результата приведения для доступа к объекту — это совсем другая история).


В зависимости от того, чем в конечном итоге будет T, Struct<T> потенциально может быть структурой стандартного макета [класс.проп]/3, и в этом случае

T *aSP = reinterpret_cast<T *>(aS);

будет хорошо определен, потому что Struct<T> будет взаимопреобразовываться указателем со своим первым членом (который имеет тип T) [базовый.состав]/4.3. Выше reinterpret_cast эквивалентно [expr.reinterpret.cast]/7

T *aSP = static_cast<T *>(static_cast<void *>(aS));

который вызовет преобразование массива в указатель [конв.массив], в результате чего Struct<T>* укажет на первый элемент aS. Затем этот указатель преобразуется в void* (через [expr.static.cast]/4 и [конв.ptr]/2), который затем преобразуется в T*, что допустимо через [expr.static.cast]/13:

A prvalue of type “pointer to cv1void” can be converted to a prvalue of type “pointer to cv2T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

По аналогии,

T *aUP = reinterpret_cast<T *>(aU);

будет четко определен в C++ 17, если Union<T> является объединением стандартной компоновки и в целом выглядит четко определенным в будущей версии C++, основанной на текущем стандартном черновике, где объединение и один из его членов всегда взаимопреобразуемый указатель [базовый.состав]/4.2

Однако все вышеперечисленное не имеет значения, поскольку

T valueS = aSP[9];

а также

T valueU = aUP[9];

будет вызывать неопределенное поведение, несмотря ни на что. aSP[9] и aUP[9] (по определению) такие же, как *(aSP + 9) и *(aUP + 9) соответственно [expr.sub]/1. Арифметика указателя в этих выражениях подчиняется [расшир.доб.]/4.

When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P.

  • If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value.
  • Otherwise, if P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0≤i+j≤n and the expression P - J points to the (possibly-hypothetical) element x[i−j] if 0≤i−j≤n.
  • Otherwise, the behavior is undefined.

aSP и aUP не указывают на элемент массива. Даже если aSP и aUP будут взаимопреобразовывать указатели с T, вам будет разрешен доступ только к элементу 0 и вычисление адреса (но не доступа) к элементу 1 гипотетического одноэлементного массива…

Я не понимаю [basic.compound]/4.2, как ты. Для меня здесь у нас есть указатель на объединение и указатель на тот же тип, что и элемент объединения. Не «один является объектом объединения, а другой является нестатическим элементом данных этого объединения». Хотя может ошибся

Martin Morterol 07.05.2019 15:43

@MartinMorterol Я не уверен, что ты имеешь в виду. Объединение (стандартного макета) и один из его элементов, взаимопреобразующий указатель, просто означает (через [expr.static.cast]/13, как указано выше), что результат приведения указателя к единице к типу «указатель на тип другого" будет допустимым значением указателя, указывающим на другой объект...

Michael Kenzel 07.05.2019 16:03

Хорошо, спасибо, я понял. Но я не понимаю, почему вы говорите: «aUP не указывает на элемент массива». Из [conv.array] «Результатом является указатель на первый элемент массива». Итак, aUP должен указывать на элемент массива?

Martin Morterol 08.05.2019 13:43

@MartinMorterol результат преобразования массива в указатель на aS и aU указывает на объект, который является первым элементом массива. Но результат reinterpret_cast будет (в лучшем случае) указывать на объект, который является подобъектом (членом) первого объекта, и этот подобъект не является элементом массива…

Michael Kenzel 08.05.2019 13:51

Так правда ли, что T valueS = aSP[0] — это неопределенное поведение?

Leonid 10.05.2019 04:25

@ Леонид да, и T valueU = aUP[9]; тоже.

Michael Kenzel 10.05.2019 10:32

Другие вопросы по теме