For a long time, dynamic initializations in C (via GCC's
__attribute__ ((__constructor__))) were deemed unportable.

By now, only old or toy compilers (like AIX xlc or Tiny CC) don't
support. Otherwise, it's nicely portably to all platforms (with
some hacks for MSVC, though).

Here's a module that support for this. Partially taken from GLib 2.

A word of a warning: It's usually best to avoid it nevertheless,
for example through a 'call_once' when possible.


2025-02-18  Bruno Haible  <br...@clisp.org>

        at-init: Add tests.
        * tests/test-at-init.sh: New file.
        * tests/test-at-init.c: New file.
        * tests/test-at-init-2.c: New file.
        * tests/test-at-init-3.c: New file.
        * modules/at-init-tests: New file.

        at-init: New module.
        * lib/at-init.h: New file.
        * lib/at-init.c: New file.
        * modules/at-init: New file.

>From 2ae8e3fac91e25f3ecf318471ee401b52e28cfeb Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 18 Feb 2025 12:01:59 +0100
Subject: [PATCH 1/2] at-init: New module.

* lib/at-init.h: New file.
* lib/at-init.c: New file.
* modules/at-init: New file.
---
 ChangeLog       |   7 +++
 lib/at-init.c   |  33 ++++++++++++
 lib/at-init.h   | 141 ++++++++++++++++++++++++++++++++++++++++++++++++
 modules/at-init |  22 ++++++++
 4 files changed, 203 insertions(+)
 create mode 100644 lib/at-init.c
 create mode 100644 lib/at-init.h
 create mode 100644 modules/at-init

diff --git a/ChangeLog b/ChangeLog
index fb902df384..ed82d50346 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2025-02-18  Bruno Haible  <br...@clisp.org>
+
+	at-init: New module.
+	* lib/at-init.h: New file.
+	* lib/at-init.c: New file.
+	* modules/at-init: New file.
+
 2025-02-17  Paul Eggert  <egg...@cs.ucla.edu>
 
 	fts: expose fts_debug
diff --git a/lib/at-init.c b/lib/at-init.c
new file mode 100644
index 0000000000..2e9e8702a4
--- /dev/null
+++ b/lib/at-init.c
@@ -0,0 +1,33 @@
+/* Computed initializations before the program starts.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "at-init.h"
+
+#if defined _MSC_VER
+
+void
+gl_at_init_dummy (_GL_UNUSED const void *p)
+{
+}
+
+#endif
+
+/* This declaration is solely to ensure that after preprocessing
+   this file is never empty.  */
+typedef int dummy;
diff --git a/lib/at-init.h b/lib/at-init.h
new file mode 100644
index 0000000000..95ec026ca3
--- /dev/null
+++ b/lib/at-init.h
@@ -0,0 +1,141 @@
+/* Computed initializations before the program starts.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+   Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef _GL_AT_INIT_H
+#define _GL_AT_INIT_H
+
+
+/* AT_INIT (function);
+   registers a function to be executed when the program is initialized, i.e.
+   even before the main() function is entered.
+
+   AT_FINI (function);
+   registers a function to be executed when the program is about to exit.
+   Similar to 'atexit (function)' or 'at_quick_exit (function)'.
+
+   The function must have an empty argument list and return 'void'.
+
+   An initialization function must not depend on the result of other
+   initialization functions.  The order in which the initialization functions
+   are executed is unspecified.
+
+   These macro invocations must *precede* the definition of the function.
+   (This is necessary for clang.)
+
+   These macros apply to executables.  They are not supported in shared
+   libraries.
+
+   Note: It would be easy to achieve the same functionality by using a small
+   C++ compilation unit.  But it's better to stay with C all the way.  */
+
+/* This is implemented for each compiler separately.  Basically, the idea is
+   to look how the corresponding C++ compiler implements static initializations
+   of C++ variables with a non-POD type, and do the same thing in C through a
+   mix of pragmas and asms.  */
+/* For clang on native Windows (which defines both __clang__ and _MSC_VER):
+     - Both definitions of AT_INIT work.
+     - Only the .CRT$XCU based definition of AT_FINI works; the __destructor__
+       based definition of AT_FINI does not work.  */
+#if defined __GNUC__ || (defined __clang__ && !defined _MSC_VER)
+/* GCC and compatible compilers */
+
+/* Documentation:
+   <https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html>  */
+
+# define AT_INIT(function) \
+    static void __attribute__ ((__constructor__)) function (void)
+# define AT_FINI(function) \
+    static void __attribute__ ((__destructor__)) function (void)
+
+#elif defined _MSC_VER
+/* MSVC */
+
+/* Taken from glib-2.83 gconstructor.h.
+   Documentation:
+   <https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization>
+   See also <https://stackoverflow.com/questions/1113409/>.  */
+
+/* Avoid an error in the pragma below.  */
+# undef read
+
+# ifdef _WIN64
+#  define GL_MSVC_SYMBOL_PREFIX ""
+# else
+#  define GL_MSVC_SYMBOL_PREFIX "_"
+# endif
+
+# define AT_INIT(function) GL_MSVC_CTOR (function, GL_MSVC_SYMBOL_PREFIX)
+# define AT_FINI(function) GL_MSVC_DTOR (function, GL_MSVC_SYMBOL_PREFIX)
+
+# define GL_MSVC_CTOR(function,_sym_prefix) \
+  static void function(void); \
+  extern int (* _array ## function)(void); \
+  int function ## _wrapper(void); \
+  int function ## _wrapper(void) { function(); gl_at_init_dummy (_array ## function); return 0; } \
+  __pragma(comment(linker,"/include:" _sym_prefix # function "_wrapper")) \
+  __pragma(section(".CRT$XCU",read)) \
+  __declspec(allocate(".CRT$XCU")) int (* _array ## function)(void) = function ## _wrapper
+
+# define GL_MSVC_DTOR(function,_sym_prefix) \
+  static void function(void); \
+  extern int (* _array ## function)(void); \
+  int function ## _constructor(void); \
+  int function ## _constructor(void) { atexit (function); gl_at_init_dummy (_array ## function); return 0; } \
+   __pragma(comment(linker,"/include:" _sym_prefix # function "_constructor")) \
+  __pragma(section(".CRT$XCU",read)) \
+  __declspec(allocate(".CRT$XCU")) int (* _array ## function)(void) = function ## _constructor
+
+extern void gl_at_init_dummy (const void *);
+
+#elif defined __SUNPRO_C
+/* Sun C */
+
+/* Needs also: #pragma init(function)  */
+# define AT_INIT(function) \
+    static void function (void)
+
+/* Needs also: #pragma fini(function)  */
+# define AT_FINI(function) \
+    static void function (void)
+
+#elif defined __xlC__
+/* AIX xlc */
+
+/* Needs also: #pragma init(function)
+   and a C++ linker.  See
+   <https://www.ibm.com/docs/de/xl-c-and-cpp-aix/13.1.0?topic=descriptions-pragma-init-only>  */
+# define AT_INIT(function) \
+    static void function (void)
+
+/* Needs also: #pragma fini(function)
+   and a C++ linker.  See
+   <https://www.ibm.com/docs/de/xl-c-and-cpp-aix/13.1.0?topic=descriptions-pragma-fini-only>  */
+# define AT_FINI(function) \
+    static void function (void)
+
+#else
+/* Another compiler */
+
+/* This is just a dummy.  It will not work.  */
+# define AT_INIT(function) \
+    static void function (void)
+# define AT_FINI(function) \
+    static void function (void)
+
+#endif
+
+#endif /* _GL_AT_INIT_H */
diff --git a/modules/at-init b/modules/at-init
new file mode 100644
index 0000000000..95296ea39c
--- /dev/null
+++ b/modules/at-init
@@ -0,0 +1,22 @@
+Description:
+Computed initializations before the program starts
+
+Files:
+lib/at-init.h
+lib/at-init.c
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+lib_SOURCES += at-init.c
+
+Include:
+"at-init.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.43.0

>From 4cba83c6240dbf85dfcd663953f558b4ef2920ae Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 18 Feb 2025 12:03:37 +0100
Subject: [PATCH 2/2] at-init: Add tests.

* tests/test-at-init.sh: New file.
* tests/test-at-init.c: New file.
* tests/test-at-init-2.c: New file.
* tests/test-at-init-3.c: New file.
* modules/at-init-tests: New file.
---
 ChangeLog              |  7 +++++
 modules/at-init-tests  | 15 ++++++++++
 tests/test-at-init-2.c | 43 +++++++++++++++++++++++++++++
 tests/test-at-init-3.c | 43 +++++++++++++++++++++++++++++
 tests/test-at-init.c   | 62 ++++++++++++++++++++++++++++++++++++++++++
 tests/test-at-init.sh  |  9 ++++++
 6 files changed, 179 insertions(+)
 create mode 100644 modules/at-init-tests
 create mode 100644 tests/test-at-init-2.c
 create mode 100644 tests/test-at-init-3.c
 create mode 100644 tests/test-at-init.c
 create mode 100755 tests/test-at-init.sh

diff --git a/ChangeLog b/ChangeLog
index ed82d50346..fd7b76b910 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
 2025-02-18  Bruno Haible  <br...@clisp.org>
 
+	at-init: Add tests.
+	* tests/test-at-init.sh: New file.
+	* tests/test-at-init.c: New file.
+	* tests/test-at-init-2.c: New file.
+	* tests/test-at-init-3.c: New file.
+	* modules/at-init-tests: New file.
+
 	at-init: New module.
 	* lib/at-init.h: New file.
 	* lib/at-init.c: New file.
diff --git a/modules/at-init-tests b/modules/at-init-tests
new file mode 100644
index 0000000000..5bf6e73ac1
--- /dev/null
+++ b/modules/at-init-tests
@@ -0,0 +1,15 @@
+Files:
+tests/test-at-init.sh
+tests/test-at-init.c
+tests/test-at-init-2.c
+tests/test-at-init-3.c
+
+Depends-on:
+write
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-at-init.sh
+check_PROGRAMS += test-at-init
+test_at_init_SOURCES = test-at-init.c test-at-init-2.c test-at-init-3.c
diff --git a/tests/test-at-init-2.c b/tests/test-at-init-2.c
new file mode 100644
index 0000000000..9b0ca7f6e2
--- /dev/null
+++ b/tests/test-at-init-2.c
@@ -0,0 +1,43 @@
+/* Test of computed initialization.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "at-init.h"
+
+static int sum_of_squares;
+
+AT_INIT (init_squares);
+#ifdef __SUNPRO_C
+# pragma init (init_squares)
+#endif
+
+static void
+init_squares (void)
+{
+  int i;
+
+  sum_of_squares = 0;
+  for (i = 0; i <= 100; i++)
+    sum_of_squares += i * i;
+}
+
+int
+get_squares (void)
+{
+  return sum_of_squares;
+}
diff --git a/tests/test-at-init-3.c b/tests/test-at-init-3.c
new file mode 100644
index 0000000000..57c39f2e02
--- /dev/null
+++ b/tests/test-at-init-3.c
@@ -0,0 +1,43 @@
+/* Test of computed initialization.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "at-init.h"
+
+static int sum_of_cubes;
+
+AT_INIT (init_cubes);
+#ifdef __SUNPRO_C
+# pragma init (init_cubes)
+#endif
+
+static void
+init_cubes (void)
+{
+  int i;
+
+  sum_of_cubes = 0;
+  for (i = 0; i <= 100; i++)
+    sum_of_cubes += i * i *i;
+}
+
+int
+get_cubes (void)
+{
+  return sum_of_cubes;
+}
diff --git a/tests/test-at-init.c b/tests/test-at-init.c
new file mode 100644
index 0000000000..238a54d1df
--- /dev/null
+++ b/tests/test-at-init.c
@@ -0,0 +1,62 @@
+/* Test of computed initialization.
+   Copyright (C) 2025 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include "at-init.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Avoid an error in the pragma on MSVC.  */
+#undef read
+
+extern int get_squares (void);
+extern int get_cubes (void);
+
+AT_INIT (initial_message);
+#ifdef __SUNPRO_C
+# pragma init (initial_message)
+#endif
+
+static void
+initial_message (void)
+{
+  const char message[] = "Initializing...";
+  write (STDOUT_FILENO, message, strlen (message));
+}
+
+AT_FINI (final_message);
+#ifdef __SUNPRO_C
+# pragma fini (final_message)
+#endif
+
+static void
+final_message (void)
+{
+  const char message[] = "Finishing...";
+  write (STDOUT_FILENO, message, strlen (message));
+}
+
+int
+main ()
+{
+  char message[100];
+  sprintf (message, "Main...%d...%d...", get_squares (), get_cubes ());
+  write (STDOUT_FILENO, message, strlen (message));
+}
diff --git a/tests/test-at-init.sh b/tests/test-at-init.sh
new file mode 100755
index 0000000000..1f078344de
--- /dev/null
+++ b/tests/test-at-init.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+output=`${CHECKER} ./test-at-init${EXEEXT}`
+if test "$output" != 'Initializing...Main...338350...25502500...Finishing...'; then
+  echo "Got output: $output" 1>&2
+  exit 1
+fi
+
+exit 0
-- 
2.43.0

Reply via email to