In C, one pattern for implementing generic containers, particularly vectors/dynamic arrays, is to allocate a header containing metadata before the memory that contains the user's actual data. The user's handle to the container is then an appropriately typed pointer to the start of his or her actual data. This pattern is used by Simple Dynamic Strings and stb_ds, for example.
I want to use the same pattern, but I'm concerned about accidentally relying on undefined behavior. In particular, I'm worried about the parts of the Standard that talk about effective types and that prohibit accessing an array out of bounds.
Hence, I was hoping that someone could check two snippets of code that achieve what I want to do in different ways. (Please excuse the void*
casts - they may be superfluous in these examples but will occur in my actual implementation.)
My header struct for a vector is:
typedef struct
{
alignas( max_align_t )
size_t size;
size_t capacity;
} Header;
In the first snippet, I navigate between the header and the user's handle via arithmetic on a pointer of type Header*
:
//Step 1: Allocate memory for header and 100 ints and initialize the header
Header *original_hdr_ptr = malloc( sizeof( Header ) + sizeof( int ) * 100 );
original_hdr_ptr->size = 0;
original_hdr_ptr->capacity = 100;
//Step 2: Get the user's handle - does this invoke any undefined behavior?
int *users_handle = (void*)( original_hdr_ptr + 1 );
users_handle[ 0 ] = 12345; //Example use by user
//Step 3: Recover pointer to header
Header *new_hdr_ptr = (Header*)(void*)users_handle - 1;
//Is new_hdr_ptr now a valid, usable pointer to the header?
//Or have I violated the no-array-access-out-of-bounds requirement?
In the second snippet, I navigate between the header and the user's handle only via arithmetic on char*
pointers:
//Step 1: Allocate memory for header and 100 ints and initialize the header
Header *original_hdr_ptr = malloc( sizeof( Header ) + sizeof( int ) * 100 );
original_hdr_ptr->size = 0;
original_hdr_ptr->capacity = 100;
//Step 2: Get the user's handle, this time via arithmetic on a char pointer
int *users_handle = (void*)( (char*)original_hdr_ptr + sizeof( Header ) ); //Does this invoke any undefined behavior?
users_handle[ 0 ] = 12345; //Example use by user
//Step 3: Recover pointer to header, again via arithmetic on a char pointer
Header *new_hdr_ptr = (Header*)( (char*)(void*)users_handle - sizeof( Header ) );
//Is new_hdr_ptr now a valid, usable pointer to the header?
Does either snippet invoke undefined behavior in C or C++?
Thanks!