Psuedo Type Checking in C using Struct
Requirement
Let the C compiler recognize two types different, even when the two are actually equivalent in terms of the size and contents.
Idea
- Wrap each type in a
struct
to add type information, as a compiler recognizes two structs (even with the same size) as different. - Do not actually define dummy structs, but use pointers to them to:
- avoid meaningless coding
- expect that the types are stored in registers for speedup
Example
typedef struct A1* a1; typedef struct A2* a2; // a function that accepts type a1 only void f(a1 p){ } // a function that accepts type a2 only void g(a2 p){ } a1 make_a1(int n){ return (a1)(unsigned long)n; } a2 make_a2(int n){ return (a2)(unsigned long)n; } main(){ a1 p1 = make_a1(0); a2 p2 = make_a2(1); f(p1); g(p2); }
The code above compiles with no relevant warnings.
Actual definitions of A1
and A2
are not needed because creating a pointer to a struct does not require the actual definition of the struct (otherwise, recursive data structures such as linked list cannot be written).
However, once the arguments of f
and g
are flipped by mistake (like f(p2)
and g(p1)
), you get warnings:
typecheck.c: In function 'main': typecheck.c:20:3: warning: passing argument 1 of 'f' from incompatible pointer type [enabled by default] f(p2); ^ typecheck.c:4:6: note: expected 'a1' but argument is of type 'a2' void f(a1 p){ } ^ typecheck.c:21:3: warning: passing argument 1 of 'g' from incompatible pointer type [enabled by default] g(p1); ^ typecheck.c:6:6: note: expected 'a2' but argument is of type 'a1' void g(a2 p){ } ^
An disadvantage of this method compared to actually defining wrapper structs is since there are no definitions of A1 and A2 people reading the code can be confused (I actually was when analyzing QEMU's source code, and I learned this trick from one of the ML entries).
Follow-up (Feb 2017)
This might be cleaner. The difference is that this version does not need never-used names of the structs, but instead it just defines empty structs.
typedef struct {}* a1; // No longer need the name A1 typedef struct {}* a2; // No longer need the name A2 void f(a1 p){ } void g(a2 p){ } a1 make_a1(int n){ return (a1)(unsigned long)n; } a2 make_a2(int n){ return (a2)(unsigned long)n; } main(){ a1 p1 = make_a1(0); a2 p2 = make_a2(1); f(p1); g(p2); }