Home > OS >  How to convert a struct varible to uint8_t array in C
How to convert a struct varible to uint8_t array in C

Time:11-16

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]
  •  Tags:  
  • c
  • Related