Нарушает ли доступ к массиву структуры 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);

person rasmus    schedule 15.02.2015    source источник
comment
Вы хотите использовать структуры для получения сильных типов, а затем хотите отбросить тип, чтобы получить int. Вы получаете худшее из обоих миров.   -  person Neil Kirk    schedule 15.02.2015
comment
@NeilKirk Только очень специфические функции будут использовать необработанные массивы int *. Остальные будут использовать типизированные структуры. Они просто есть, чтобы избежать ошибок при использовании ручек в общем случае.   -  person rasmus    schedule 15.02.2015
comment
Я думаю, вам следует рассказать нам больше о вашем реальном проекте, так как ваш дизайн очень странный.   -  person Neil Kirk    schedule 15.02.2015
comment
@NeilKirk Не уверен, почему вы считаете использование дескрипторов странным. Объяснение всего дизайна может занять некоторое время, но оно исходит из дизайна, ориентированного на данные, где разные системы/менеджеры хранят данные, доступ к которым можно получить с помощью дескрипторов. Эти дескрипторы могут быть простыми целыми числами, но упрощают передачу неправильных дескрипторов не той системе/менеджеру. Я, вероятно, смогу избежать описанного преобразования, если это необходимо, поэтому я и задал вопрос.   -  person rasmus    schedule 15.02.2015
comment
Например, возможно, вы могли бы добавить operator int() к своим дескрипторам, чтобы избежать повторных интерпретаций.   -  person Neil Kirk    schedule 15.02.2015
comment
@Neil Kirk Предположим, он использует OpenGL (вероятно, то же самое с Direct3D). Использование сильных типов поможет вам, но, в конце концов, вам все равно придется передать свой int (или массив int) в API. Нежелательно копировать массив Texture в массив int, если они бинарно идентичны.   -  person sbabbi    schedule 15.02.2015
comment
@NeilKirk Что в этом странного? Почти то же самое я делаю во всех своих интерфейсах между языками, когда объект присутствует в коде на одном языке, но к нему нужно обращаться из другого языка. Сгенерированные swig интерфейсы делают примерно то же самое. (Тип дескриптора может различаться: мой объявлен как void*, а IIRC, интерфейс swig Java использует long long. Но идея та же: объект представлен дескриптором, который является своего рода волшебным файлом cookie, который позволяет найти объект.)   -  person James Kanze    schedule 16.02.2015
comment
Хорошо, я исправлен!   -  person Neil Kirk    schedule 16.02.2015


Ответы (3)


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

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

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

person Community    schedule 15.02.2015
comment
Вы уверены, что это не нарушает правила псевдонимов? Что, если я запишу элемент с помощью указателя int, а затем прочитаю член с помощью указателя Texture? - person Neil Kirk; 15.02.2015
comment
@NeilKirk Все еще хорошо. У вас есть lvalue типа Texture для объекта типа Texture и lvalue типа int для подобъекта типа int. Там нет проблемы с псевдонимом. - person ; 15.02.2015

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

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

person Jerry Coffin    schedule 15.02.2015
comment
Вы не обсуждаете строгие псевдонимы, которые, я думаю, будут здесь проблемой, но я не эксперт. - person Neil Kirk; 15.02.2015
comment
Для этого вопроса меня интересует только конкретный случай, когда структура содержит один элемент. Действительно ли компилятору разрешено вставлять отступы в этом случае? - person rasmus; 15.02.2015
comment
@rasmus: Да, это так. Он не может вставлять отступ перед элементом, но может после него. - person Jerry Coffin; 15.02.2015
comment
@NeilKirk: В этом случае я не вижу реальной причины обсуждать строгие правила псевдонимов. Момент, который я поднял, достаточен, чтобы ответить на вопрос. - person Jerry Coffin; 15.02.2015
comment
Спасибо за ответ. Сначала я принял это, но после некоторого размышления решил пойти с ответом hvd, поскольку в нем также обсуждается строгое псевдоним, о котором я специально спрашивал. Иногда мне хочется принять два ответа. По крайней мере, ты получил мой +1 - person rasmus; 15.02.2015
comment
@rasmus: ИМО, спрашивать о строгом псевдониме в этой ситуации немного похоже на высказывание; если я собираюсь идти ночью по известному криминальному району с крупной суммой денег, какого цвета носки мне надеть? Вместо белого или черного правильный ответ — просто не делайте этого. - person Jerry Coffin; 15.02.2015
comment
@JerryCoffin Да, я полностью это понимаю. Я просто чувствовал, что упоминание об этом даст более полный ответ на вопрос из-за того, как был поставлен вопрос. Или, возможно, объясните, почему это не имеет значения. Лично я нашел полезной дополнительную информацию в ответе hvd, связанную со строгим псевдонимом. Это также может помочь определить достоверность метода, если компилятор действительно гарантирует, что для этой одночленной структуры не будет дополнительного заполнения. Например, MSVC гарантирует это. Но, как я уже сказал, я очень благодарен за ваше время и ответ. - person rasmus; 16.02.2015

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

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

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

struct Handle
{
    int handle;
};

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

person James Kanze    schedule 15.02.2015
comment
Он не нарушает строгое использование псевдонимов для доступа к объектам или подобъектам, определенным как int, через lvalue типа int, и я действительно не понимаю, как в противном случае вы могли бы даже привести законный аргумент. Не могли бы вы уточнить? (Гарантия, которую вы указываете, на самом деле не гарантирует того, что вы думаете, даже в C. Она работает только в объединениях. Здесь помогает другая гарантия, что указатель на структуру стандартного макета указывает на ее начальный член.) - person ; 15.02.2015
comment
Примечание. Я бы полностью согласился с вашим ответом, если бы все было наоборот. Учитывая произвольный объект int, пытаться получить к нему доступ, как если бы это был Handle, не очень хорошая идея, независимо от того, находится ли он в массиве. Но это не то, о чем спрашивает ОП. - person ; 15.02.2015
comment
Никакая часть кода не является устаревшей. Рассматриваемый код используется для сопоставления дескрипторов с данными и имеет возможность вставлять сразу несколько дескрипторов в карту (отсюда и массивы int). Но с информацией из этих замечательных ответов я, вероятно, переосмыслю эту часть дизайна. Проблема с подклассом Handle будет заключаться в том, что типизированные дескрипторы больше не будут POD. Что для меня требование. - person rasmus; 15.02.2015
comment
@hvd У вас есть интересный момент. Согласно стандарту, компилятор может предположить, что int* и Texture* не являются псевдонимами. Но компилятор должен предположить, что int* может быть псевдонимом с p->handle (где p — это Texture*). Что касается гарантии, распространяющейся только на союзы: формально вы можете быть правы (я не смог найти фактический текст с этой гарантией, когда публиковал), но на практике большое количество кода (например, Python) зависит от его работы при приведении указатели тоже. (Вероятно, есть ограничения.) - person James Kanze; 16.02.2015
comment
@JamesKanze И, если я правильно помню, Python строится на GCC с -fno-strict-aliasing, потому что GCC оптимизирует способы, которые не работают для Python. - person ; 16.02.2015