Это стандартная практика в Си? Предполагается, что я должен memcpy
в U32 при хранении других типов данных?
Это зависит от того, что вы подразумеваете под "этим". Преобразование указателей объектов назад и вперед между типами void *
и другими типами указателей объектов является довольно распространенным явлением, и оно служит разумным целям (а иногда и необоснованным). С другой стороны, совсем не типично поддерживать постоянный пул памяти, в который вы записываете и читаете значения различного типа.
Но если вы хотите что-то, что служит плоским пулом примитивов, то объединение массивов, вероятно, будет лучшим выбором:
#define WORD_COUNT 1024
union {
int8_t as_schars[WORD_COUNT * 4];
uint8_t as_uchars[WORD_COUNT * 4];
int16_t as_sshorts[WORD_COUNT * 2];
uint16_t as_ushorts[WORD_COUNT * 2];
int32_t as_sints[WORD_COUNT];
uint32_t as_uints[WORD_COUNT];
float as_floats[WORD_COUNT]; // assumes 32-bit floats
} primitives;
Затем вы можете обращаться к элементам напрямую, без вызовов функций, с помощью выражений, таких как
uint8_t a_byte = primitives.as_uchars[42];
int16_t a_short = primitives.as_sshorts[222];
float f = primitives.as_floats[665];
primitives.as_uints[1] = 12;
У вас также нет риска неопределенного поведения, возникающего при наведении указателей вперед и назад и разыменовании результатов.
В качестве альтернативы, если ваше хранилище разделено на 32-разрядные блоки, у вас есть пара других основанных на объединении альтернатив, основанных на объединении для отдельных модулей, таких как:
union primitive {
int8_t as_schar;
uint8_t as_uchar;
int16_t as_sshort;
uint16_t as_ushort;
int32_t as_sint;
uint32_t as_uint;
float as_float; // assumes 32-bit floats
};
При условии, что вы можете гарантировать, что у такого объединения нет отступов, вы можете использовать его в качестве базового типа для хранения и обмена данными:
union primitive prim = /* ... */;
uint8_t a_byte = prim.as_uchar;
int16_t a_short = prim.as_sshort;
float f = prim.as_float;
Если вы не можете предположить или гарантировать, что такое объединение имеет правильный размер хранилища, то, вероятно, все же лучше использовать его для хранения и восстановления данных, чем использовать memcpy
:
int16_t read_s16(uint32_t *storage_somewhere) {
return ((union primitive) { .as_uint = *storage_somewhere }).as_sshort;
}
void write_float(uint32_t *storage_somewhere, float data) {
*storage_somewhere = ((union primitive) { .as_float = data }).as_uint;
}
Эти альтернативы дают компилятору как можно более ясное представление о том, что вы делаете, и с большей вероятностью он сгенерирует оптимальный код для этого, чем для случая memcpy
.