Имеет ли доступ к массиву структуры POD как массив его одного члена, нарушает строгий псевдоним?

У меня есть целочисленные значения, которые используются для доступа к данным в несвязанных хранилищах данных, то есть к дескрипторам. Я решил обернуть целые числа в структуре, чтобы иметь строго типизированные объекты, чтобы разные целые числа не могли быть перемешаны. Они, и должны быть, POD. Это то, что я использую:

struct Mesh {
    int handle;
};
struct Texture {
    int handle;
};

У меня есть массивы этих ручек, например: Texture* textureHandles;.

Иногда мне нужно передать массив дескрипторов как int* в более общие части кода. Сейчас я использую:

int* handles = &textureHandles->handle;

который по существу принимает указатель на первый элемент структуры и интерпретирует его как массив.

Мой вопрос в основном, если это законно, или если он нарушает строгий псевдоним, чтобы манипулировать int* handles и Texture* textureHandles, указывая на одну и ту же память. Я думаю, что это должно быть разрешено, поскольку в обоих случаях доступ к базовому типу (int) осуществляется одинаково. Оговорка, которую я имею, связана с тем, что я обращаюсь к нескольким структурам, беря адрес члена внутри одной структуры.

Как расширение моего первого вопроса, было бы хорошо?

int* handles = reinterpret_cast<int*>(textureHandles);

Ответ 1

reinterpret_cast<int*>(textureHandles) определенно так же хорош, как &textureHandles->handle. В стандарте есть исключительное исключение, унаследованное от C even, в котором говорится, что указатель на структуру стандартного макета, соответствующим образом преобразованный, указывает на начальный элемент этой структуры и наоборот.

Использование этого для изменения дескриптора также прекрасное. Это не нарушает правила псевдонимов, потому что вы используете lvalue типа int для изменения под-объекта типа int.

Приращение результирующего указателя и использование его для доступа к другим элементам в массиве объектов Texture, однако, немного немного. Джерри Коффин уже указывал, что возможно, что sizeof(Texture) > sizeof(int). Даже если sizeof(Texture) == sizeof(int), хотя, арифметика указателя определяется только для указателей на массивы (где произвольный объект может рассматриваться как массив длины 1). У вас нет массива int в любом месте, поэтому добавление просто undefined.

Ответ 2

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

Тем не менее, с конструкцией только одного элемента (типа int или чего-то не менее значительного, например long), вероятность того, что большинство компиляторов не будет вставлять какие-либо дополнения, текущее использование, вероятно, довольно безопасно как общее правило.

Ответ 3

Это, безусловно, нарушает строгий псевдоним, и если функция может получить доступ массив как через int*, так и Mesh* или Texture*, вы можете очень хорошо сталкиваются с проблемами (хотя, вероятно, только в том случае, если он изменяет массив каким-то образом).

Из вашего описания проблемы, я не думаю, что правила строгое сглаживание - это то, что вас интересует. Реальная проблема заключается в том, может ли компилятор добавлять дополнения к структурам, которые не являются присутствует в int, так что sizeof( Mesh ) > sizeof( int ). А также в то время как ответ формально да, я не могу представить компилятор, который сделайте это, по крайней мере, сегодня и, по крайней мере, с int или более крупными типами в struct. (Аппарат, адресованный словами, вероятно, добавит дополнение к struct, который содержал только char.)

Реальный вопрос, вероятно, заключается в том, является ли общий код наследие и не может быть изменено или нет. В противном случае очевидное решение - создать общий тип дескриптора:

struct Handle
{
    int handle;
};

а затем либо вывести из него свои типы, либо использовать reinterpret_cast как вы предлагаете. Существует (или, по крайней мере, была) гарантия, которая позволила доступ к элементу struct через указатель на другой struct, если член и все предыдущие члены были идентичны. Вот как вы имитируете наследование на C. И даже если гарантия был удален и единственная причина, по которой он когда-либо присутствовал в С++ был из-за совместимости с C; компилятор не осмелился нарушить он, учитывая количество существующего программного обеспечения, которое зависит от него. (The например, реализация Python. И практически все Python плагины, в том числе написанные на С++.)