Let’s say you want to allocate a struct that contains a pointer to
other data that also needs allocation. The natural way to do it is
calling malloc twice, one for the container and one for the data.
/* The natural way to allocate a struct and its data */structsample_type{floatA;uint32_tB;uint8_tC;boolD;boolE;boolF;boolG;};structsample_struct{uint32_tCount;sample_type*Data;};uint32_tCount=5;sample_struct*Container=(sample_struct*)malloc(sizeof(sample_struct));Container->Count=Count;Container->Data=(sample_type*)malloc(sizeof(sample_type)*Count);// Do something to the data
for(uint32_ti=0;i<Container->Count;i++){Container->Data[i]={};}free(Container->Data);free(Container);
Something doesn’t feel right. These two allocations are going to be
used together, one after the other, but there is no guarantee they
will be next to each other in memory. On top of that you have two
calls to malloc that can fail, two calls to free you need to remember,
and twice the allocator overhead.
Here is one way to use just a single allocation for the container and
its data.
1
2
3
4
5
6
7
8
9
10
11
12
13
/* Using a single allocation */uint32_tCount=5;sample_struct*Container=(sample_struct*)malloc(sizeof(sample_struct)+sizeof(sample_type)*Count);Container->Count=Count;Container->Data=(sample_type*)(Container+1);// Do something to the data
for(uint32_ti=0;i<Container->Count;i++){Container->Data[i]={};}free(Container);// Frees both the container and the data
If you ask me the second version is better: a single call to malloc,
a single call to free, and things that belong together are guaranteed
to be adjacent in memory.
Watch out for alignment
There is something you need to be aware of while using this trick.
Container + 1 points to the first byte after the struct, and that
address has to be properly aligned for sample_type. In this example
it works out, sizeof(sample_struct) is 16 on a 64 bit machine and
sample_type only needs 4 byte alignment. But if sample_type ever
needs a stricter alignment than the container (a SIMD type with
alignas(16) for example) you’ll end up with a misaligned pointer and
nobody is going to warn you about it.
The safe way is to round the offset up to the alignment of the data.
/* Single allocation with the data properly aligned */// The alignment sample_type needs, in bytes. Alignments are always a
// power of two (1, 2, 4, 8, 16...).
size_tAlign=alignof(sample_type);// Offset is where the data will start, measured from the beginning of
// the allocation. It's sizeof(sample_struct) rounded up to the next
// multiple of Align.
//
// Since Align is a power of two, Align - 1 has all the bits below it
// set. For Align = 16, Align - 1 is 0b1111 and ~(Align - 1) is a mask
// with those 4 low bits cleared.
//
// Adding Align - 1 pushes the size past the next multiple of Align
// (unless it's already on one), and the & with the mask clears the low
// bits, rounding it back down to that multiple.
//
// Example with a 20 byte struct and Align = 16:
// 20 + 15 = 35
// 35 & ~15 = 32, the first multiple of 16 that fits the struct.
size_tOffset=(sizeof(sample_struct)+Align-1)&~(Align-1);// One allocation: the struct, any padding needed to reach Offset, and
// then the data. uint8_t* so pointer arithmetic is in bytes.
uint8_t*Memory=(uint8_t*)malloc(Offset+sizeof(sample_type)*Count);// The struct lives at the start of the block.
sample_struct*Container=(sample_struct*)Memory;Container->Count=Count;// The data starts Offset bytes in, which is guaranteed to be aligned.
Container->Data=(sample_type*)(Memory+Offset);
On the other hand, where Data sits inside the struct doesn’t matter.
The array always starts after the whole struct, so you can add members
anywhere you want.
1
2
3
4
5
6
structsample_struct{uint32_tCount;sample_type*Data;floatOther;// Fine, sizeof(sample_struct) grows and the array moves with it.
};
One last note for C++ folks: all of this works because sample_type
is a trivial type. If your type has constructors or destructors you
need to construct each element with placement new and destroy them
yourself before calling free.