0

Say you have a struct in C:

typedef struct ID_Info {
  uint16_t model_number;
  uint16_t serial_number;
  uint16_t firmware_version;
} ;
ID_Info id_info;

Now, say I need to set each uint16 variable in this struct to the values of data received byte by byte. So for example, if I received the following bytes: 0x00, 0x11, 0x22, 0x33, 0x44 and 0x55 in some data array data[], I now need to set the values as follows:

id_info.model_number = data[1]*256 + data[0];     // 0x1100
id_info.serial_number = data[3]*256 + data[2];    // 0x3322;
id_info.firmware_version = data[5]*256 + data[4]; // 0x5544;

This is easy enough to hard code as shown above. However, I'd like to be able to do this without hard-coding values and iteratively if possible. Therefore, if I needed to add a variable to the struct, my code and loop would automatically know I need to iterate for two more bytes (assuming a unit16). So this loop would need to iterate foreach member in the struct. Furthermore, is there a way to infer the variable type to know how many bytes I need? Say I needed to add a uint8, and in this case the code could know I only need one byte.

So maybe the pseudo-code would look something like this:

int i = 0;
foreach(member in id_info)
  if(member is uint8)
    id_info.member = data[i];
    i =+ 1;
  else if (member is uint16)
    id_info.member = data[i] + 256*data[i+1];
    i =+ 2;
  else
    throw error

This way I could easily add and removed struct members without many changes to the code. Thanks in advance for any insight!

7
  • 1
    Use an array instead of a structure. There's no way in C to iterate through structure elements. Commented Jul 7, 2022 at 19:31
  • C doesn't give you access to a list of struct members or anything like that. You might consider just using a single memcpy call to copy all the needed bytes from the byte array into your struct. But then you need to worry about whether the compiler adds padding into the struct (i.e. you might need to add a packed attribute to the struct). You also need to worry about whether your machine is little-endian or big-endian. Commented Jul 7, 2022 at 19:36
  • " I received the following bytes: 0x00, 0x11, 0x22, 0x33, 0x44 and 0x55" - is there an alignment mismatch between what you receive and what you want to populate? Can't you just populate the correct array element directly? Can you use compiler extensions to pack your struct so it fits like a glove as the receiver of one element? Commented Jul 7, 2022 at 19:37
  • 3
    What you're describing is known as Marshaling or Serialization. The simplest solution is to write a couple of functions for each structure to perform the marshaling and demarshaling. That does create a maintenance task, since the functions need to be updated whenever the structure is changed. Trying to avoid that maintenance task leads you down the garden path to all sorts of thorns. So for me, the only two choices are either just do the work to keep things in sync, or write a code generator that outputs the structure definition as well as the two functions. Commented Jul 7, 2022 at 19:51
  • @jackfrost9p, What type is data? Commented Jul 7, 2022 at 20:02

2 Answers 2

1

If it's not a performance issue (your sample data looks like it isn't), instead of a hard-coded structure with C types, you could define a structure where the type information is encoded, perhaps based on an enum, the name information as a string, and that along with a large enough value type.

The enum type might look like this:

typedef enum {
    ui16, ui8
} Type;

One entry could be defined as:

struct entry {
    Type type;
    char *name;
    long value;
};

It is assumed that long is large enough for the largest data type.

A small, self-contained C test program based on your example might then look like the following:

#include <stdio.h>

typedef enum {
    ui16, ui8
} Type;

struct entry {
    Type type;
    char *name;
    long value;
};

struct entry id_info[] = {
    {ui16, "model_number",     0},
    {ui16, "serial_number",    0},
    {ui16, "firmware_version", 0}
};

int main(void) {
    unsigned char data[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};

    int x = 0;
    for (int i = 0; i < sizeof(id_info) / sizeof(id_info[0]); i++) {
        struct entry *current = &id_info[i];
        switch (current->type) {
            case ui8:
                current->value = data[x];
                x++;
                break;
            case ui16:
                current->value = data[x] + 256 * data[x + 1];
                x += 2;
                break;
        }
    }

    //and now print it

    for (int i = 0; i < sizeof(id_info) / sizeof(id_info[0]); i++) {
        struct entry *current = &id_info[i];
        switch (current->type) {
            case ui8:
                printf("uint8_t %s: %02lx\n", current->name, current->value);
                break;
            case ui16:
                printf("uint16_t %s: %04lx\n", current->name, current->value);
                break;
        }
    }
    return 0;
}

The program would produce the following output on the debug console:

uint16_t model_number: 1100
uint16_t serial_number: 3322
uint16_t firmware_version: 5544
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much for the insight!! For now I am going to go with a macro approach since I think it will better fit my application. But I know you were able to more closely match my initial thought and I greatly appreciate you taking the time to help! Thank you again!
0

One way to do this is with preprocessor macros.

With this method, it is easy to add new elements. And, the import/export functions will be automatically updated.

#ifndef NOINC
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#endif

// define all struct members
#define ALLSTRUCT(_cmd) \
    _cmd(uint16_t,"%u",model_number) \
    _cmd(uint16_t,"%u",serial_number) \
    _cmd(uint16_t,"%u",firmware_version)

// define symbol
#define SYMDEF(_typ,_fmt,_sym) \
    _typ _sym;

// define struct
typedef struct ID_Info {
    ALLSTRUCT(SYMDEF)
} ID_Info;

ID_Info id_info;

// deserialize
#define SYMIN(_typ,_fmt,_sym) \
    do { \
        str->_sym = *(_typ *) ptr; \
        ptr += sizeof(_typ); \
    } while (0);

// serialize
#define SYMOUT(_typ,_fmt,_sym) \
    do { \
        *(_typ *) ptr = str->_sym; \
        ptr += sizeof(_typ); \
    } while (0);

// print
#define SYMPRT(_typ,_fmt,_sym) \
    printf("  " #_sym "=" _fmt " (%8.8X)\n",str->_sym,str->_sym);

// struct_out -- output struct to byte array
uint8_t *
struct_out(const ID_Info *str,uint8_t *ptr)
{

    ALLSTRUCT(SYMOUT)

    return ptr;
}

// struct_in -- input struct from byte array
const uint8_t *
struct_in(ID_Info *str,const uint8_t *ptr)
{

    ALLSTRUCT(SYMIN)

    return ptr;
}

// struct_prt -- print struct to byte array
void
struct_prt(const ID_Info *str)
{

    printf("struct_prt:\n");
    ALLSTRUCT(SYMPRT)
}

// prtu8 -- print byte array
void
prtu8(const uint8_t *ptr,size_t count,const char *sym)
{

    printf("%s:",sym);

    for (size_t idx = 0;  idx < count;  ++idx)
        printf(" %2.2X",ptr[idx]);

    printf("\n");
}

int
main(void)
{

    uint8_t data_in[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
    uint8_t data_out[sizeof(data_in)];

    // show original byte array
    prtu8(data_in,sizeof(data_in),"data_in");

    // import data into struct
    struct_in(&id_info,data_in);

    // show struct values
    struct_prt(&id_info);

    // export data from struct
    struct_out(&id_info,data_out);

    // show exported byte array
    prtu8(data_out,sizeof(data_out),"data_out");

    // reimport the struct data
    struct_in(&id_info,data_out);

    // show struct data
    struct_prt(&id_info);

    return 0;
}

Here is the [redacted] preprocessor output:

typedef struct ID_Info {
    uint16_t model_number;
    uint16_t serial_number;
    uint16_t firmware_version;
} ID_Info;
ID_Info id_info;
uint8_t *
struct_out(const ID_Info * str, uint8_t * ptr)
{
    do {
        *(uint16_t *) ptr = str->model_number;
        ptr += sizeof(uint16_t);
    } while (0);
    do {
        *(uint16_t *) ptr = str->serial_number;
        ptr += sizeof(uint16_t);
    } while (0);
    do {
        *(uint16_t *) ptr = str->firmware_version;
        ptr += sizeof(uint16_t);
    } while (0);
    return ptr;
}

const uint8_t *
struct_in(ID_Info * str, const uint8_t * ptr)
{
    do {
        str->model_number = *(uint16_t *) ptr;
        ptr += sizeof(uint16_t);
    } while (0);
    do {
        str->serial_number = *(uint16_t *) ptr;
        ptr += sizeof(uint16_t);
    } while (0);
    do {
        str->firmware_version = *(uint16_t *) ptr;
        ptr += sizeof(uint16_t);
    } while (0);
    return ptr;
}

void
struct_prt(const ID_Info * str)
{
    printf("struct_prt:\n");
    printf("  " "model_number" "=" "%u" " (%8.8X)\n", str->model_number, str->model_number);
    printf("  " "serial_number" "=" "%u" " (%8.8X)\n", str->serial_number, str->serial_number);
    printf("  " "firmware_version" "=" "%u" " (%8.8X)\n", str->firmware_version, str->firmware_version);
}

void
prtu8(const uint8_t * ptr, size_t count, const char *sym)
{
    printf("%s:", sym);
    for (size_t idx = 0; idx < count; ++idx)
        printf(" %2.2X", ptr[idx]);
    printf("\n");
}

int
main(void)
{
    uint8_t data_in[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
    uint8_t data_out[sizeof(data_in)];

    prtu8(data_in, sizeof(data_in), "data_in");
    struct_in(&id_info, data_in);
    struct_prt(&id_info);
    struct_out(&id_info, data_out);
    prtu8(data_out, sizeof(data_out), "data_out");
    struct_in(&id_info, data_out);
    struct_prt(&id_info);
    return 0;
}

Here is the test program output:

data_in: 00 11 22 33 44 55
struct_prt:
  model_number=4352 (00001100)
  serial_number=13090 (00003322)
  firmware_version=21828 (00005544)
data_out: 00 11 22 33 44 55
struct_prt:
  model_number=4352 (00001100)
  serial_number=13090 (00003322)
  firmware_version=21828 (00005544)

2 Comments

Thank you very much! This was not an approach I envisioned at all, but I was able to implement it in my application and its working great! Exactly what I needed. Thank you again!
@jackfrost9p You're welcome! This answer is pretty much code I've used myself in the past, so it's race proven ... The alternative is to have a script [as part of the build] that scans .h files and parses the struct definitions and creates .c files with the dump/restore functions [I've done that as well].

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.