Guess what! it has nothing to do with OOP emulation. All cleanup in a C program has to be done explicitly by the developer, be it coded in object-oriented, functional, imperative or whatever the paradigm-of-the-week is.
Furthermore, the way kernel devs use goto for cleanup is one of the ways that actually keeps the code simpler and less prone to errors. There are a few alternatives to do cleanup in complex code:
- Don't do error checks. In your average C101 homework it's often assumed that many syscalls cannot fail. Obviously, the absolute minimum required in a real world program is a simple assert that stops the program before it can misbehave. Any real-world program that cannot lose data or crash the whole system such as office programs, databases and kernels obviously cannot do this.
- Do an early return and clean up all data so far. Very error prone as the cleanup code has to be replicated in multiple places.
- if/do-while(0)-break wrapping of code. Easy as long as there is one tier of resources to allocate. Any more and the function becomes arrow code.
- Wrap the constructors and destructors in a macro to emulate RAII. The result is still arrowish, although the cleanup is harder to fail. Not allowed in many projects due to an undeserved loathing of macros.
- Split the code into multiple functions. Now you have to pass around the state in function parameters. Having the code split into multiple functions makes the it more prone to logic errors.
- Use goto to jump to the cleanup section. All relevant code is kept in the same function and the code between alllocations and frees stays flat and concise.