Skip to content

atexit: Clarify DLL behavior is the same as _onexit - #5952

Open
Jay Satiro (jay) wants to merge 1 commit into
MicrosoftDocs:mainfrom
jay:atexit
Open

atexit: Clarify DLL behavior is the same as _onexit#5952
Jay Satiro (jay) wants to merge 1 commit into
MicrosoftDocs:mainfrom
jay:atexit

Conversation

@jay

Copy link
Copy Markdown
Contributor
  • Document that atexit called from within a DLL will register the routine to run when the DLL is unloaded.

atexit is a wrapper around _onexit which already documents this behavior.

Ref: curl/curl#22383 (comment)

Reported-by: Michał Petryka

Closes #xxxx


I looked in the CRT source for 2010 (pre-Universal CRT) and 2022 (Universal CRT) and in both atexit is a wrapper around _onexit.

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\onexit.c
/***
*_onexit(func), atexit(func) - add function to be executed upon exit
*
*Purpose:
*       The _onexit/atexit functions are passed a pointer to a function
*       to be called when the program terminate normally.  Successive
*       calls create a register of functions that are executed last in,
*       first out.
*
*Entry:
*       void (*func)() - pointer to function to be executed upon exit
*
*Exit:
*       onexit:
*           Success - return pointer to user's function.
*           Error - return NULL pointer.
*       atexit:
*           Success - return 0.
*           Error - return non-zero value.
*
*Notes:
*       This routine depends on the behavior of _initterm() in CRT0DAT.C.
*       Specifically, _initterm() must not skip the address pointed to by
*       its first parameter, and must also stop before the address pointed
*       to by its second parameter.  This is because _onexitbegin will point
*       to a valid address, and _onexitend will point at an invalid address.
*
*Exceptions:
*
*******************************************************************************/

_onexit_t __cdecl _onexit (
        _onexit_t func
        )
{
        _onexit_t retval;

        _lockexit();

        __try {
            retval = _onexit_nolock(func);
        }
        __finally {
            _unlockexit();
        }

        return retval;
}


static _onexit_t __cdecl _onexit_nolock (
        _onexit_t func
        )
{
        _PVFV * p;
        size_t  oldsize;
        _PVFV * onexitbegin = (_PVFV *) DecodePointer(__onexitbegin);
        _PVFV * onexitend = (_PVFV *) DecodePointer(__onexitend);

        /* overflow check */
        if (onexitend < onexitbegin ||
            ((char *)onexitend - (char *)onexitbegin) + sizeof(_PVFV) < sizeof(_PVFV))
        {
            return NULL;
        }

        /*
         * First, make sure the table has room for a new entry
         */
        if ( (oldsize = _msize_crt(onexitbegin))
                < ((size_t)((char *)onexitend -
            (char *)onexitbegin) + sizeof(_PVFV)) )
        {
            /*
             * not enough room, try to grow the table. first, try to double it.
             */
            size_t newsize = oldsize + __min(oldsize, (MAXINCR * sizeof(_PVFV)));
            if ( newsize < oldsize ||
                 (p = (_PVFV *)_realloc_crt(onexitbegin, newsize)) == NULL )
            {
                /*
                 * failed, try to grow by MININCR
                 */
                newsize = oldsize + MININCR * sizeof(_PVFV);
                if ( newsize < oldsize ||
                     (p = (_PVFV *)_realloc_crt(onexitbegin, newsize)) == NULL )
                    /*
                     * failed again. don't do anything rash, just fail
                     */
                    return NULL;
            }

            /*
             * update __onexitend and __onexitbegin
             */
#pragma warning(suppress: 22008) /* prefast is confused */
            onexitend = p + (onexitend - onexitbegin);
            onexitbegin = p;
            __onexitbegin = (_PVFV *) EncodePointer(onexitbegin);
        }

        /*
         * Put the new entry into the table and update the end-of-table
         * pointer.
         */
         *(onexitend++) = (_PVFV) EncodePointer(func);
        __onexitend = (_PVFV *) EncodePointer(onexitend);

        return func;
}

int __cdecl atexit (
        _PVFV func
        )
{
        return (_onexit((_onexit_t)func) == NULL) ? -1 : 0;
}
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.31.31103\crt\src\vcruntime\utility.cpp
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// On-Exit Table
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// When a module uses the Universal CRT DLL, the behavior of _onexit(), atexit(),
// and at_quick_exit() is different depending on whether the module is an EXE or
// a DLL.  If it is an EXE, then calls to these functions are passed to the
// Universal CRT DLL and the callbacks are registered with its atexit function
// tables.  This way, functions registered by the EXE are called whenever one of
// the exit() functions is called.
//
// If the module is a DLL, it has its own table of registered functions.  This
// table is executed when the DLL is unloaded (during DLL_PROCESS_DETACH).
//
// Here, we define the module-local function tables used by DLLs that use the
// Universal CRT DLL.  If this module is an EXE or if the Universal CRT is
// statically linked into this module, then these tables will have pointers
// initialized to -1.
//
// The user-callable _onexit(), atexit(), and at_quick_exit() functions then
// either use the module-local function tables or will forward calls to the
// Universal CRT DLL, as appropriate.
static _onexit_table_t module_local_atexit_table{};
static _onexit_table_t module_local_at_quick_exit_table{};
static bool module_local_atexit_table_initialized = false;

extern "C" _onexit_t __cdecl _onexit(_onexit_t const function)
{
    _PVFV* const onexit_first = module_local_atexit_table._first;

    if (onexit_first == reinterpret_cast<_PVFV*>(-1))
    {
        return _crt_atexit(reinterpret_cast<_PVFV>(function)) == 0
            ? function
            : nullptr;
    }
    else
    {
        return _register_onexit_function(&module_local_atexit_table, function) == 0
            ? function
            : nullptr;
    }
}

extern "C" int __cdecl atexit(_PVFV const function)
{
    return _onexit(reinterpret_cast<_onexit_t>(function)) != nullptr
        ? 0
        : -1;
}

/cc Michał Petryka (@MichalPetryka)

- Document that atexit called from within a DLL will register the
  routine to run when the DLL is unloaded.

atexit is a wrapper around _onexit which already documents this
behavior.

Ref: curl/curl#22383 (comment)

Reported-by: Michał Petryka

Closes #xxxx
@prmerger-automator

Copy link
Copy Markdown
Contributor

Jay Satiro (@jay) : Thanks for your contribution! The author(s) and reviewer(s) have been notified to review your proposed change.

1 similar comment
@prmerger-automator

Copy link
Copy Markdown
Contributor

Jay Satiro (@jay) : Thanks for your contribution! The author(s) and reviewer(s) have been notified to review your proposed change.

@jay

Copy link
Copy Markdown
Contributor Author

_crt_atexit may also need this information? I'm not sure since I do not have the source for that.

@learn-build-service-prod

Copy link
Copy Markdown
Contributor

Learn Build status updates of commit 4b73d28:

✅ Validation status: passed

File Status Preview URL Details
docs/c-runtime-library/reference/atexit.md ✅Succeeded

For more details, please refer to the build report.

@v-regandowner

Copy link
Copy Markdown
Contributor

Tyler Whitney (@TylerMSFT)

Can you review the proposed changes?

IMPORTANT: When the changes are ready for publication, adding a #sign-off comment is the best way to signal that the PR is ready for the review team to merge.

#label:"aq-pr-triaged"
@MicrosoftDocs/public-repo-pr-review-team

@prmerger-automator prmerger-automator Bot added the aq-pr-triaged Tracking label for the PR review team label Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants