Skip to content




Programming »

Use Macro to Print a Struct with its field

C/C++ does not support to print out struct's field name. This is a guide showing how to print struct field and value using macros.

Last update: 2022-06-04


Table of Content

Problem#

Here is a struct in C++:

typedef struct Person_t {
    string name;
    unsigned int age;
    Person_t(string _name, unsigned int _age) {
        name = _name;
        age = _age;
    }
} Person_t;

To print out its field name and value:

cout << "Person_t person_a" << endl
        << "    " << "string name = " << person_a.name << endl
        << "    " << "unsigned int age = " << person_a.age << endl
        << endl;

To print another object, juts copy, paste, and replace the object person_a, change the field name, and the text?

How to use a shorter form of printing? How to apply on different structure types?

Macros#

Define STRUCT macro to print out the structure types, name of the instance, and struct fields:

#define STRUCT(type, pInstance, ... )   \
    {                                   \
        cout << #type << " "            \
             << #pInstance              \
             << endl;                   \
        type* pStr = pInstance;         \
        __VA_ARGS__                     \
        cout << endl;                   \
    }

Each field of struct will be wrapped in the FIELD macro:

#define FIELD(type, name)               \
    {                                   \
        cout << "    "                  \
             << #type << " "            \
             << #name << " = "          \
             << pStr->name              \
             << endl;                   \
    }

And at this point, we can use macro to print out a struct:

int main() {
    Person_t person_a("Human", 100);
    STRUCT(Person_t,  &person_a,
        FIELD(string, name);
        FIELD(unsigned int, age);
    );
}

If we have another struct, the macros still can be used:

typedef struct Shape_t {
    string name;
    unsigned int edges;
    double area;
    Shape_t(string _name, unsigned int _edges) {
        name = _name;
        edges = _edges;
    }
} Shape_t;

int main() {
    Shape_t shape_a("square", 4);
    STRUCT(Shape_t,  &shape_a,
        FIELD(string, name);
        FIELD(unsigned int, edges);
        FIELD(double, area);
    );
}

We still be able to shorten the code, if we define a macro which wraps up all field of a struct:

#define FIELDS_PERSON                   \
    FIELD(string, name);                \
    FIELD(unsigned int, age);

And then, the printing macro is just as short as below:

int main() {
    Person_t person_a("Human", 100);
     STRUCT(Person_t,  &person_a, FIELDS_PERSON);
}

Example#

Here is an example of printing EEPROM data. EEPROM contains multiple structures, and we need to print out all filed and data in bytes of each structure.

First, the STRUCT macro print the name, create a pointer, print out all fields, and check CRC data:

#define STRUCT(type, offset, ...)                                   \
    {                                                               \
        cout << #offset << ": " << offset << endl;                  \
        cout << #type << ": " << endl;                              \
        type *pStr = (type*) &pEEPROM[offset];                      \
        __VA_ARGS__                                                 \
        if (pStr->crc8                                              \
            == crc_engine.crc8(                                     \
                (unsigned char *)pStr, sizeof(type) - 1))           \
            cout << "    CRC OK!";                                  \
        else                                                        \
            cout << ">>> CRC ERROR! <<<";                           \
        cout << endl;                                               \
        cout << endl;                                               \
    }

A macro is defined to return another macro, depending on the number of argument:

#define GET_FIELD(_1, _2, _3, NAME,...) NAME
#define FIELD(...)                                                  \
    GET_FIELD(__VA_ARGS__,                                          \
        FIELD_ARRAY,                                                \
        FIELD_SINGLE                                                \
    )(__VA_ARGS__)

When we use FIELD(x, y),
it will expand to GET_FIELD(x, y, FIELD_ARRAY, FIELD_SINGLE, ...)(x, y),
and finally is replaced to FIELD_SINGLE(x, y).

When we use FIELD(x, y, z),
it will expand to GET_FIELD(x, y, z, FIELD_ARRAY, FIELD_SINGLE, ...)(x, y, z),
and finally is replaced to FIELD_ARRAY(x, y, z).

Finally, we define the macros to process data defend on field types:

#define FIELD_ARRAY(type, array, count)                             \
    {                                                               \
        cout << "    " << #type << " ";                             \
        cout << #array << "[" << dec << count << "] = ";            \
        unsigned char *pArr = (unsigned char*)&pStr->array;         \
        for (int i=0; i<(count*sizeof(type)); i++)                  \
            cout << hex(pArr[i]) << " ";                            \
        cout << endl;                                               \
    }

#define FIELD_SINGLE(type, name)                                    \
    {                                                               \
        cout << "    " << #type << " ";                             \
        cout << #name << " = ";                                     \
        unsigned char *pArr = (unsigned char*)&pStr->name;          \
        for (int i=0; i<sizeof(type); i++)                          \
            cout << hex(pArr[i]) << " ";                            \
        cout << endl;                                               \
    }

Example of usage:

typedef struct NVM_Data_t
{
    unsigned char   lastMode[MAX_LAST_MODE];
    unsigned short  id;
    unsigned int    offTime;
    unsigned char   exception;
    unsigned char   reserved[1];
    unsigned char   crc8;
} NVM_Data_t;


volatile unsigned char *pEEPROM;

STRUCT(Mode_NVMLast_t, NVM_EEP_MODE_OFFSET,
    FIELD(unsigned char, lastMode, MAX_LAST_MODE);
    FIELD(unsigned short, id);
    FIELD(unsigned int, offTime);
    FIELD(unsigned char, reserved, 1);
    FIELD(unsigned char, crc8);
);

Comments