If you like, you can allocate a big buffer and manage the memory yourself.
If you do so, make sure not to use structs, or if you do, learn about "byte padding" or "byte alignment", since it will make each struct occupy more space than it "needs" to.
Once you have raw allocated memory (with malloc, std::vector or using an array, for example), you need to "tightly store" your data.
Here's some code:
char array[5000] = { 0 }; // or char* array = malloc(5000);
// Let's say you want to store several "char, int" structs
const int structSize = sizeof(char) + sizeof(int);
*(char*)&array[0 * structSize] = 'a';
*(int*)&array[0 * structSize + sizeof(char)] = 1;
*(char*)&array[1 * structSize] = 'b';
*(int*)&array[1 * structSize + sizeof(char)] = 2;
*(char*)&array[2 * structSize] = 'c';
*(int*)&array[2 * structSize + sizeof(char)] = 3;
WARNING: In the code above, if you are storing structs/classes rather than raw types (char, int...), make sure to use "placement new" on the memory location.
If, instead, you use a "struct { char, int }", its size will be 8 bytes (on most computers, not all), 3 of which are padding (unused memory).
Please note that, while you gain in memory usage, you lose in cpu efficiency (that is what the point of padding is).
Alternatively, see this article for alternatives that tell the compiler not to do the padding (using "#pragma pack").
#pragma pack(1)
struct nopadding
{
char first;
int second;
};
sizeof(nopadding); // <- is now equal to 5 instead of 8