Home > Back-end >  Dynamically Populate Struct in C
Dynamically Populate Struct in C

Time:07-08

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!

CodePudding user response:

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: lx\n", current->name, current->value);
                break;
            case ui16:
                printf("uint16_t %s: lx\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

CodePudding user response:

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)
  • Related