I have written this C code which I assume provides portable tagged pointers:
typedef struct {
char tag[2];
int data;
} tagged_int;
#define TAG(x,y) (&(x)->tag[(y)])
#define UNTAG(x) (&(x)[-*(x)])
int main(void) {
tagged_int myint = {{0,1}, 33};
tagged_int *myptr = &myint;
char *myint_tag_1 = TAG(myptr,1);
char *myint_tag_0 = TAG(myptr,0);
char tag_1 = *myint_tag_1;
char tag_0 = *myint_tag_0;
tagged_int *myint_1 = UNTAG(myint_tag_1);
tagged_int *myint_0 = UNTAG(myint_tag_0);
}
However, I am curious about whether it really is portable.
While the array manipulation parts are portable, is char *
to struct *
conversion portable, assuming the char *
refers to the first field/element in the struct *
? (This outputs compiler warnings sadly, but I guess you'd get those with "normal" tagged pointers anyway...)
#define UNTAG(x) (&(x)[-*(x)])
does not serve as a pointer to tagged_int
; it is a char *
and will not be automatically converted.
If you insert a cast, #define UNTAG(x) ((tagged int *)(&(x)[-*(x)]))
, then this is almost legal per C 2011 (draft N1570) 6.7.2.1 15, “A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa.” An impediment here is that you have a pointer to the first member of the first member (that is, to a char
that is element 0 of the array that is the first member). Depending on how strictly you interpret certain things in the C standard, this might or might not be considered supported (and might be mitigated by using two casts, as in #define UNTAG(x) ((tagged int *)(char (*)[2])(&(x)[-*(x)]))
). In any case, I would expect it to be portable, as a C implementation would have to go out of its way to break this while supporting other accepted C semantics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With