Cyan4973 / Writing_Safer_C_code

Collection of articles on good practices and tools to improve C code quality
BSD 2-Clause "Simplified" License
281 stars 21 forks source link

Safer Dynamic Memory Allocation #2

Open ldo opened 2 years ago

ldo commented 2 years ago

Your essay covers some interesting points, but I notice it skirts the issue of managing dynamic objects. There are times when you have no choice about this. For example, I deal a lot with shareable libraries that offer their services through opaque objects. Since different versions of the libraries might have entirely different sizes and internal layouts of these objects, it is not practicable to let the caller allocate them on the stack; they have to go on the heap.

And then you have the well-known complications of checking for errors on such allocations, and ensuring that everything is correctly cleaned up on both success and failure code paths. All too often I see a rat’s nest of gotos to deal with this, when really the simplest solution is to avoid gotos altogether.

More explanation (with examples) here: https://github.com/ldo/a_structured_discipline_of_programming

Cyan4973 commented 2 years ago

Yes, this topic is not addressed in this repository. A guiding principle within this repository is : how can we leverage the compiler to help us detect programming errors ?

Clearly, it doesn't cover all types of programming errors. Only those that the compiler can safely warn us against.

Sane programming practices, which are very helpful to mitigate presence and improve detection of programming errors, do not necessarily leverage the compiler, and are more dependent on the programmer and reviewer to properly follow a method or pattern. I believe this is what you present in your essay.

I believe it's a reasonably good pattern. If followed strictly, it should lead to better control over risks of memory leaks.

There are 2 comments that come to mind :

This second question is probably the more important one. I would suggest that having a good pattern doesn't protect from mistakes, and therefore it's important to have good processes in place to cover them.

For situations that definitely happen when the pattern is violated, such as memory leak, I recommend to run standard tests under a memory sanitizer or a valgrind VM. They should both quickly report a leak issue if there is one. For situation that could happen theoretically, but will likely not, such as a memory allocation failure, this is more tricky. The first line of defense would be a static analyzer, which is likely going to detect the most obvious risks of undefined accesses. More complex, one could try to substitute the memory allocator by a fake one, designed to fail on purpose from time to time, in order to stress that part of the software. However, now we are entering the realm of complex fuzzer tests, so I don't think it would fit in a short essay.

ldo commented 2 years ago

The answer is “Nassi-Shneiderman all the way”. That means no early returns.

The thing with this pattern is, violations should stand out like a sore thumb and be easy to spot.