In the wake of the heartbleed bug, OpenSSL has been rightly critizised for using its own freelist. With plain-old malloc
, the bug would have almost certainly been found long ago.
However, some kind of malloc wrapping is actually very common. For example when I have a big object that owns many small, fixed sized objects, I often allocate them from an array.
/* BigObj is allocated with `malloc`, but LittleObj using `alloc_little`. */
typedef struct {
/* bigObj stuff here */
int nlittle;
LittleObj littles[MAX_LITTLE];
} BigObj;
LittleObj *alloc_little(BigObj *big)
{
if(big->nlittle == MAX_LITTLE)
return NULL;
return = big->littles + big->nlittle++;
}
alloc_little
is faster than malloc()
, but the main reason is simplicity. There is no free_little
to call, because the LittleObjs
just remain valid until the BigObj
is destroyed.
So is this an anti-pattern? If so, can I mitigate the risks, or should I just abandon it? Similar questions go for any kind of memory pool, such as GNU Obstacks and the talloc
used in Samba.
No, absolutely not. And proposing malloc() as the solution is just terrible.
Complex pieces of system software almost always have special memory requirements, and very often the solution involves implementing one or more customer memory allocators. Reusing objects off a free list is one good strategy. Others include memory pools, slab allocators, stack allocators, reference counting and even garbage collection.
C/C++ happens to be an exceptionally fine tool for implementing memory allocators, but it provides very little protection from bad decisions or bad execution. The mistake in OpenSSL was not the decision to use a free list, but doing it so badly.
I would say the main risk mitigation strategy, after making good choices and writing good code, is exhaustive unit testing, including failure modes and performance. Don’t leave home without it.
No.
C is not safe. C is not friendly. C does not catch your errors for you. As you say, using a different allocator might have caught the error sooner – but it would not have prevented it in the first place, at the design stage. The same or a similar error would likely still have been made, and while other strategies might have helped catch it sooner, it would still be after the fact.
The antipattern is in forgetting this and designing code that can potentially overflow or make invalid accesses in the first place, that doesn’t either check what it’s being asked to do or prove it won’t do anything dangerous at compile-time. So there is an antipattern here if you just “don’t track” a LittleObj
: deal with this by being able to prove where their owners are at all times (and that it’s mechanically impossible for the owner of a LittleObj
to outlive its associated pool), not by hoping undefined behaviour will point out a mistake later.
Ruling this sort of technique out as an antipattern makes it less easy to use C for one of its strengths: implementing low-level, unsafe, runtime details so that higher-level languages and systems can be built on top of it (not every GC or other runtime can be built on malloc effectively). If in doubt, better not to be using C for the complex high-level stuff at all – leave it to an altogether safer system rather than trying to hammer C into a tool it isn’t, and let a machine prove or ensure that your program is safe (stricter programming language, restrictive API, machine-checkable spec, etc.). Otherwise what would we implement low-level features in?
The ultimate counterexample is probably the ML Kit, whose C-implemented backend uses regions – a much more advanced form of the same basic idea as pools – combined with the ironclad SML type system to prevent invalid memory accesses.
2
of cause, it’s not an anti-pattern, it’s just one more way to fire in your leg
it can be usefull or can be not usefull, depending on usage scenario
there are a lot of technics to use statically pre-allocate memory (and not use malloc/free) and such technics often use in embedded devices
but, using such technics you should clearly understand will be possible to expose data to another user of your application. for example, if you use pre-allocated memory to dram primitives on screen – it’s generally safe, if you use such memory to return signed (by key stored in some crypto storage) data – you have to wipe data before returning memory record to user.
I mean, then someone calls your alloc_little() you returns some memory and it can contains previous data. If your code doesn’t operate with some sensitive data (it’s really very hard to specify what IS a sensitive data) – you can don’t clean the memory. If you think, data operated inside LittleObj is sensitive – you have to clear this memory before usage.
Moreover, you have to track all allocated memorries, because on can call alloc_little()/free_little() and keep address of struct. and periodically poll it for information.
SO, it’s not an antipattern, it’s veru usefull technic on some cases but you should clearly understand what do you do and who can access this memory.