// // onexit.cpp // // Copyright (c) Microsoft Corporation. All rights reserved. // // The _onexit registry, which stores pointers to functions to be called when // the program terminates or, when the CRT is statically linked into a DLL, when // the DLL is unloaded. // // When the CRT is statically linked into an EXE or a DLL, this registry is used // to hold the on-exit functions for the module (EXE or DLL) into which it is // linked. // // When the dynamic CRT is used, this object is part of the AppCRT DLL and this // registry stores the on-exit functions for the VCRuntime. If the EXE for the // process uses this VCRuntime, then this registry also stores the on-exit // functions for that EXE. If a DLL uses the dynamic CRT, then that DLL has its // own registry, defined in the statically-linked VCStartup library. // #include // The global atexit and at_quick_exit registries extern "C" { _onexit_table_t __acrt_atexit_table{}; } extern "C" { _onexit_table_t __acrt_at_quick_exit_table{}; } enum : size_t { initial_table_count = 32, minimum_table_increment = 4, maximum_table_increment = 512 }; // Registers a function to be executed on exit. This function modifies the global // onexit table. extern "C" int __cdecl _crt_atexit(_PVFV const function) { return _register_onexit_function(&__acrt_atexit_table, reinterpret_cast<_onexit_t>(function)); } extern "C" int __cdecl _crt_at_quick_exit(_PVFV const function) { return _register_onexit_function(&__acrt_at_quick_exit_table, reinterpret_cast<_onexit_t>(function)); } extern "C" int __cdecl _initialize_onexit_table(_onexit_table_t* const table) { if (!table) { return -1; } // If the table has already been initialized, do not do anything. Note that // this handles both the case where the table was value initialized and where // the table was initialized with encoded null pointers. if (table->_first != table->_end) { return 0; } _PVFV* const encoded_nullptr = __crt_fast_encode_pointer(nullptr); table->_first = encoded_nullptr; table->_last = encoded_nullptr; table->_end = encoded_nullptr; return 0; } // Appends the given 'function' to the given onexit 'table'. Returns 0 on // success; returns -1 on failure. In general, failures are considered fatal // in calling code. extern "C" int __cdecl _register_onexit_function(_onexit_table_t* const table, _onexit_t const function) { return __acrt_lock_and_call(__acrt_select_exit_lock(), [&] { if (!table) { return -1; } _PVFV* first = __crt_fast_decode_pointer(table->_first); _PVFV* last = __crt_fast_decode_pointer(table->_last); _PVFV* end = __crt_fast_decode_pointer(table->_end); // If there is no room for the new entry, reallocate a larger table: if (last == end) { size_t const old_count = end - first; size_t const increment = old_count > maximum_table_increment ? maximum_table_increment : old_count; // First, try to double the capacity of the table: size_t new_count = old_count + increment; if (new_count == 0) { new_count = initial_table_count; } _PVFV* new_first = nullptr; if (new_count >= old_count) { new_first = _recalloc_crt_t(_PVFV, first, new_count).detach(); } // If that didn't work, try to allocate a smaller increment: if (new_first == nullptr) { new_count = old_count + minimum_table_increment; new_first = _recalloc_crt_t(_PVFV, first, new_count).detach(); } if (new_first == nullptr) { return -1; } first = new_first; last = new_first + old_count; end = new_first + new_count; // The "additional" storage obtained from recalloc is sero-initialized. // The array holds encoded function pointers, so we need to fill the // storage with encoded nullptrs: _PVFV const encoded_nullptr = __crt_fast_encode_pointer(nullptr); for (auto it = last; it != end; ++it) { *it = encoded_nullptr; } } *last++ = reinterpret_cast<_PVFV>(__crt_fast_encode_pointer(function)); table->_first = __crt_fast_encode_pointer(first); table->_last = __crt_fast_encode_pointer(last); table->_end = __crt_fast_encode_pointer(end); return 0; }); } // This function executes a table of _onexit()/atexit() functions. The // terminators are executed in reverse order, to give the required LIFO // execution order. If the table is uninitialized, this function has no // effect. After executing the terminators, this function resets the table // so that it is uninitialized. Returns 0 on success; -1 on failure. extern "C" int __cdecl _execute_onexit_table(_onexit_table_t* const table) { return __acrt_lock_and_call(__acrt_select_exit_lock(), [&] { if (!table) { return -1; } _PVFV* first = __crt_fast_decode_pointer(table->_first); _PVFV* last = __crt_fast_decode_pointer(table->_last); if (!first || first == reinterpret_cast<_PVFV*>(-1)) { return 0; } // This loop calls through caller-provided function pointers. We must // save and reset the global state mode before calling them, to maintain // proper mode nesting. (These calls to caller-provided function pointers // are the only non-trivial calls, so we can do this once for the entire // loop.) { __crt_state_management::scoped_global_state_reset saved_state; _PVFV const encoded_nullptr = __crt_fast_encode_pointer(nullptr); _PVFV* saved_first = first; _PVFV* saved_last = last; for (;;) { // Find the last valid function pointer to call: while (--last >= first && *last == encoded_nullptr) { // Keep going backwards } if (last < first) { // There are no more valid entries in the list; we are done: break; } // Store the function pointer and mark it as visited in the list: _PVFV const function = __crt_fast_decode_pointer(*last); *last = encoded_nullptr; function(); _PVFV* const new_first = __crt_fast_decode_pointer(table->_first); _PVFV* const new_last = __crt_fast_decode_pointer(table->_last); // Reset iteration if either the begin or end pointer has changed: if (new_first != saved_first || new_last != saved_last) { first = saved_first = new_first; last = saved_last = new_last; } } } if (first != reinterpret_cast<_PVFV*>(-1)) { _free_crt(first); } _PVFV* const encoded_nullptr = __crt_fast_encode_pointer(nullptr); table->_first = encoded_nullptr; table->_last = encoded_nullptr; table->_end = encoded_nullptr; return 0; }); }