Предполагая, что у меня есть такой союз
union buffer {
struct { T* data; int count; int capacity; };
struct { void* data; int count; int capacity; } __type_erased;
};
Не возникнут ли у меня проблемы, если я смешаю чтение/запись с анонимными членами структуры и членами __type_erased в соответствии с правилами псевдонимов C11?
В частности, меня интересует поведение, которое происходит, если доступ к компонентам осуществляется независимо (например, через разные указатели). Проиллюстрировать:
grow_buffer(&buffer.__type_erased);
buffer.data[buffer.count] = ...
Я прочитал все соответствующие вопросы, которые смог найти, но я до сих пор не на 100% разобрался в этом, поскольку некоторые люди, кажется, предполагают, что такое поведение не определено, в то время как другие говорят, что оно законно. Кроме того, информация, которую я нахожу, представляет собой смесь правил C++, C99, C11 и т. д., которые довольно трудно переварить. Здесь меня явно интересует поведение, предписанное C11 и демонстрируемое популярными компиляторами (Clang, GCC).
Изменить: больше информации
Сейчас я провел несколько экспериментов с несколькими компиляторами и решил поделиться своими выводами на случай, если кто-то столкнется с похожей проблемой. Предыстория моего вопроса заключается в том, что я пытался написать удобную высокопроизводительную универсальную реализацию динамического массива на простом C. Идея состоит в том, что операция с массивом выполняется с использованием макросов, а тяжелые операции (например, увеличение массива) выполняется с использованием структуры шаблона со стиранием типа с псевдонимом. Например, у меня может быть такой макрос:
#define ALLOC_ONE(A)\
(_array_ensure_size(&A.__type_erased, A.count+1), A.count++)
который при необходимости увеличивает массив и возвращает индекс вновь выделенного элемента. В спецификации (6.5.2.3) указано, что доступ к одному и тому же местоположению через разных членов союза разрешен. Моя интерпретация этого заключается в том, что, хотя _array_ensure_size() не знает о типе объединения, компилятор должен знать, что член __type_erased потенциально может быть изменен побочным эффектом. То есть я бы предположил, что это должно работать. Однако кажется, что это серая зона (и, честно говоря, в спецификации действительно не ясно, что представляет собой доступ к членам). Последний Clang от Apple (clang-800.0.33.1) не имеет с ним проблем. Код компилируется без предупреждений и работает как положено. Однако при компиляции с помощью GCC 5.3.0 код вылетает с ошибкой сегментации. На самом деле, у меня есть сильное подозрение, что поведение GCC является ошибкой — я попытался сделать мутацию члена объединения явной, удалив изменяемый указатель ref и приняв четкий функциональный стиль, например:
#define ALLOC_ONE(A) \
(A.__type_erased = _array_ensure_size(A.__type_erased, A.count+1),\
A.count++)
Это снова работает с Clang, как и ожидалось, но снова приводит к сбою GCC. Мой вывод состоит в том, что продвинутая манипуляция типами с объединениями — это серая зона, в которой следует действовать осторожно.