Answer 1/3: use a union and a packed struct
See also my much longer answer here: Portability of using union for conversion
You can do the conversion to a byte array using a union. Be sure to pack the struct to remove padding bytes.
typedef struct __attribute__ ((__packed__)) message_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_t;
typedef union message_converter_u
{
message_t message;
uint8_t bytes[sizeof(message_t)];
} message_converter_t;
Now do the conversion through the message_converter_t
union:
message_t message =
{
.time = 0x1234,
.lat = 0x2122,
.ns = 'n', // 0x6E
.lon = 0x1834,
.ew = 'e', // 0x65
};
message_converter_t converter;
converter.message = message;
That's it!
converter.bytes
is now magically a uint8_t
array with 8 elements containing all the bytes of the struct.
It has endianness considerations, however!
Note: copying message
into converter.message
above is unnecessarily inefficient, since it's an unnecessary byte-for-byte copy of the whole message
struct. A more-efficient way is to simply construct the union type alone and populate the struct data inside the union directly. See "struct_to_array_via_type_punning_union_more_efficient.c" below for that demo.
Here is some sample print code to print all the bytes:
// Print the bytes
printf("bytes = [");
for (size_t i = 0; i < sizeof(converter.bytes); i++)
{
printf("0x%02X", converter.bytes[i]);
if (i < sizeof(converter.bytes) - 1)
{
printf(", ");
}
}
printf("]\n");
and the output on a 64-bit little-endian x86-architecture Linux machine:
bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]
Notice that due to my machine being little-endian, the least-significant-byte 0x34
comes first in the time
variable of 0x1234
. So, you get 0x34
and then 0x12
. This happens with all of the multi-byte variables. To remove endianness considerations across hardware architectures, you'd have to move to a bit-shifting approach instead of using a union--see my link above for examples and more details, and also see my Answer 2/3 here.
Full, runnable example
struct_to_array_via_type_punning_union.c: <-- download it as part of my eRCaGuy_hello_world repo
#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h> // For `uint8_t`, `int8_t`, etc.
#include <stdio.h> // For `printf()`
typedef struct message_unpacked_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_unpacked_t;
typedef struct __attribute__ ((__packed__)) message_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_t;
typedef union message_converter_u
{
message_t message;
uint8_t bytes[sizeof(message_t)];
} message_converter_t;
// int main(int argc, char *argv[]) // alternative prototype
int main()
{
printf("This is the start of `main()`.\n");
// demonstrate that packing the struct matters
printf("sizeof(message_unpacked_t) = %zu bytes\n", sizeof(message_unpacked_t)); // 10 bytes due to padding
printf("sizeof(message_t) = %zu bytes\n", sizeof(message_t)); // 8 bytes
message_t message =
{
.time = 0x1234,
.lat = 0x2122,
.ns = 'n', // 0x6E
.lon = 0x1834,
.ew = 'e', // 0x65
};
message_converter_t converter;
// Note: copying `message` into `converter.message` here is unnecessarily inefficient. A
// more-efficient way is to simply construct the union type alone and populate the struct
// data inside the union directly. See "struct_to_array_via_type_punning_union_more_efficient.c"
// for that demo.
converter.message = message;
// Print the bytes
printf("bytes = [");
for (size_t i = 0; i < sizeof(converter.bytes); i++)
{
printf("0x%02X", converter.bytes[i]);
if (i < sizeof(converter.bytes) - 1)
{
printf(", ");
}
}
printf("]\n");
return 0;
}
Build and run command:
mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj struct_to_array_via_type_punning_union.c \
-o bin/struct_to_array_via_type_punning_union && bin/struct_to_array_via_type_punning_union
Sample output:
eRCaGuy_hello_world/c$ mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj struct_to_array_via_type_punning_union.c \
> -o bin/struct_to_array_via_type_punning_union && bin/struct_to_array_via_type_punning_union
This is the start of `main()`.
sizeof(message_unpacked_t) = 10 bytes
sizeof(message_t) = 8 bytes
bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]
More-efficient technique: do NOT copy from a struct to a union! Just use the union alone as your message_t
!
struct_to_array_via_type_punning_union_more_efficient.c
#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h> // For `uint8_t`, `int8_t`, etc.
#include <stdio.h> // For `printf()`
typedef struct message_data_unpacked_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_data_unpacked_t;
typedef struct __attribute__ ((__packed__)) message_data_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_data_t;
typedef union message_u
{
message_data_t data;
uint8_t bytes[sizeof(message_data_t)];
} message_t;
// int main(int argc, char *argv[]) // alternative prototype
int main()
{
printf("This is the start of `main()`.\n");
// demonstrate that packing the struct matters
printf("sizeof(message_data_unpacked_t) = %zu bytes\n", sizeof(message_data_unpacked_t)); // 10 bytes due to padding
printf("sizeof(message_data_t) = %zu bytes\n", sizeof(message_data_t)); // 8 bytes
message_t message =
{
.data =
{
.time = 0x1234,
.lat = 0x2122,
.ns = 'n', // 0x6E
.lon = 0x1834,
.ew = 'e', // 0x65
},
};
// Print the bytes
printf("bytes = [");
for (size_t i = 0; i < sizeof(message.bytes); i++)
{
printf("0x%02X", message.bytes[i]);
if (i < sizeof(message.bytes) - 1)
{
printf(", ");
}
}
printf("]\n");
return 0;
}
Sample output is the same as before:
eRCaGuy_hello_world/c$ mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj struct_to_array_via_type_punning_union_more_efficient.c \
> -o bin/struct_to_array_via_type_punning_union_more_efficient && bin/struct_to_array_via_type_punning_union_more_efficient
This is the start of `main()`.
sizeof(message_data_unpacked_t) = 10 bytes
sizeof(message_data_t) = 8 bytes
bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]
Related
Here is one of my kind of related answers in C++, which I want to remember in case I need to look at it again: Use a char[4] or char[8] as a constant int?
Keywords: c type punning; c struct to byte array conversion; c array to bytes; c serialization; c struct serialization; c object serialization