1. Changelog
1.1. Revision 0 - December 10th, 2023
- 
     Initial release. ✨ 
2. Introduction, Motivation, and Prior Art
During the production of the defer paper, we found some implementations in niche cases performed unwinding. Therefore, as a stopgap, the 
This is not the best state of affairs, as having an uncheckable form of unwinding means that implementations need not provide any user-actionable way to detect whether or not their implementation is doing unwinding (without onerous 
This proposal sets out to define a conditionally supported unwinding feature for C, and provide compile-time integer constant expressions through macros in specific headers to allow for a user to know which termination/non-local jump functions will produce behavior that they can rely on. It will also allow them to programmatically devise their own solutions if necessary.
3. Design
The design of this addition is based on a few observations, documented below. Notably,
- 
     some implementations in very select cases do perform stack unwinding, even in C; 
- 
     that stack unwinding unwinds the versions of defer __attribute__ (( cleanup ( …))) 
- 
     and, those implementations make the choice at compile-time (not run-time) to do such cleanup. 
Therefore, we wanted to provide a conditionally supported, program-checkable way to do stack unwinding. It is our hope that by providing these documenting macros in the various headers (
3.1. "Why Does This Not Unwind The Whole Call Stack??"
Most C implementations do NOT provide a compiler-driven or library-driven unwinding that we could find, even with 
- 
     exit 
- 
     _Exit 
- 
     quick_exit 
- 
     thrd_exit 
- 
     or, abort 
did not produce any code that called either the cleanup-annotated variables, or other code. 
Note: This is compatible with C++ semantics for a similar C++ feature: constructors and destructors.
It is noteworthy that not even C++ destructors run on the invocation of any of these functions, either. (You can test that assumption here.) They have to use the C++-specific function 
The one place this does not hold up is 
#include <stdlib.h>#include <stdio.h>#include <threads.h>extern void * ep ; extern void * ep2 ; extern int alternate ; void cfree ( void * userdata ) { void ** pp = ( void ** ) userdata ; printf ( "freeing %p !! \n " , * pp ); free ( * pp ); } [[ gnu :: noinline ]] void use ( void * p ) { if (( ++ alternate % 2 ) == 0 ) ep = p ; else ep2 = p ; } int thread ([[ maybe_unused ]] void * arg ) { __attribute__ (( cleanup ( cfree ))) void * p = malloc ( 1 ); printf ( "allocating %p !! \n " , p ); use ( p ); thrd_exit ( 1 ); return 1 ; } int main () { __attribute__ (( cleanup ( cfree ))) void * p = malloc ( 1 ); printf ( "allocating %p !! \n " , p ); int r = 0 ; thrd_t th0 = {}; thrd_create ( & th0 , thread , NULL); thrd_join ( th0 , & r ); use ( p ); exit ( 0 ); return 0 ; } void * ep = 0 ; void * ep2 = 0 ; int alternate = 0 ; 
As of December 6th, 2023 on GCC trunk with the latest libpthreads, this code will print:
allocating 0xa072a0 !! allocating 0x7f8034000b70 !! freeing 0x7f8034000b70 !!
with 
allocating 0x47e2a0 !! allocating 0x7f7e14000b70 !!
with 
However, note that even in this example, the memory from 
Finally, we note that pretty much everything in MSVC is done by doing stack unwinding with their Structured Exception Handling (SEH) or similar techniques, so for the macros we provide almost every single one will be defined and have the value of 
4. Implementation Experience
MSVC performs select types of stack unwinding with 
The reason we provide so many different macros is because implementations have, effectively, chosen what happens for these on a function-by-function basis: therefore, the best we can do to provide good standards-backed, implementation-defined/conditionally supported behavior is to mention it directly in the paper.
5. Wording
Wording is relative to the latest draft revision of the C Standard.
5.1. Add a new §5.1.2.5 Unwinding describing the Conditionally Supported unwinding semantics
5.1.2.5 UnwindingUnwinding is a conditionally supported feature of executing statements and expressions as the program returns to a specific location through a non-local jump, or through the program termination. There is:
partial unwinding, when a program or thread is not terminated and the program returns to some location within itself and on the same thread;
thread unwinding, when a program is not terminated but a thread is terminated;
or, program unwinding, when a program is normally or abnormally terminated.
Unwinding is a conditionally supported feature. Support is queried by checking the following macro definitions from Clause 7:S
(__STDC_LONGJMP_UNWINDS__ , 7.13)< setjmp . h > 
(__STDC_SIGNAL_SIGABRT_UNWINDS__ , 7.14)< signal . h > 
(__STDC_SIGNAL_SIGFPE_UNWINDS__ , 7.14)< signal . h > 
(__STDC_SIGNAL_SIGILL_UNWINDS__ , 7.14)< signal . h > 
(__STDC_SIGNAL_SIGINT_UNWINDS__ , 7.14)< signal . h > 
(__STDC_SIGNAL_SIGSEGV_UNWINDS__ , 7.14)< signal . h > 
(__STDC_SIGNAL_SIGTERM_UNWINDS__ , 7.14)< signal . h > 
(__STDC__EXIT_UNWINDS__ , 7.24)< stdlib . h > 
(__STDC_ABORT_UNWINDS__ , 7.24)< stdlib . h > 
(__STDC_EXIT_UNWINDS__ , 7.24)< stdlib . h > 
(__STDC_QUICK_EXIT_UNWINDS__ , 7.24)< stdlib . h > 
(__STDC_THRD_EXIT_UNWINDS__ , 7.28.1)< threads . h > It is implementation-defined if other features or functions provide unwinding semantics. When supported, specific function calls or actions specified in this document or by the implementation trigger unwinding.
For partial unwinding, a program that performs a non-local jumps from one block into another block runs every currently reached but unexecuted
statement (6.8.1), in the order and with the semantics as specified in 6.8.1, that has been reached between the current execution path (including recursive function invocations) and the location being jumped to.defer For thread unwinding, a program that performs the termination of a single thread of execution runs every currently reached but unexecuted
statement (6.8.1), in the order and with the semantics as specified in 6.8.1, that has been reached between the current execution path (including recursive function invocations) and the start of the execution of the thread.defer For program unwinding, a program that terminates (normally or abnormally) runs every reached but currently unexecuted
statement, in the order and with the semantics as specified in 6.8.1, that has been reached between the current execution path (including recursive function invocations) and the start of the program.defer When not supported, none of the actions described in the preceding paragraphs of this section are taken.
5.2. Modify §6.8.7 Defer statements describing the Conditionally Supported unwinding semantics
6.8.7 Defer statementsIf E has any defer statements D that have been reached and their S have not yet executed, but the program is terminated or leaves *E through any means such as:
a function with the deprecated
function specifier, or a function annotated with the_Noreturn /no_return attribute, is called;_Noreturn 
or, any signal
,SIGABRT , orSIGINT occurs;SIGTERM then any such S are not run,
unless as specified otherwise by the implementationFN0✨)except indicated by the conditional support for unwinding (5.1.2.5) . Any other D that have not been reached are not run.FN0✨)The execution of deferred statements upon non-local jumps or program termination is a technique sometimes known as "unwinding" or "stack unwinding", and some implementations perform it. See also ISO/IEC 14882 Programming languages — C++, section [except.ctor].
5.3. Add a new paragraph 3 of §7.13 to describe one of the conditionally supported unwinding macros
7.13 Non-local jumps< setjmp . h > …The macro
__STDC_LONGJMP_UNWINDS__ is an integer constant expression with a value equivalent to 1 if partial unwinding (5.1.2.5) is supported when the
function is invoked successfully, or 0 otherwise.longjmp …
5.4. Add a new paragraph to §7.14 to describe several of the conditionally supported unwinding macros
7.14 Signal handling< signal . h > …The macros
__STDC_SIGNAL_SIGABRT_UNWINDS__ __STDC_SIGNAL_SIGFPE_UNWINDS__ __STDC_SIGNAL_SIGILL_UNWINDS__ __STDC_SIGNAL_SIGINT_UNWINDS__ __STDC_SIGNAL_SIGSEGV_UNWINDS__ __STDC_SIGNAL_SIGTERM_UNWINDS__ are integer constant expressions with a value equivalent to 1 if unwinding (5.1.2.5) is supported when the signals
,SIGABRT ,SIGFPE ,SIGILL ,SIGINT , orSIGSEGV are raised, respectively, or 0 otherwise.SIGTERM …
5.5. Modify paragraph 4 of §7.24 to describe several of the conditionally supported unwinding macros
7.24 General utilities< stdlib . h > ……
which is never greater than
; and,MB_LEN_MAX __STDC__EXIT_UNWINDS__ __STDC_ABORT_UNWINDS__ __STDC_EXIT_UNWINDS__ __STDC_QUICK_EXIT_UNWINDS__ are integer constant expressions with a value equivalent to 1 if program unwinding (5.1.2.5) is supported when the functions
,_Exit ,abort , orexit are invoked and terminate the program, respectively, or 0 otherwise.quick_exit …
5.6. Modify paragraph 3 of §7.28.1 to describe one of the conditionally supported unwinding macros
7.28 Threads< threads . h > 7.28.1 Introduction……
which is never greater than
; and,MB_LEN_MAX __STDC_THRD_EXIT_UNWINDS__ is an integer constant expression with a value equivalent to 1 if thread unwinding or program unwinding (5.1.2.5) is supported when the function
is invoked and terminates the thread or program, or 0 otherwise.thrd_exit …
5.7. Modify Annex J’s list of implementation-defined behaviors
Note: 📝 For the editor to do within the Annex J implementation-defined behavior list.