defer
reference implementation for C
Table of Contents
- 1. Description
- 1.1. An example for static cleanup handling
- 1.2. Dynamic versus static control flow for deferred statements
- 1.3. Guarded blocks for function bodies
- 1.4. Triggering the execution of deferred statements
- 1.5. Execution of deferred statements and panic recovery
- 1.6. Improved usage of certain C library features
- 1.7. Provided interfaces
- 1.8. Error codes
- 1.9. Interoperability with C++
- 1.10. Implementation specifics
- 2. Reference manual
- 2.1. *=defer=: ensure the execution of the deferred statement at the end of the guarded block
- 2.2. *=guard=: mark a whole block as "guarded" and as using the defer mechanism.
- 2.3. *=panic=: Unwind the whole call stack and execute the deferred statements on the way down
- 2.4. Termination functions
- 2.5. *=recover=: stop a panic and/or return such the error code
- 2.6. Signal handlers
- 2.7. Encapsulated storage management functions
- 3. Terms
1 Description
This is a reference implementation of a C
language mechanism
and library-based implementation for error handling and deferred
cleanup adapted from similar features in the Go Programming Language.
This mechanism improves the
proximity – visibility – maintainability – robustness – security
of cleanup and error handling over existing language features.
The intent is to integrate the basic tools that are provided here into
the C
standard by providing a new library clause for a new header with
the proposed name <stddefer.h>
. A description of the general
approach can be found in the following document:
1.1 An example for static cleanup handling
In the following code snippet we have one guarded block and three deferred statements.
guard { void * const p = malloc(25); if (!p) break; defer free(p); void * const q = malloc(25); if (!q) break; defer free(q); if (mtx_lock(&mut)==thrd_error) break; defer mtx_unlock(&mut); // all resources acquired }
The idea is that we indicate with a defer
keyword that the
statement, e.g a call to free
, is only to be executed at the end of
the guarded block, and, that we want this action to happen
unconditionally in which way ever the guarded block is left. For the
three deferred statements together it says that they should be
executed in the inverse order than they were encountered. So the
control flow for this code example can be visualized as follows:
Here, the dashed lines represent circumstancial control flow that might arise when some resources are not available or when the execution is interrupted by a signal.
This new technique has at least two advantages to commonly used C
or
C++
techniques:
- proximity
- The cleanup code (
free
ormtx_unlock
) is coded close to the place where its need arises. - visibility
- The cleanup code is not hidden in some previously
defined function (such as for
atexit
handlers) or constructor/destructor pairs (C++
)
For normal control flow (without intermediate return
, exit
, …)
code with similar properties can be coded with existing tools. The
above is equivalent to something like the following
{ void * const p = malloc(25); if (!p) goto DEFER0; if (false) { DEFER1: free(p); goto DEFER0; } void * const q = malloc(25); if (!q) goto DEFER1; if (false) { DEFER2: free(q); goto DEFER1; } if (mtx_lock(&mut)==thrd_error) goto DEFER2; if (false) { DEFER3: mtx_unlock(&mut); goto DEFER2; } // all resources acquired goto DEFER3; DEFER0:; }
Here, the if(false)
clauses guarantee that the deferred statements
are jumped over when they are first met, and the labels and goto
statements implement the hops from back to front to execute the
deferred statements eventually.
Obviously, most C
programmers would not code like this but they
would prefer to write down a linearization of the above, which is a
quite common idiom for cleanup handling in C
:
{ void * const p = malloc(25); if (!p) goto DEFER0; void*const q = malloc(25); if (!q) goto DEFER1; if (mtx_lock(&mut)==thrd_error) goto DEFER2; // all resources acquired mtx_unlock(&mut); DEFER2: free(q); DEFER1: free(p); DEFER0:; }
This has the advantage of only making the circumstantial control flow explicit (with three *=goto=) but it does that for the price of proximity; the cleanup code is far from the place where its need arises.
Nevertheless, even this linearization needs some form of naming
convention for the labels. For more complicated code the maintenance
of these jumps can be tricky and prone to errors. This shows another
advantage of the defer
approach:
- maintainability
- The cleanup specification is not dependent on
arbitrary naming such as labels (
C
) or RAII classes (C++
) and does not change whendefer
orbreak
statements are added or removed.
Another important property that is much more difficult to implement in
C
(and that needs try/catch
blocks in C++
) is that all exits
from the guarded block are detected and acted upon: break
, return
,
thrd_exit
, exit
, panic
, or
an interruption by a signal. That is, unless there are nasal deamons
flying around, we have a forth important property
- robustness
- Any deferred statement is guaranteed to be executed eventually.
This is different from C++
's handling of destructors, which are only
guaranteed to be executed if there is a try/catch
block underneath.
This principle of deferred execution extends to nested guarded blocks in a natural way, even if they are stacked in different function calls.
1.2 Dynamic versus static control flow for deferred statements
In the example above the control flow that was the result of deferring
statements was static, that is the defer
statements themselves
were not placed into conditionals or loops. Being able to place them
inside an if
, for example, gives the programmer more flexibility, in
particular it allows to only defer a statement if the corresponding
resource has really been needed.
// create a small local buffer as a compound literal double* p = (len > MAXLEN) ? 0 : (double[MAXLEN]){ 0 }; // if len is too big, go to the heap, instead if (p) { p = defer_calloc(len, sizeof double); defer free(p); } // now use p as a buffer until we leave the function
Here, the deferred statement will only be evaluated at the termination
of the guarded block only in cases where the buffer referenced by p
is dynamically allocated. To support such constructs, our
implementation determines the order and number of times each deferred
statement is executed at run-time. This approach requires allocating
storage at run-time to maintain defer state. The defer mechanism
provides fault tolerance by continuing to operate properly in the
event of the failure of a defer
statement by enduring the
deferred statement is executed to release the resource.
defer_calloc
and the defer
statement may both exhaust memory.
If the call succeeds (so a resource is allocated) but the defer
mechanism fails, the call to free
is evaluated immediately and the
execution of the enclosing guarded block is terminated by a panic with
an error code of DEFER_ENOMEM
.
1.3 Guarded blocks for function bodies
The current idea in our proposal to the C standardization is to have
each function body to implement a guarded block, such that an explicit
guard
statement is not necessary for many common use
cases. Generally, this can not be done by macro wonders but needs
compiler magic.
The reference implementation achieves that for the gcc
family of
compilers by some internal magic by keeping track of the stack
pointer. This has some caveats, see below. Nevertheless
in this documentation we will always speak about a guarded block,
regardless if a guard
statement is given explicitly or if
the function body acts as such.
1.4 Triggering the execution of deferred statements
There are three "severity" levels for termination of a guarded block,
break
or similar just terminates the guarded block, return
terminates all guarded blocks of the same function and
exit
or panic
of the same thread across all
function calls.
A deferred statement can be triggered in two ways.
- explicitly: by leaving a guarded block when coming to the end of
it or by using
break
,defer_break
,exit
… from within. implicitly: from a signal handler. Two signal handlers are provided
defer_sig_flag
anddefer_sig_jump
. Their actions are as their names indicate. The first just sets a flag and the user code then typically investigates that flag and acts accordingly. This would typically be used for a signal likeSIGINT
where the user interrupts the program execution. The second handler jumps to the first defer clause on the defer stack and triggers apanic
. This one is more appropriate for signals that are related to faulty operations that by themselves are not recoverable.This implementation does not install any of these handlers by default.
1.5 Execution of deferred statements and panic recovery
Once execution starts inside a deferred statement, the condition that
lead there can be investigated by means of recover
. This returns
an integer error code that indicates what happened. If it is 0
,
this execution of the defer clause is just "normal" retreat as of
break
, return
or exit
. If it is any other value
something "remarkable" might have happened, generally a
panic
is on its way down in the call stack.
Once an error condition has been recovered, the responsibility to
handle the error is passed back to the user. If they don't want
that they may re-issue the panic by calling `panic(code,
0)`, where the 0
indicates that the same final action (e.g
exit
) as before the recovery is used.
1.6 Improved usage of certain C library features
As examples how this could be used by the C library, there are
also augmented versions of storage allocation function, such as
defer_malloc
. The idea that these will panic if they encounter
an out-of-memory condition. That has several effects
- Explicit handling of such error conditions has not to be repeated at every call site.
- Cleanup actions that the user has installed by means of defer clause will be called, e.g to close files or to free large allocations.
- User code may establish a recovery mechanism through
recover
or alike. This will probably be rarely used by "normal" applications, but security critical applications would get a handle to avoid catastrophes, here.
1.6.1 Advantages of defer
for error handling
Inspection of the assembler that is created for these functions shows that all of this generates very little overhead for the fast execution path. In general, this technique might provide a sensible way to provide the same error detection facilities as Annex K, but in a way that
- preserves the prototypes of the functions
- is thread safe.
1.7 Provided interfaces
The important interfaces of this tool are:
guard
prefixes a guarded blockdefer
prefixes a defer clausebreak
ends a guarded block and executes all its defer clausesreturn
unwinds all guarded blocks of the current function and returns to the callerexit
unwinds all defer clauses of all active function calls of the thread and exits normallypanic
starts global unwinding of all guarded blocksrecover
inside a defer clause stops a panic and provides an error code
The features exit
, quick_exit
and thrd_exit
are overloaded to do
the unwinding as soon as this header is included. To be consistent
when such code is linked to legacy object files that have been
compiled differently, you should wrap the system calls. This can
usually be done by providing `-wrap=exit` etc arguments to the
linker. (You'd have to check your local manual for that.) This feature
may (but shouldn't) be switched off by providing the environment
variable DEFER_NO_WRAP
to the build.
In a similar way, this implementation also offers to wrap the
allocation functions of the C library, but the default here is the
other way around: per default malloc
and Co are not wrapped. You
may enable this by setting DEFER_MALLOC_WRAP
. Otherwise, you may
use functions defer_malloc
and similar to catch allocation
errors.
1.8 Error codes
send with panic
) are always positive, and that "system" error codes
are generally negated errno
numbers. To deal with such error codes
in a portable way, for all POSIX error codes we provide an equivalent
prefixed with DEFER_
. E.g there is DEFER_ENOMEM
that is the same
as ENOMEM
on platforms where that exists and some other unused
number otherwise.
For signal numbers there is a sophisticated encoding in even smaller
negative values. Because the system signal numbers may conflict with
errno
numbers we cannot use these directly. Instead, for all POSIX
signal numbers there is constant that replaces the SIG
prefix by a
DEFER_
prefix. E.g there is DEFER_HUP
to represent the signal
SIGHUP
regardless if that signal number actually exists on the
platform.
Two convenience macros ease the use of this feature, defer_if
as
a pseudo selection statement, and recover_signal
to
recover just signals and do nothing if there was none.
1.9 Interoperability with C++
C++ has similar features for out-of-order code execution, namely
destructors and catch
clauses. The defer
statement combines those
two features into one.
This implementation provides features to translate between these
mechanisms such that stack unwinding can be reliably performed in
programs that mix C and C++. In particular, if called from C++ code,
the C feature itself translates panics and requests for exit at the
boundary into C++ exceptions. The "only" thing that C++ applications
have to do for this to work is to establish a mark, that effectively
the current language is C++. When the C++ code includes
<stddefer_codes.h>
this is done (by some hackery) for any
translation unit that contains the function main
.
Nevertheless, to really ensure that resources are released by such a
C++ programm it is a good idea to have all exceptions caught. This
will ensure that all destructors down to and including in main
are
called when unwound. An easy way to achieve this is to establish
main
as a try-catch
block:
int main(int argc, char* argv[]) try { ... do your stuff ... } catch (...) { throw; }
The same could be done for functions that are called with threads of their own.
To establish interoperability for C++ code that might be called from
C
a bit more is needed. A wrapper testing_CPP
for a function
testing
can be established as in this example:
#include "stddefer_codes.h" #include "stddefer_cpp.h++" int testing_CPP(int rec) { std::defer_boundary boundary; // Must be the first declared variable try { return testing(rec); } catch (...) { boundary.panic(); } return 0; }
That is, we need
- a local variable of type
std::defer_boundary
for which the constructor memorizes the state prior to the call, - a
try/catch
clause that guarantees that exceptions are caught and that all the destructors are called when unwound - a call to the method
panic()
that either propagates any exception that was caught (if the caller is alsoC++
) or that translates an exception to a panic (if the caller isC
).
For the moment there is no automatization of such a wrapper feature.
When used consistently like that, arbitray back and forth translation
between C defer
and C++ exceptions is performed but has its limits
in the expressiveness of the defer
error codes. We translate some of
the standard codes and exceptions:
error code (C or POSIX) |
exception (C++ ) |
remark |
---|---|---|
0 |
std::defer_exception(0) |
unwinding and exit or thrd_exit |
-DEFER_EBADF |
std::ios_base::failure |
|
-DEFER_EDOM |
std::domain_error |
|
-DEFER_EINVAL |
std::invalid_argument |
|
-DEFER_ENOMEM |
std::bad_alloc |
|
-DEFER_EOVERFLOW |
std::overflow_error |
|
-DEFER_ERANGE |
std::out_of_range |
|
-DEFER_EUNDERFLOW |
std::underflow_error |
|
other | std::defer_exception(other) |
Support for other codes might come in the future.
1.10 Implementation specifics
This implementation uses setjmp/longjmp
as a main feature to jump to
deferred statements within the same function or to unwind the call
stack.
We distinguishes "jumps" that are known to target
the same function (_Defer_shrtjmp
) and those that are known to
jump to another function on the call stack (_Defer_longjmp
). The
unwind for a return
will usually be all short jumps, whereas
exit
and other unwinding of the call stack always initiates a long
jump.
This implementation is quite hackerish because it uses nested for
loops to implement the principal macros defer
and guard
. It does
so to ensure a mostly library only iplementation through macros, that
could in principle work with any C compiler. This technique leads to
code that is difficult to read and can therefore not be recommended.
Also, we use a intermediate processor for the production of the C (and
C++) code called shnell. This uses #pragma
annotations for the
definition of complicated macros and for "code unrolling". So if you'd
want to modify this implementation, you'd have to download shnell and
modify the "real" sources.
1.10.1 Extension: using computed goto
The implementation can distinguish cases where basically all jumps
are finally implemented as being long, or platforms where some
shortcut for a short jump can be taken. Currently this is only
implemented for gcc and friends that implement so-called "computed
goto
", that is labels for which addresses can be taken, and
where these addresses then can be used in an extended goto
feature. Such a specialized implementation can gain a lot on the
unwind side (these then are mostly simple jumps), but they still
have to keep track of all the guarded blocks and defer clauses
with setjmp
because these could be jumped to from other
functions or from signal handlers.
1.10.2 Extension: Captured values for defer
Local variables that are used inside a deferred statement must be
alive when the deferred statement is executed, so generally at the end
of the guarded block. This policy is necessary to be able to react to
changes to the variable during processing. For example, a pointer
value that is to be freed could be rewritten by calls to realloc
to
resize the object.
If C
will be extended with lamdas (hopefully in a nearer future) the
defer feature
defer { ... code comes here ... }
could be rewritten to default to something like
defer [&](){ ... code comes here ... }
that is we could say that defer receives a lambda with no parameters
and executes it when leaving the guard
. And the default for captures
would then just be &
, that is all variables are captured "by
reference".
In some cases it makes sense for defer
to capture a local variable
by value. In lambda notation this would be someting like
defer [&, toto](){ ... code comes here ... }
where the variable toto
would be frozen to the value it has at the
point of the defer
statement, and that particular value of toto
would then be provided when the deferred statement is executed at the
end of the guard
. In the reference implementation the same can be
achieved by using the alternative macro defer_capture
defer_capture(toto){
... code comes here ...
}
1.10.3 Omission of guard
for function body
For gcc
and friends the implementation is able to detect the
boundaries between different function calls. For a defer
that is not
placed inside a guard
this is important such that we do not
accidentally attach it to a guard
in another function. This only
works with compiler magic that keeps track of the "stack pointer".
1.10.4 Synchronization of local variables
The guarantees for the state of local variables that setjmp/longjmp
provides are weaker than those needed in our context. Therefore we use
some other synchronization features that guarantee up-to-date values,
namely atomic variables and fences. If these are not available, this
implementation will still work, but precautions as described for
defer
have to be made.
1.10.5 Caveats
This implementation is special, because it is almost a "header only"
implementation that overloads return
(plus some others) with a
macro. This is not ideal and can have some performance issues or even
compilation failures with system-provided inline functions. As a rule
of thumb, try to include the <stddefer.h>
header as late as
possible, such that there are as few interactions as possible.
2 Reference manual
2.1 *=defer=: ensure the execution of the deferred statement at the end of the guarded block
defer statement
Deferred statements may not themselves contain guarded blocks or other
defer clauses, and shall not call functions that may result in a
termination of the the execution other than panic
. Additionally,
such a call to panic
must only occur after the current panic state
had been tested with recover
.
The deferred statement may use any local variable that is visible
where the defer
is placed and that is still alive when the deferred
statement is executed, that is at the end of the surrounding guard
or function body. This property is checkable at compile time, and a
violation usually results in an abortion of the compilation. This
implementation here puts everything in place, such that a deferred
statement uses the last-written value for all variables, but the
success of that depends on the presence of some synchronization
feautures.
If such synchronization features are not available, local variables
that may change between the defer
itself and the execution of the
deferred statement and that are used in the deferred statement must be
declared volatile
such that the latest value is taken into
account. So as a rule of thumb, variables that you use inside a
deferred statement should be qualified: const
qualified for those
where the defer clause should use the original value, and volatile
qualified for those that should be used with their latest changes.
The implementation uses allocation as of calloc
and free
to
maintain the list of defer clauses. In case that calloc
fails,
the defer clause is executed and then the whole execution is
unwound by means of panic
and an error argument of
-DEFER_ENOMEM
.
2.1.1 defer_capture
: capture some variables and defer the execution of the depending statement
defer_capture([id0 [, id1 [, id2 ...]]]) statement
The argument list must be empty, or contain a list of variables. The effect is that those variables are evaluated, and the values are stored in a secret place. When the deferred statement is finally executed, address-less local variables with the same name and type (but const-qualified) are placed before the deferred statement, and are initialized with these frozen values.
2.2 *=guard=: mark a whole block as "guarded" and as using the defer mechanism.
guard compound-statement
If such a block is terminated normally or with a break
or continue
statement, all deferred statements that have been registered a defer
statement are executed in reverse order of their registration. There
is also a macro defer_break
that can be used in contexts where
break
or continue
statements would refer to an inner loop or
switch
statement. Also, return
, exit
, quick_exit
and
thrd_exit
all trigger the execution of deferred statements, up to
their respective levels of nestedness of guarded blocks.
Other standard means of non-linear control flow out of or into the
block (goto
, longjmp
, _Exit
, abort
), do not invoke that
mechanism and may result in memory leaks or other damage when used
within such a guarded block.
For some of these constructs, there are replacements defer_goto
,
defer_abort
etc that can be used instead.
2.3 *=panic=: Unwind the whole call stack and execute the deferred statements on the way down
noreturn void panic(int C); noreturn void panic(int C, void F(int));
After all deferred statement of the current thread are executed in
reverse order in which the defer
statements have been encountered,
in the last stack frame that has a defer
or guard
statement,
F(C)
is executed. Here F
is a defer_handler
and C
is an error
code. If omitted, F
defaults to an implementation specific
defer_handler
.
Valid defer handlers are all functions that have the correct signature
and that terminate the current thread or the whole
execution. Prominent candidates from the C library are exit
and
thrd_exit
.
This unwind chain can be stopped with a recover
call within one of
the executed deferred statements (or if a defer_if
clause is used).
The error code should be negative if this is supposed to map to a
system defined error condition as is for errno
or a caught
signal. Otherwise, all user supplied error numbers should be positive.
When unwinding an error condition triggered by the system, the
error code will always be negative.
2.3.1 defer_assert
: assert a runtime condition or panic
void defer_assert(condition, code, string-literal);
This is similar to a usual call to the assert
macro in that it
asserts that condition
holds. It cannot be switched off at compile
time but the panic that is triggered can be recovered. In particular,
the compiler can detect that the following code can only be reached if
the condition holds, and optimize the code accordingly.
For example, the following asserts that pointer p
is non-null, and
otherwise triggers a ENOMEM
panic.
defer_assert(p, -DEFER_ENOMEM, "unexpected allocation failure"); // p is now assumed to be non-null
2.4 Termination functions
noreturn void defer_exit(int); noreturn void defer_thrd_exit(int); noreturn void defer_quick_exit(int); noreturn void defer__Exit(int);
The functionality of these functions is the same as their un-prefixed C library counterparts, only that instead of terminating the thread or the execution immediately they will unwind the stack of collected deferred statements.
Code that is compiled with the <stddefer.h>
header will also just
replace calls to the C library functions
noreturn void exit(int); noreturn void thrd_exit(int); noreturn void quick_exit(int);
by calls to these wrappers.
Other code that is not compiled with it, may still call the C library
functions directly without unwinding. So when you are mixing such
units of different origin you should use linker tricks such as
-wrap-function=exit
to help you replace these calls by the wrapped
ones.
2.5 *=recover=: stop a panic and/or return such the error code
int recover(void);
A call to this function must reside within a deferred statement.
This will only stop unwinding of the panic if the error code had not
been 0
. Normal break
, return
, thrd_exit
, exit
etc will not
be be stopped and the guarded block, function, thread or program will
finish when all the associated deferred statements have been executed.
If the error code is non-zero, execution of the defer clause
then continues as if the nearest guarded block had been broken by
break
. That is, the execution of the defer clauses of
that particular guarded block are continued and then execution
resumes at the end of that guarded block.
In general, error codes provided by the system will be negative and
user provided error codes shall be positive. So to see if a system
error occurred you may e.g compare the value to -ERANGE
, or, if the
error originated from a caught signal to DEFER_SIGNO(SIGTERM)
.
2.5.1 defer_if
defer_if(err) statement-1 else statement-2
would be equivalent to a defer clause
defer { register int const err = recover(); if (err) statement-1 else statement-2 }
This stops an unwind originating from a panic
call or from a signal
with non-zero error code (or similar), returns the error code in a
local int
variable named err
, and executes the depending statement
if the value is non-zero, or an alternative else
clause, if any, if
the value is zero. The else
clause is optional.
the variable err
can then be used inside the if
or else
clauses,
but it is not mutable and its address cannot be taken. And it is a bit
useless within the else
clause, because we know that it is zero,
there.
2.5.2 defer_show
: show information about the latest panic
on stderr
void defer_show(int a);
This can be used as diagnosis tool after a recover
to provide
feedback to the user what went wrong with the execution.
2.5.3 recover_signal
: stop an unwind originating from a signal and/or return the signal number
int recover_signal(void);
This differs from recover
because it only reacts on signals that are
caught by the provided signal handlers (defer_sig_flag
and
defer_sig_jump
), not on other break
or panic
events. It can be
used in any context and does not need a surrounding guarded block or
defer
clause. Its cost is the lookup of a thread local variable and
a memory synchronization with that variable. So it should probably not
be placed inside performance critical loops.
When a defer signal occurs within a guarded block or defer clause and
is not caught by means of recover_signal
, the execution is
considered to be compromised, a panic results, and the deferred
statements of the thread are unwound.
This returns the number of the signal that has been caught.
A typical use of this in conjunction with defer_sig_flag
would be an
active loop:
int main(void) { // Establish the signal handler for user interrupts. signal(SIGINT, defer_sig_flag); while (active) { if (recover_signal()) { puts("caught signal, stopping loop and continuing"); break; } ... do whatever you have to do ... } }
Here, an INT
event can occur at any point of the computation before
or inside the while
loop. At the first detection of a signal, the
while
loop then is broken, and execution continues after it. This
guarantees that the whole loop body is always executed until its end.
2.6 Signal handlers
The provided signal handlers use extensions that might not be available everywhere. In particular, they use the fact that thread-local variables can be accessed from a signal handler.
2.6.1 defer_sig_flag
: a signal handler that flags the execution of a thread.
void defer_sig_flag(int sig);
This one is not too intrusive and hands control for the signal back to
the user code. Thus handling is delayed until the application code
reaches the end of a guarded block and/or actively issues a
recover_signal
operation to receive the code.
Generally this might be used to handle signals triggered by the user via the terminal.
This is not suited for signals that jump back to exactly the same code location where some repair work has to be performed to continue, such as invalid arithmetic or segmentation faults.
2.6.2 defer_sig_jump
: a signal handler that flags the execution of a thread and jumps to the first defer clause
void defer_sig_jump(int sig);
This is intrusive and jumps back into the next available defer clause. Therefore this has to assume that the error is caught by the same thread that is to perform that defer clause, and not all platforms may guarantee this.
This is suited for signals that otherwise would jump back to exactly the same code location where some repair work has to be performed to continue, such as arithmetic or segmentation faults.
Probably the C standard function signal
itself is not
appropriate for this kind of handler, because signals should be
switched off during the handling of such interrupts. Your platform
might propose a better tool than signal
for that. Be careful.
2.7 Encapsulated storage management functions
void* defer_malloc(size_t size); void* defer_calloc(size_t nmemb, size_t size); void* defer_realloc(void *ptr, size_t size); void* defer_aligned_alloc(size_t alignment, size_t size);
The functionality of these functions is the same as their un-prefixed
C library counterparts, only that instead of returning a null pointer
on failure they will issue a panic
. For example, defer_malloc
,
will execute panic(-DEFER_EINVAL)
when called with a size
of 0
and execute panic(-DEFER_ENOMEM)
when the allocation fails.
This has two advantages. First, when using these functions you will
never again forget to check of the result of such a function. Second,
subsequent code (if reached) can always assume that the return value
is non-null. For example other than for realloc
the following is a
valid idiom:
void* p = malloc(small); defer free(p); ... do something with p ... defer { ... save some precious values from p ... } p = defer_realloc(p, huge);
The assignment to p
is only executed if the call to defer_realloc
is successful, and the call to free
in the deferred statement will
always use the good (= last allocated) value for p
. Also, if it
fails execution is immediately transferred to the currently deferred
statements, and the necessary terminal actions are taken as indicated,
first the second deferred statement may save values from p
and then
p
is freed.
2.7.1 DEFER_MALLOC_WRAP
: force the usage of the wrapped allocation functions
Code compiled with this macro set, will replace all usage of the C library storage allocation functions by the wrapped versions as presented above.
3 Terms
3.1 Copyright and license
BSD 3-Clause License Copyright (c) 2020, Freek Wiedijk Jens Gustedt All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.