Not having studied the topic in depth, my first thought is whether true pointers and a garbage collector can coexist (without artificially removing parts of the language). The big condition for knowing if something is still in use is whether any references exist to it. But in a language like C , you could cast a pointer to something else (like a void pointer for some quasi generic linked list), store it somewhere, an…
You don't need to give up pointers, or pointer arithmetic, just the ability to obscure what a pointer points to. Casting to void does not do this (and indeed, has no run time effect at all). Metadata is associated with the pointed-to block of memory, so as long as the address is recognizable as a pointer at runtime, you can do GC. Some of the things that break GC aren't even technically valid C code. For example, say…
Yes, but there's a legal way to do this.
reference the other two areas with offsets relative to the first area
If you cast the pointers to uintptr_t, and perform your arithmetic on uintptr_t and cast your final pointer back (void * ) before using it, what you've done is perfectly legal and safe (albeit weird) since uintptr_t is an unsigned integer type with all the flexibility of unsigned integers, and if x = (uintptr_t)p then it's guaranteed that p == (void * )x.
In other words:
char * x = malloc(100);
char * y = malloc(100);
ptrdiff_t yminusx = y - x;
x[yminusx] = '\0'
is undefined behaviour, but char * x = malloc(100);
char * y = malloc(100);
uintptr_t yminusx = (uintptr_t)(y) - (uintptr_t)(x);
*(char *)(void *)((uintptr_t)(x) + yminusx) = '\0'
is valid C (and has the same meaning on a DWIM compiler).