Тип punning и союзы в C

В настоящее время я работаю над проектом по созданию небольшого компилятора только для этого.

Я решил использовать подход к созданию чрезвычайно простой виртуальной машины для таргетинга, поэтому мне не нужно беспокоиться о том, как изучать все элементы эльфа, сборку intel и т. Д.

Мой вопрос касается типа punning в C с использованием союзов. Я решил поддерживать только 32-битные целые числа и 32-битные значения float в памяти vm. Чтобы облегчить это, «основная память» vm настраивается следующим образом:

typedef union { int i; float f; }word; memory = (word *)malloc(mem_size * sizeof(word)); 

Поэтому я могу просто обрабатывать секцию памяти как int или float в зависимости от инструкции.

Это технически печатается? Конечно, было бы, если бы я использовал ints как слова памяти, а затем использовал float *, чтобы рассматривать их как float. Мой нынешний подход, хотя и синтаксически иной, я не думаю, что он семантически отличается. В конце концов я все еще обрабатываю 32 бита в памяти как int или float.

Единственная информация, которую я могу найти в Интернете, предполагает, что это зависит от реализации. Есть ли более переносимый способ добиться этого, не тратя пустую часть пространства?

Я мог бы сделать следующее, но тогда я бы взял в 2 раза больше памяти и «изобретал колесо» в отношении профсоюзов.

 typedef struct { int i; float f; char is_int; } 

редактировать

Я, возможно, не уточнил свой точный вопрос. Я знаю, что я могу использовать либо float, либо int из объединения без неопределенного поведения. То, что мне нужно, – это, в частности, способ иметь 32-битную ячейку памяти, которую я могу безопасно использовать в качестве int или float, не зная, что такое последнее значение. Я хочу учитывать ситуацию, когда используется другой тип.

Да, хранение одного члена объединения и чтение другого – это пинирование типа (при условии, что типы достаточно разные). Более того, это единственный тип универсального типа (любого типа для любого типа), который официально поддерживается языком C. Это подтверждается в определенном смысле, что язык обещает, что в этом случае будет фактически выполняться пинтинг типа, т. Е. Произойдет физическая попытка прочитать объект одного типа как объект другого типа. Среди прочего это означает, что запись одного члена профсоюза и чтение другого члена подразумевает зависимость данных между записью и чтением. Это, однако, все еще оставляет вам бремя обеспечения того, чтобы тип punning не создавал ловушку.

Когда вы используете кастинговые указатели для типа punning (что обычно понимается как «classический» тип punning), язык явно указывает, что в общем случае поведение не определено (кроме переопределения значения объекта как массива char s и других ограниченных случаев) , Компиляторы, такие как GCC, реализуют так называемую «строгую семантику сглаживания», что в основном означает, что пинтинг на основе указателей может работать не так, как вы ожидаете, что он будет работать. Например, компилятор может (и будет) игнорировать зависимость данных между типами чтения и записи и записывать их произвольно, тем самым полностью разрушая ваши намерения. это

 int i; float f; i = 5; f = *(float *) &i; 

может быть легко перестроена в

 f = *(float *) &i; i = 5; 

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

В современном компиляторе C, когда вам действительно нужно выполнить физическую переинтерпретацию значения одного объекта как значение другого типа, вы ограничены либо memcpy байтами от одного объекта к другому, либо пингом типа объединения. Других путей нет. Указатели каста больше не являются жизнеспособным вариантом.

Пока вы получаете доступ только к члену ( int или float ), который был недавно сохранен, нет проблем и нет реальной зависимости от реализации. Это абсолютно безопасно и четко определено для хранения значения в члене объединения, а затем чтение этого же элемента.

(Имейте в виду, что нет никакой гарантии, что int и float имеют одинаковый размер, хотя они присутствуют в каждой системе, которую я видел.)

Если вы сохраняете значение в одном члене, а затем читаете другое, это тип punning. Обозначение сноски в последнем проекте C11:

Если элемент, используемый для чтения содержимого объекта объединения, не совпадает с элементом, используемым последним для хранения значения в объекте, соответствующая часть представления объекта значения переинтерпретируется как представление объекта в новом типе как описанный в 6.2.6 (процесс, иногда называемый «пингом типа»). Это может быть ловушечное представление.