In C, I want to place a char
id at the very end of a struct so that I can discern the struct type from a pointer to the end of the struct (allocated dynamically). Obviously, the possibility of padding at the end makes this difficult. I thought of two approaches.
The first approach is to place an array of chars that extends all the way to the end of the struct so that (char*)ptr_to_end - 1
always point to a valid char. I think this should work if the compiler is not doing any funny business. Otherwise, it should fail to compile:
typedef struct
{
int foo;
int bar;
char type;
} MyStructDummy;
typedef struct
{
int foo;
int bar;
char type[ sizeof( MyStructDummy ) - offsetof( MyStructDummy, type ) ];
} MyStruct;
_Static_assert(
sizeof( MyStruct ) == sizeof( MyStructDummy ),
"Could not ensure char at end of MyStruct"
);
The second approach is to use offsetof
to always access the malloc-ed bloc as individual (member) variables and never as a complete struct. That way, we avoid ever imparting the struct's type as an effective type over the whole block or accidentally changing padding values:
typedef struct
{
int foo;
int bar;
char type;
} MyStruct;
int *MyStruct_foo( void *end_ptr )
{
return (int*)( (char*)end_ptr - sizeof( MyStruct ) + offsetof( MyStruct, foo ) );
}
int *MyStruct_bar( void *end_ptr )
{
return (int*)( (char*)end_ptr - sizeof( MyStruct ) + offsetof( MyStruct, bar ) );
}
char *MyStruct_type( void *end_ptr )
{
return (char*)end_ptr - 1;
}
Is either of these approaches preferable to the other? Is there an existing C idiom that achieves what I want to achieve (I can't use a flexible array member because I want to maintain C++ compatability)?
Thanks!
EDIT:
Karl asked how placing an id at the end of a struct could be useful. Consider this memory-conserving implementation of a dynamic array/vector:
//VecHdr is for vectors without an automatic element destructor function
//and whose capacity is < UINT_MAX
typedef struct
{
alignas( max_align_t )
unsigned int size;
unsigned int cap;
char type_and_flags; //At very end
} VecHdr; //Probable size: 16 bytes
//VecHdr is for vectors with an element destructor or whose capacity is >= UINT_MAX
typedef struct
{
alignas( max_align_t )
size_t size;
size_t cap;
void (*element_destructor_func)( void* );
char type_and_flags; //At very end
} VecHdrEx; //Probable size: 32 bytes
//...
int *foo = vec_create( int );
//This macro returns a pointer to a malloced block of ints, preceded by a VecHdr
int *bar = vec_create_ex( int, my_element_destructor );
//This macro returns a pointer to malloced block of ints, preceded by a VecHdrEx
vec_push( foo, 12345 );
//vec_push knows that foo is preceded by a VecHdr by checking (char*)foo - 1
//foo's VecHdr may eventually be replaced with a VecHdrEx if we add enough elements
vec_push( bar, 12345 );
//vec_push knows that bar is preceded by a VecHdrEx by checking (char*)foo - 1