Suppose I have a struct
like:
typedef struct S S;
struct S {
S *next;
// ...
S *gc_next;
};
I.e., it contains multiple "next" pointers to be simultaneously on multiple linked lists. If I want to write a single function that can follow any "next" pointer, I can pass the offset of which "next" pointer I want, e.g.:
void S_free_impl( S *p, size_t next_offset ) {
while ( p ) {
S *next = *PTR_OFFSET( p, S*, next_offset );
free( p );
p = next;
}
}
where PTR_OFFSET
is a macro that, given a pointer to some struct
, the type of the member I want (in this case, S*
), and the offset of the member I want, then dereferencing that pointer, I can get the value of the wanted member.
To get the offset, I can write a front-end macro to call S_free_impl()
that uses offsetof
:
#define S_FREE(PTR,NEXT) S_free_impl( (PTR), offsetof( S, NEXT ) )
then use it like:
S_FREE( p, gc_next ); // free nodes following "gc_next" pointer
An initial implementation of the PTR_OFFSET
macro can be:
#define PTR_OFFSET(PTR,TYPE,OFFSET) \
(TYPE*)((char*)(PTR) + (OFFSET))
However, if you compile, say, using gcc -Wcast-align
, you get:
foo.c:21:24: warning: cast from 'char *' to 'S **' (aka 'struct S **') increases
required alignment from 1 to 8 [-Wcast-align]
S *next = *PTR_OFFSET( p, S*, next_offset );
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One way to suppress the warning, is to write a sort-of C equivalent of C++'s reinterpret_cast
:
#define REINTERPRET_CAST(T,EXPR) ((T)(uintptr_t)(EXPR))
then rewrite PTR_OFFSET
to use it:
#define PTR_OFFSET(PTR,TYPE,OFFSET) \
REINTERPRET_CAST( TYPE*, REINTERPRET_CAST( char*, (PTR) ) + (OFFSET) )
then the warning goes away because of casting through a uintptr_t
.
The question is: is this the best (most portable) way to get what I want in C? Specifically:
- The ability to write functions that operate on different
struct
members (of the same type). - A portable way to use the value returned by
offsetof
to convert it to an actual pointer-to-member.