5

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...)

SoniEx2
  • 1,864
  • 3
  • 27
  • 40
  • 3
    Not sure what you're trying to do here. What is the tag intended to provide? – Steve Summit Jul 01 '18 at 17:12
  • 1
    @SteveSummit it's a tagged pointer. it provides a bit of data as part of the pointer, as opposed to being part of the struct. – SoniEx2 Jul 01 '18 at 17:14
  • 2
    look portable for me but I see no usecase, (`(&(x)[-*(x)])` is unreadable something like `((x) - *(x))` look better, and we see that this code look very strange now) – Stargateur Jul 01 '18 at 17:15
  • 5
    I'm not clear about your implementation, but I don't see any tag being stored in the pointer. [Tagged pointer](https://en.wikipedia.org/wiki/Tagged_pointer) means some bits of the pointer itself is used for tagging. You only have data and tag in the struct and then points to the struct. And even if you store the pointer in the `data` field then obviously it's not portable because pointers are not `int`. They're only convertible to `(u)intptr_t` – phuclv Jul 01 '18 at 17:16
  • 2
    @SoniEx2 Okay, but I really don't see you storing any extra data as part of a pointer. (It's a popular technique, but I believe it's *always* nonportable, because it's quite machine- and system-dependent whether there are any bits in a pointer that are guaranteed to be unused, such that you can "steal" them for your own use without ill effect.) – Steve Summit Jul 01 '18 at 17:20
  • some examples: [Portable tagged pointers](https://stackoverflow.com/q/47501033/995714), https://stackoverflow.com/q/20315714/995714, https://stackoverflow.com/q/16198700/995714 – phuclv Jul 01 '18 at 17:20
  • "_is char * to struct * conversion portable_" implicitly? Or did your mind intended a cast to be there that your fingers forgot to type? – curiousguy Jul 02 '18 at 07:06
  • @curiousguy I thought casts weren't needed when using assignments? – SoniEx2 Jul 02 '18 at 11:07

1 Answers1

2

#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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • My idea is that `TAG` and `UNTAG` should work for any taggable struct, so I guess you'd add the casts when using `UNTAG` (not in `UNTAG` itself)... `(tagged int *)(char (*)[2])UNTAG(x)` I guess? – SoniEx2 Jul 01 '18 at 17:31