I have a struct variable of struct:
typedef struct message_t
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message;
message msg;
msg.time = 0x1234;
msg.lat = 0x2122;
msg.ns = 'n';
msg.lon = 0x2234;
msg.ew = 'e';
uint8_t msg_arr[8];
How to convert msg
to uint8_t array msg_arr[8]
?
By using memcpy to convert the data,
memcpy(msg_arr, &msg, sizeof(msg_arr));
for(int i=0; i < 8; i ){
printf("msg[%d]: %d \n", i, msg_arr[i]);
}
I get the output:
msg[0]: 52
msg[1]: 18
msg[2]: 34
msg[3]: 33
msg[4]: 110
msg[5]: 0
msg[6]: 52
msg[7]: 34
It seems something wrong with using memcpy. for example, msg[5], msg[6] and msg[7]
are not correct.
CodePudding user response:
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!
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("0xX", 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("0xX", 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("0xX", 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]
Keywords: c type punning; c struct to byte array conversion
CodePudding user response:
Answer 2/3: convert a struct to an array of bytes via manual bit-shifting
Convert a struct to an array in C via bit shifting, which unlike the union technique, does NOT have architecture-based endianness concerns or differences which can vary from hardware architecture to architecture! Rather, the manual bit-shifting removes endianness concerns at the expense of manually copying the bytes from the struct into an array (whereas the union and raw ptr techniques do NOT require copying any bytes)!
This technique being endianness-agnostic makes it better than the other two approaches (union and raw ptr) if you ever need to send serialized data between different systems of different endianness.
struct_to_array_via_bit_shifting.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()`
// Read a single byte at index `byte_num` from any multi-byte variable.
#define BYTE(value, byte_num) ((uint8_t)(((value) >> (8*(byte_num))) & 0xff))
// Notice with this technique the struct does NOT need to be packed! This is contrary to the
// union technique.
typedef struct message_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_t;
void message_struct_to_array(const message_t* message, uint8_t* bytes)
{
bytes[0] = BYTE(message->time, 0);
bytes[1] = BYTE(message->time, 1);
bytes[2] = BYTE(message->lat, 0);
bytes[3] = BYTE(message->lat, 1);
bytes[4] = BYTE(message->ns, 0);
bytes[5] = BYTE(message->lon, 0);
bytes[6] = BYTE(message->lon, 1);
bytes[7] = BYTE(message->ew, 0);
}
// int main(int argc, char *argv[]) // alternative prototype
int main()
{
printf("Answer 2/3: convert a struct to an array of bytes via manual bit-shifting.\n");
message_t message =
{
.time = 0x1234,
.lat = 0x2122,
.ns = 'n', // 0x6E
.lon = 0x1834,
.ew = 'e', // 0x65
};
// NB: this is NOT the same thing as `sizeof(message_t)` due to padding bytes which are in the
// `message_t` struct!
const size_t MESSAGE_NUM_BYTES = sizeof(message.time) sizeof(message.lat)
sizeof(message.ns) sizeof(message.lon) sizeof(message.ew);
uint8_t bytes[MESSAGE_NUM_BYTES];
message_struct_to_array(&message, bytes);
// Print the bytes
printf("bytes = [");
for (size_t i = 0; i < sizeof(bytes); i )
{
printf("0xX", bytes[i]);
if (i < sizeof(bytes) - 1)
{
printf(", ");
}
}
printf("]\n");
return 0;
}
Sample output:
eRCaGuy_hello_world/c$ mkdir -p bin && gcc -Wall -Wextra -Werror -O3 -std=c11 -save-temps=obj struct_to_array_via_bit_shifting.c \ > -o bin/struct_to_array_via_bit_shifting && bin/struct_to_array_via_bit_shifting Answer 2/3: convert a struct to an array of bytes via manual bit-shifting. bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]
CodePudding user response:
Answer 3/3: use a packed struct and a raw uint8_t
pointer to it
Convert a struct to an array in C via a raw pointer without doing any copies of the data, such as into an array via memcpy or via the bitshifting approach. This also has architecture endianness concerns, just like using a union.
The concept is super simple though. Just pack the struct and then look at it as though it was an array of bytes!--like this:
// Use a raw pointer
uint8_t * bytes = (uint8_t*)(&message);
It's essentially an explicit, manual union, since you're just looking at the data "through another lens", as I like to call it, rather than actually copying, manipulating, or changing the data.
Full example:
struct_to_array_via_type_punning_raw_pointer.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_unpacked_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_unpacked_t;
// This MUST be packed to work properly without having padding bytes in the byte array!
typedef struct __attribute__ ((__packed__)) message_s
{
uint16_t time;
uint16_t lat;
uint8_t ns;
uint16_t lon;
uint8_t ew;
} message_t;
// int main(int argc, char *argv[]) // alternative prototype
int main()
{
printf("Answer 3/3: use a packed struct and a raw `uint8_t` pointer to it`.\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
};
// Use a raw pointer
uint8_t * bytes = (uint8_t*)(&message);
// Print the bytes
printf("bytes = [");
for (size_t i = 0; i < sizeof(bytes); i )
{
printf("0xX", bytes[i]);
if (i < sizeof(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_raw_pointer.c \
-o bin/struct_to_array_via_type_punning_raw_pointer && bin/struct_to_array_via_type_punning_raw_pointer
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_raw_pointer.c -o bin/struct_to_array_via_type_punning_raw_pointer && bin/struct_to_array_via_type_punning_raw_pointer Answer 3/3: use a packed struct and a raw `uint8_t` pointer to it`. sizeof(message_unpacked_t) = 10 bytes sizeof(message_t) = 8 bytes bytes = [0x34, 0x12, 0x22, 0x21, 0x6E, 0x34, 0x18, 0x65]