Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safer Dynamic Memory Allocation #2

Open
ldo opened this issue Dec 29, 2021 · 2 comments
Open

Safer Dynamic Memory Allocation #2

ldo opened this issue Dec 29, 2021 · 2 comments

Comments

@ldo
Copy link

ldo commented Dec 29, 2021

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
Copy link
Owner

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 :

  • I'm personally a big supporter of early returns. They general help simplify expression in many functions. But this comes in direct conflict with the suggested pattern, since it's absolutely required to pass through the defined end section to ensure proper cleaning.
    • To solve this situation, I personally like the nested pattern to answer that point, with an "outer" function which is charge of allocating / freeing resources, and an "inner" function which works with pre-validated resources. The inner function can have as many early return as it wishes, it will always come back at the same location in the outer function, which can then proceed on the cleaning section which can't be skipped.
    • There are drawbacks though, and the situation becomes more difficult when there are too many arguments to pass to the inner function. When that happens, it's required to simplify the expression, typically by grouping resources in a single "object", thus shortening the function signature.
  • What happens when the pattern is not followed properly ? For example, an early return is added later on, into the inside block, by another programmer unaware of the pattern ?

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
Copy link
Author

ldo commented Dec 29, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants