This is an automated email from the ASF dual-hosted git repository.

acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 52b77dcb4d325c04bb1855789b83e237f25b4d10
Author: Marco Casaroli <[email protected]>
AuthorDate: Tue Oct 20 21:06:21 2026 +0200

    libs/libc/stdlib: Grow the exit function list on the heap.
    
    atexit(), on_exit(), at_quick_exit() and __cxa_atexit() share one table
    of CONFIG_LIBC_MAX_EXITFUNS entries in the task information.  When it was
    full, the registration failed, and a C++ program with more static
    objects than entries lost their destructors without notice.  With
    CONFIG_LIBC_MAX_EXITFUNS=0 (the default) atexit() returned 0 and never
    called the function.
    
    Keep CONFIG_LIBC_MAX_EXITFUNS entries in the task information, and
    allocate more entries from the heap, 8 at a time.  The number of exit
    functions is now limited only by memory: ATEXIT_MAX and
    sysconf(_SC_ATEXIT_MAX) are INT_MAX.  A registration fails only when the
    heap is full, and then returns non-zero.  The heap part is freed after
    the exit functions ran, or when the process ends without exit().
    
    With CONFIG_LIBC_MAX_EXITFUNS=0 there are no exit functions, as before,
    but atexit(), on_exit() and at_quick_exit() are not built: a program
    that calls them does not link, instead of losing its functions without
    notice.  __cxa_atexit() still registers nothing, so that C++ programs
    link; the destructors of static objects do not run, as before.
    
    struct atexit_list_s in struct task_info_s gets a count and a pointer
    for the heap part.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 include/nuttx/atexit.h        |  21 ++++++--
 libs/libc/stdlib/Kconfig      |  16 ++++--
 libs/libc/stdlib/lib_atexit.c | 118 +++++++++++++++++++++++++++++-------------
 sched/tls/task_uninitinfo.c   |  12 +++++
 4 files changed, 124 insertions(+), 43 deletions(-)

diff --git a/include/nuttx/atexit.h b/include/nuttx/atexit.h
index c7b32e1b7ff..882d859f2c4 100644
--- a/include/nuttx/atexit.h
+++ b/include/nuttx/atexit.h
@@ -28,6 +28,7 @@
  ****************************************************************************/
 
 #include <nuttx/config.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stdlib.h>
 
@@ -35,9 +36,17 @@
  * Pre-processor Definitions
  ****************************************************************************/
 
-/* Amount of exit functions */
+/* The number of exit functions is limited only by memory: the first
+ * CONFIG_LIBC_MAX_EXITFUNS entries are in the task information, the next
+ * ones come from the heap.  With CONFIG_LIBC_MAX_EXITFUNS 0 there are no
+ * exit functions: atexit(), at_quick_exit() and on_exit() are not built.
+ */
 
-#define ATEXIT_MAX (CONFIG_LIBC_MAX_EXITFUNS)
+#if CONFIG_LIBC_MAX_EXITFUNS > 0
+#  define ATEXIT_MAX INT_MAX
+#else
+#  define ATEXIT_MAX 0
+#endif
 
 /****************************************************************************
  * Public Types
@@ -59,11 +68,15 @@ struct atexit_s
   FAR void   *arg;
 };
 
+#if CONFIG_LIBC_MAX_EXITFUNS > 0
 struct atexit_list_s
 {
-  int             nfuncs;
-  struct atexit_s funcs[ATEXIT_MAX];
+  int                 nfuncs; /* Number of registered functions */
+  int                 nextra; /* Number of entries in extra */
+  FAR struct atexit_s *extra; /* Entries after funcs[] (heap) */
+  struct atexit_s     funcs[CONFIG_LIBC_MAX_EXITFUNS];
 };
+#endif
 
 /****************************************************************************
  * Public Function Prototypes
diff --git a/libs/libc/stdlib/Kconfig b/libs/libc/stdlib/Kconfig
index 272258344e3..a3433fd55a7 100644
--- a/libs/libc/stdlib/Kconfig
+++ b/libs/libc/stdlib/Kconfig
@@ -31,11 +31,21 @@ config LIBC_MAX_TMPFILE
                file path.
 
 config LIBC_MAX_EXITFUNS
-       int "Maximum amount of exit functions"
+       int "Number of preallocated exit functions"
        default 0
        ---help---
-               Configure the amount of exit functions for atexit/on_exit. The 
ANSI
-               default is 32, but most likely we don't need as many.
+               The number of exit functions (atexit(), on_exit(), 
at_quick_exit()
+               and the C++ __cxa_atexit()) that the task information of each
+               process holds.  More functions come from the heap, 8 at a time,
+               so the number is limited only by memory.  The entries in the 
task
+               information need no heap, also not at start-up.
+
+               With 0, there are no exit functions: atexit(), on_exit() and
+               at_quick_exit() are not built, so a program that calls them does
+               not link, and __cxa_atexit() registers nothing (the destructors
+               of static C++ objects do not run).  A C++ ELF program (built 
with
+               -fno-use-cxa-atexit) with a static object in a function calls
+               atexit(), so it needs a value larger than 0.
 
 config LIBC_DISABLE_HEXSTR_TO_FLOAT
        bool "Disable hex-string to float/double conversions"
diff --git a/libs/libc/stdlib/lib_atexit.c b/libs/libc/stdlib/lib_atexit.c
index 132ab302ff4..4ad98ea0346 100644
--- a/libs/libc/stdlib/lib_atexit.c
+++ b/libs/libc/stdlib/lib_atexit.c
@@ -29,11 +29,20 @@
 #include <stdlib.h>
 
 #include <nuttx/atexit.h>
+#include <nuttx/lib/lib.h>
 #include <nuttx/mutex.h>
 #include <nuttx/tls.h>
 
 #if CONFIG_LIBC_MAX_EXITFUNS > 0
 
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Number of entries that each heap allocation adds */
+
+#define ATEXIT_GROW 8
+
 /****************************************************************************
  * Private Functions
  ****************************************************************************/
@@ -60,6 +69,25 @@ static FAR struct atexit_list_s *get_exitfuncs(void)
   return &info->ta_exit;
 }
 
+/****************************************************************************
+ * Name: get_entry
+ *
+ * Description:
+ *    Return entry 'idx' of the list: in funcs[] or in the heap part.
+ *
+ ****************************************************************************/
+
+static inline FAR struct atexit_s *
+get_entry(FAR struct atexit_list_s *aehead, int idx)
+{
+  if (idx < CONFIG_LIBC_MAX_EXITFUNS)
+    {
+      return &aehead->funcs[idx];
+    }
+
+  return &aehead->extra[idx - CONFIG_LIBC_MAX_EXITFUNS];
+}
+
 /****************************************************************************
  * Public Functions
  ****************************************************************************/
@@ -69,51 +97,59 @@ int atexit_register(int type, CODE void (*func)(void), FAR 
void *arg,
 {
   FAR struct task_info_s   *info = task_get_info();
   FAR struct atexit_list_s *aehead;
-  int                       idx;
-  int                       ret = ERROR;
+  FAR struct atexit_s      *entry;
+  int                       ret;
 
   /* REVISIT: Missing logic */
 
   UNUSED(dso);
 
-  /* The following must be atomic */
+  if (func == NULL)
+    {
+      return ERROR;
+    }
 
   aehead = get_exitfuncs();
 
-  if (func)
+  ret = nxrmutex_lock(&info->ta_lock);
+  if (ret < 0)
     {
-      ret = nxrmutex_lock(&info->ta_lock);
-      if (ret < 0)
-        {
-          return -ret;
-        }
+      return ERROR;
+    }
 
-      if ((idx = aehead->nfuncs) < ATEXIT_MAX)
-        {
-          aehead->funcs[idx].type = type;
-          aehead->funcs[idx].func = func;
-          aehead->funcs[idx].arg  = arg;
-          aehead->nfuncs++;
-          ret = OK;
-        }
-      else
+  /* Grow the heap part when the list is full */
+
+  if (aehead->nfuncs >= CONFIG_LIBC_MAX_EXITFUNS + aehead->nextra)
+    {
+      entry = lib_realloc(aehead->extra, (aehead->nextra + ATEXIT_GROW) *
+                                         sizeof(struct atexit_s));
+      if (entry == NULL)
         {
-          ret = ERROR;
+          nxrmutex_unlock(&info->ta_lock);
+          return ERROR;
         }
 
-      nxrmutex_unlock(&info->ta_lock);
+      aehead->extra   = entry;
+      aehead->nextra += ATEXIT_GROW;
     }
 
-  return ret;
+  entry       = get_entry(aehead, aehead->nfuncs);
+  entry->type = type;
+  entry->func = func;
+  entry->arg  = arg;
+  aehead->nfuncs++;
+
+  nxrmutex_unlock(&info->ta_lock);
+  return OK;
 }
 
 void atexit_call_exitfuncs(int status, bool quick)
 {
   FAR struct atexit_list_s *aehead;
   FAR struct task_info_s   *info = task_get_info();
+  FAR struct atexit_s      *entry;
   CODE void               (*func)(void);
   FAR void                 *arg;
-  int                       idx;
   int                       type;
 
   /* Call exit functions in reverse order */
@@ -133,16 +169,16 @@ void atexit_call_exitfuncs(int status, bool quick)
           break;
         }
 
-      idx = aehead->nfuncs - 1;
+      entry = get_entry(aehead, aehead->nfuncs - 1);
 
-      type = aehead->funcs[idx].type;
-      func = aehead->funcs[idx].func;
-      arg  = aehead->funcs[idx].arg;
+      type = entry->type;
+      func = entry->func;
+      arg  = entry->arg;
 
       /* Remove the function to prevent recursive call to it */
 
-      aehead->funcs[idx].func = NULL;
-      aehead->funcs[idx].arg  = NULL;
+      entry->func = NULL;
+      entry->arg  = NULL;
       aehead->nfuncs--;
 
       nxrmutex_unlock(&info->ta_lock);
@@ -172,9 +208,21 @@ void atexit_call_exitfuncs(int status, bool quick)
           (*((CODE void (*)(FAR void *))func))(arg);
         }
     }
-}
 
-#endif
+  /* Release the heap part */
+
+  if (nxrmutex_lock(&info->ta_lock) >= 0)
+    {
+      if (aehead->nfuncs == 0)
+        {
+          lib_free(aehead->extra);
+          aehead->extra  = NULL;
+          aehead->nextra = 0;
+        }
+
+      nxrmutex_unlock(&info->ta_lock);
+    }
+}
 
 /****************************************************************************
  * Name: atexit
@@ -187,9 +235,7 @@ void atexit_call_exitfuncs(int status, bool quick)
  *
  *    Limitations in the current implementation:
  *
- *      1. Only a single atexit function can be registered unless
- *         CONFIG_LIBC_MAX_EXITFUNS defines a larger number.
- *      2. atexit functions are not inherited when a new task is
+ *      1. atexit functions are not inherited when a new task is
  *         created.
  *
  * Input Parameters:
@@ -241,9 +287,7 @@ int at_quick_exit(CODE void (*func)(void))
  *
  *    Limitations in the current implementation:
  *
- *      1. Only a single on_exit function can be registered unless
- *         CONFIG_LIBC_MAX_EXITFUNS defines a larger number.
- *      2. on_exit functions are not inherited when a new task is
+ *      1. on_exit functions are not inherited when a new task is
  *         created.
  *
  * Input Parameters:
@@ -262,6 +306,8 @@ int on_exit(CODE void (*func)(int, FAR void *), FAR void 
*arg)
                          NULL);
 }
 
+#endif /* CONFIG_LIBC_MAX_EXITFUNS > 0 */
+
 /****************************************************************************
  * Name: __cxa_atexit
  *
diff --git a/sched/tls/task_uninitinfo.c b/sched/tls/task_uninitinfo.c
index c8cb24eae98..bd104741109 100644
--- a/sched/tls/task_uninitinfo.c
+++ b/sched/tls/task_uninitinfo.c
@@ -193,6 +193,18 @@ void task_uninit_info(FAR struct task_group_s *group)
     }
 #endif
 
+#if CONFIG_LIBC_MAX_EXITFUNS > 0
+  /* Free the heap part of the exit function list (the task did not call
+   * exit())
+   */
+
+  if (info->ta_exit.extra != NULL)
+    {
+      group_free(group, info->ta_exit.extra);
+      info->ta_exit.extra = NULL;
+    }
+#endif
+
   /* Release all environment variables */
 
   env_release(info);

Reply via email to