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)