UB При разыменовании массива союзов - PullRequest
1 голос
/ 23 апреля 2019

Какие из этих неопределенных поведений:

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?

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

Ответы [ 3 ]

8 голосов
/ 07 мая 2019

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


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

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

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

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

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

Значение типа «указатель на cv1 void» можетпреобразовать в значение типа «указатель на cv2 T», где T - это тип объекта, а cv2 - такая же квалификация cv, как и выше, или cv-квалификация, чем, CV1 .Если исходное значение указателя представляет адрес A байта в памяти и A не удовлетворяет требованию выравнивания T, то результирующее значение указателя не определено. В противном случае, если исходное значение указателя указывает на объект a, и существует объект b типа T (игнорирующий квалификацию cv), который может быть преобразован в указатель с a, результатом будетуказатель на b.В противном случае значение указателя при преобразовании не изменится.

Аналогично,

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

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

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

T valueS = aSP[9];

и

T valueU = aUP[9];

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

Когда выражение J, имеющее целочисленный тип, добавляется или вычитается из выражения P типа указателя, результат имеет тип P.

  • Если P оценивается как нулевое значение указателя, а J оценивается как 0, результат является нулевым значением указателя.
  • В противном случае, если P указывает на элемент x[i] объекта массива x с n элементами, выражения P + J и J + P (где Jимеет значение j ) указывает на (возможно, гипотетический) элемент x[i+j], если 0≤i + j≤n , а выражение P - J указывает на (возможно, гипотетический)) element x[i−j] if 0≤i − j≤n .
  • В противном случае поведение не определено.

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

4 голосов
/ 07 мая 2019

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

5) Любой тип указателя объекта T1 * может быть преобразован в другой указатель объекта типа cv T2*.Это в точности эквивалентно static_cast (static_cast (expression)) (что подразумевает, что если требование выравнивания T2 не является более строгим, чем T1, значение указателя не изменяется, и преобразование полученного указателя обратно в его исходный тип приводит к исходному значению),В любом случае результирующий указатель может быть разыменован безопасно только в том случае, если это разрешено правилами псевдонимов типов (см. Ниже)

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

Всякий раз, когдаделается попытка прочитать или изменить сохраненное значение объекта типа DynamicType с помощью glvalue типа AliasedType, поведение не определено, если не выполняется одно из следующих условий:

  1. AliasedType и DynamicType похожи.
  2. AliasedType является (возможно, cv-квалифицированным) подписанным или неподписанным вариантом DynamicType.
  3. AliasedType - это std :: byte, (начиная с C ++ 17) char или unsigned char: это позволяет проверить представление объекта любого объекта в виде массива байтов.

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

Подобные:

Неформально два типа похожи, если, игнорируя квалификацию cv верхнего уровня:

  1. они одного типа;или
  2. они оба указатели, а указываемые типы похожи;или
  3. они оба являются указателями на член одного и того же класса, а типы указываемых членов похожи;или
  4. оба являются массивами одинакового размера или массивами с неизвестной границей, а типы элементов массива схожи.

И, из C ++17 осадка :

Два объекта a и b являются взаимозаменяемыми по указателю, если:

  • это один и тот же объект или
  • одинявляется объектом объединения, а другой является нестатическим членом данных этого объекта ([class.union]), или
  • один является объектом класса стандартной компоновки, а другой - первые нестатические данныечлен этого объекта или, если объект не имеет нестатических членов данных, любой подобъект базового класса этого объекта ([class.mem]), или
  • существует объект c такой, что a и cпреобразуются в указатели, а c и b преобразуются в указатели.

Если два объекта преобразуются в указатели, то они имеют одинаковый адрес, и можно получить указатель на один из объектауказатель на другой через reinterpret_cast.[Примечание: объект массива и его первый элемент не являются взаимозаменяемыми по указателю, даже если они имеют один и тот же адрес.- конец примечания]

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

T *aSP = reinterpret_cast<T *>(aS); // Is OK
T *aUP = reinterpret_cast<T *>(aU); // Is OK. 
0 голосов
/ 03 мая 2019

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

РЕДАКТИРОВАТЬ: Это на самом деле неправильно. Правильный ответ здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...