Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate version ID of struct definition?

Tags:

c

gcc

clang

Basically, what I want is some kind of compile-time generated version that is associated with the exact definition of a struct. If the definition of the struct changes in any way (field added, moved, maybe renamed), I want that version to change, too.

Such a version constant would be useful when reading in a previously serialized struct, to make sure that it's still compatible. The alternative would be manually keeping track of a manually specified constant, which has potentially confusing effects if incrementing it is forgotten (deserializing produces garbage), and also raises the question when exactly to increment it (during development and testing, or only during some kind of release).

This could be achieved by using an external tool to generate a hash over the struct definition, but I'm wondering if it is possible with the C compiler (and/or maybe its preprocessor) itself.

This is actually some form of introspection and so I suspect that this may not be possible at all in ANSI C, but I would be happy with a solution that works with gcc and clang.

like image 604
Julien Oster Avatar asked Oct 08 '22 09:10

Julien Oster


2 Answers

The Windows API used to (still does?) have a size member as one of the first members of a struct, so that it knew what version of the struct it was being passed (see WNDCLASSEX as an example):

struct Foo
{
    size_t size;
    char *bar;
    char *baz;
    /* Other fields */
};

And before calling you set the size using sizeof:

struct Foo f;

f.size = sizeof(struct Foo);
f.bar = strdup("hi");
f.baz = strdup("there");

somefunc(&f);

Then somefunc would know, based on the size member, which version of the struct it was dealing with. Because sizeof is evaluated at compile time instead of run-time, this allows for backwards ABI compatibility.

like image 170
Sean Bright Avatar answered Oct 13 '22 12:10

Sean Bright


There is nothing that would do it automatically, but you can build something that works reasonably reliably: you can use sizeof and offsetof, and combine them in such a way that the order in which you combine them mattered. Here is an example:

#include <stdio.h>
#include <stddef.h>

#define COMBINE2(a,b) ((a)*31+(b)*11)
#define COMBINE3(a,b,c) COMBINE2(COMBINE2(a,b),c)
#define COMBINE4(a,b,c,d) COMBINE2(COMBINE3(a,b,c),d)

typedef struct A {
    int a1;
    char a2;
    float a3;
} A;

typedef struct B {
    int b1;
    char b2;
    double b3;
} B;

typedef struct C {
    char c2;
    int c1;
    float c3;
} C;

typedef struct D {
    int d1;
    char d2;
    float d3;
    int forgotten[2];
} D;

int main(void) {
    size_t aSign = COMBINE4(sizeof(A), offsetof(A,a1), offsetof(A,a2), offsetof(A,a3));
    size_t bSign = COMBINE4(sizeof(B), offsetof(B,b1), offsetof(B,b2), offsetof(B,b3));
    size_t cSign = COMBINE4(sizeof(C), offsetof(C,c1), offsetof(C,c2), offsetof(C,c3));
    size_t dSign = COMBINE4(sizeof(D), offsetof(D,d1), offsetof(D,d2), offsetof(D,d3));
    printf("%ld %ld %ld %ld", aSign, bSign, cSign, dSign);
    return 0;
}

This code prints

358944 478108 399864 597272

As you can see, this code produces run-time constants for each structure that reacts to re-ordering of fields of different lengths and changing fields' types. It also reacts to adding fields even if you forget to update the list of fields on which you base your computation, which should produce some sort of a safety net.

like image 42
Sergey Kalinichenko Avatar answered Oct 13 '22 12:10

Sergey Kalinichenko