Alejandro Colomar wrote: > We voted yesterday the proposal n3750 with the name aprintf(), and the > votes were 10-8-0 (yes-no-abstention). Most of those 'no' votes (if not > all) said it was just about the name. And the only strong reason they > brought was that they don't believe this is something that > implementations would find acceptable, not because of the name itself. > > So, if large implementations do add it, that would invalidate those > arguments.
It feels safe to add it to Gnulib, because — as shown — several other packages define aprintf() in the same way. In the worst case, WG14 will pick a different name, and Gnulib will then follow suit. 2026-03-11 Bruno Haible <[email protected]> vaprintf: Add tests. * tests/test-vaprintf.c: New file, based on tests/test-xvasprintf.c. * modules/vaprintf-tests: New file. vaprintf: New module. * lib/stdio.in.h (_GL_ATTRIBUTE_DEALLOC_FREE): New macro, copied from lib/string.in.h. (free, rpl_free): Define as in lib/string.in.h. (aprintf, vaprintf): New declarations. * lib/vaprintf.c: New file, based on lib/xvasprintf.c. * lib/aprintf.c: New file, based on lib/xasprintf.c. * m4/stdio_h.m4 (gl_STDIO_H_REQUIRE_DEFAULTS): Initialize GNULIB_VAPRINTF. Require gl_STDLIB_H_REQUIRE_DEFAULTS. * modules/stdio-h (Depends-on): Add stdlib-h. (Makefile.am): Substitute GNULIB_VAPRINTF, GNULIB_FREE_POSIX, REPLACE_FREE. * modules/vaprintf: New file.
>From 68143964a370b3a565d081d4d994aee772eaca00 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Wed, 11 Mar 2026 18:22:19 +0100 Subject: [PATCH 1/2] vaprintf: New module. * lib/stdio.in.h (_GL_ATTRIBUTE_DEALLOC_FREE): New macro, copied from lib/string.in.h. (free, rpl_free): Define as in lib/string.in.h. (aprintf, vaprintf): New declarations. * lib/vaprintf.c: New file, based on lib/xvasprintf.c. * lib/aprintf.c: New file, based on lib/xasprintf.c. * m4/stdio_h.m4 (gl_STDIO_H_REQUIRE_DEFAULTS): Initialize GNULIB_VAPRINTF. Require gl_STDLIB_H_REQUIRE_DEFAULTS. * modules/stdio-h (Depends-on): Add stdlib-h. (Makefile.am): Substitute GNULIB_VAPRINTF, GNULIB_FREE_POSIX, REPLACE_FREE. * modules/vaprintf: New file. --- ChangeLog | 16 ++++++++ lib/aprintf.c | 32 +++++++++++++++ lib/stdio.in.h | 78 ++++++++++++++++++++++++++++++++++++ lib/vaprintf.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++ m4/stdio_h.m4 | 5 ++- modules/stdio-h | 4 ++ modules/vaprintf | 31 +++++++++++++++ 7 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 lib/aprintf.c create mode 100644 lib/vaprintf.c create mode 100644 modules/vaprintf diff --git a/ChangeLog b/ChangeLog index 6dd220d9f7..a597434c63 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2026-03-11 Bruno Haible <[email protected]> + + vaprintf: New module. + * lib/stdio.in.h (_GL_ATTRIBUTE_DEALLOC_FREE): New macro, copied from + lib/string.in.h. + (free, rpl_free): Define as in lib/string.in.h. + (aprintf, vaprintf): New declarations. + * lib/vaprintf.c: New file, based on lib/xvasprintf.c. + * lib/aprintf.c: New file, based on lib/xasprintf.c. + * m4/stdio_h.m4 (gl_STDIO_H_REQUIRE_DEFAULTS): Initialize + GNULIB_VAPRINTF. Require gl_STDLIB_H_REQUIRE_DEFAULTS. + * modules/stdio-h (Depends-on): Add stdlib-h. + (Makefile.am): Substitute GNULIB_VAPRINTF, GNULIB_FREE_POSIX, + REPLACE_FREE. + * modules/vaprintf: New file. + 2026-03-11 Bruno Haible <[email protected]> xvasprintf-tests: Fix typo. diff --git a/lib/aprintf.c b/lib/aprintf.c new file mode 100644 index 0000000000..aad64eaf6e --- /dev/null +++ b/lib/aprintf.c @@ -0,0 +1,32 @@ +/* Simplified variant of vasprintf and asprintf. + Copyright (C) 1999, 2002-2004, 2006, 2009-2026 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 <stdio.h> + +char * +aprintf (const char *format, ...) +{ + va_list args; + va_start (args, format); + char *result = vaprintf (format, args); + va_end (args); + + return result; +} diff --git a/lib/stdio.in.h b/lib/stdio.in.h index 33b0b8e48a..4028bfa4fa 100644 --- a/lib/stdio.in.h +++ b/lib/stdio.in.h @@ -122,6 +122,20 @@ # endif #endif +/* _GL_ATTRIBUTE_DEALLOC_FREE declares that the function returns pointers that + can be freed via 'free'; it can be used only after declaring 'free'. */ +/* Applies to: functions. Cannot be used on inline functions. */ +#ifndef _GL_ATTRIBUTE_DEALLOC_FREE +# if defined __cplusplus && defined __GNUC__ && !defined __clang__ +/* Work around GCC bug <https://gcc.gnu.org/PR108231> */ +# define _GL_ATTRIBUTE_DEALLOC_FREE \ + _GL_ATTRIBUTE_DEALLOC ((void (*) (void *)) free, 1) +# else +# define _GL_ATTRIBUTE_DEALLOC_FREE \ + _GL_ATTRIBUTE_DEALLOC (free, 1) +# endif +#endif + /* The __attribute__ feature is available in gcc versions 2.5 and later. The __-protected variants of the attributes 'format' and 'printf' are accepted by gcc versions 2.6.4 (effectively 2.7) and later. @@ -231,6 +245,50 @@ /* The definition of _GL_WARN_ON_USE is copied here. */ +/* Make _GL_ATTRIBUTE_DEALLOC_FREE work, even though <stdlib.h> may not have + been included yet. */ +#if @GNULIB_FREE_POSIX@ +# if (@REPLACE_FREE@ && !defined free \ + && !(defined __cplusplus && defined GNULIB_NAMESPACE)) +/* We can't do '#define free rpl_free' here. */ +# if defined __cplusplus && (__GLIBC__ + (__GLIBC_MINOR__ >= 14) > 2) +_GL_EXTERN_C void rpl_free (void *) _GL_ATTRIBUTE_NOTHROW; +# else +_GL_EXTERN_C void rpl_free (void *); +# endif +# undef _GL_ATTRIBUTE_DEALLOC_FREE +# define _GL_ATTRIBUTE_DEALLOC_FREE _GL_ATTRIBUTE_DEALLOC (rpl_free, 1) +# else +# if defined _MSC_VER && !defined free +_GL_EXTERN_C +# if defined _DLL + __declspec (dllimport) +# endif + void __cdecl free (void *); +# else +# if defined __cplusplus && (__GLIBC__ + (__GLIBC_MINOR__ >= 14) > 2) +_GL_EXTERN_C void free (void *) _GL_ATTRIBUTE_NOTHROW; +# else +_GL_EXTERN_C void free (void *); +# endif +# endif +# endif +#else +# if defined _MSC_VER && !defined free +_GL_EXTERN_C +# if defined _DLL + __declspec (dllimport) +# endif + void __cdecl free (void *); +# else +# if defined __cplusplus && (__GLIBC__ + (__GLIBC_MINOR__ >= 14) > 2) +_GL_EXTERN_C void free (void *) _GL_ATTRIBUTE_NOTHROW; +# else +_GL_EXTERN_C void free (void *); +# endif +# endif +#endif + /* Macros for stringification. */ #define _GL_STDIO_STRINGIZE(token) #token #define _GL_STDIO_MACROEXPAND_AND_STRINGIZE(token) _GL_STDIO_STRINGIZE(token) @@ -1808,6 +1866,26 @@ _GL_CXXALIAS_SYS (vasprintf, int, _GL_CXXALIASWARN (vasprintf); #endif +#if @GNULIB_VAPRINTF@ +/* Write formatted output to a string dynamically allocated with malloc(). + Return the resulting string. Upon memory allocation error, or some + other error, return NULL, with errno set. */ +_GL_FUNCDECL_SYS (aprintf, char *, + (const char *format, ...), + _GL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (1, 2) + _GL_ARG_NONNULL ((1)) + _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE); +_GL_CXXALIAS_SYS (aprintf, char *, + (const char *format, ...)); +_GL_FUNCDECL_SYS (vaprintf, char *, + (const char *format, va_list args), + _GL_ATTRIBUTE_FORMAT_PRINTF_STANDARD (1, 0) + _GL_ARG_NONNULL ((1)) + _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE); +_GL_CXXALIAS_SYS (vaprintf, char *, + (const char *format, va_list args)); +#endif + #if @GNULIB_VDZPRINTF@ /* Prints formatted output to file descriptor FD. Returns the number of bytes written to the file descriptor. Upon diff --git a/lib/vaprintf.c b/lib/vaprintf.c new file mode 100644 index 0000000000..58147e1ce1 --- /dev/null +++ b/lib/vaprintf.c @@ -0,0 +1,101 @@ +/* Simplified variant of vasprintf and asprintf. + Copyright (C) 1999, 2002-2004, 2006-2026 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 <stdio.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +/* Checked size_t computations. */ +#include "xsize.h" + +static char * +astrcat (size_t argcount, va_list args) +{ + /* Determine the total size. */ + size_t totalsize = 0; + { + va_list ap; + va_copy (ap, args); + for (size_t i = argcount; i > 0; i--) + { + const char *next = va_arg (ap, const char *); + totalsize = xsum (totalsize, strlen (next)); + } + va_end (ap); + } + + /* Test for overflow in the summing pass above or in (totalsize + 1) + below. */ + if (totalsize == SIZE_MAX) + { + errno = ENOMEM; + return NULL; + } + + /* Allocate and fill the result string. */ + char *result = (char *) malloc (totalsize + 1); + if (result == NULL) + return NULL; + { + char *p = result; + for (size_t i = argcount; i > 0; i--) + { + const char *next = va_arg (args, const char *); + size_t len = strlen (next); + memcpy (p, next, len); + p += len; + } + *p = '\0'; + } + + return result; +} + +char * +vaprintf (const char *format, va_list args) +{ + /* Recognize the special case format = "%s...%s". It is a frequently used + idiom for string concatenation and needs to be fast. We don't want to + have a separate function astrcat() for this purpose. */ + { + size_t argcount = 0; + + for (const char *f = format;;) + { + if (*f == '\0') + /* Recognized the special case of string concatenation. */ + return astrcat (argcount, args); + if (*f != '%') + break; + f++; + if (*f != 's') + break; + f++; + argcount++; + } + } + + char *result; + if (vaszprintf (&result, format, args) < 0) + return NULL; + + return result; +} diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4 index 9d4126f586..0be1fd98ea 100644 --- a/m4/stdio_h.m4 +++ b/m4/stdio_h.m4 @@ -1,5 +1,5 @@ # stdio_h.m4 -# serial 75 +# serial 76 dnl Copyright (C) 2007-2026 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -182,6 +182,7 @@ AC_DEFUN([gl_STDIO_H_REQUIRE_DEFAULTS] gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_STDIO_H_SIGPIPE]) gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_SZPRINTF]) gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_TMPFILE]) + gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_VAPRINTF]) gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_VASPRINTF]) gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_VASZPRINTF]) gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_VFSCANF]) @@ -208,6 +209,8 @@ AC_DEFUN([gl_STDIO_H_REQUIRE_DEFAULTS] gl_MODULE_INDICATOR_INIT_VARIABLE([GNULIB_MDA_TEMPNAM], [1]) ]) m4_require(GL_MODULE_INDICATOR_PREFIX[_STDIO_H_MODULE_INDICATOR_DEFAULTS]) + dnl Make sure the shell variable for GNULIB_FREE_POSIX is initialized. + gl_STDLIB_H_REQUIRE_DEFAULTS AC_REQUIRE([gl_STDIO_H_DEFAULTS]) ]) diff --git a/modules/stdio-h b/modules/stdio-h index 51940a8a8d..cc2f8f2a50 100644 --- a/modules/stdio-h +++ b/modules/stdio-h @@ -17,6 +17,7 @@ snippet/c++defs snippet/warn-on-use ssize_t stddef-h +stdlib-h sys_types-h stdio-windows @@ -117,6 +118,7 @@ stdio.h: stdio.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) -e 's/@''GNULIB_STDIO_H_SIGPIPE''@/$(GNULIB_STDIO_H_SIGPIPE)/g' \ -e 's/@''GNULIB_SZPRINTF''@/$(GNULIB_SZPRINTF)/g' \ -e 's/@''GNULIB_TMPFILE''@/$(GNULIB_TMPFILE)/g' \ + -e 's/@''GNULIB_VAPRINTF''@/$(GNULIB_VAPRINTF)/g' \ -e 's/@''GNULIB_VASPRINTF''@/$(GNULIB_VASPRINTF)/g' \ -e 's/@''GNULIB_VASZPRINTF''@/$(GNULIB_VASZPRINTF)/g' \ -e 's/@''GNULIB_VDPRINTF''@/$(GNULIB_VDPRINTF)/g' \ @@ -140,6 +142,7 @@ stdio.h: stdio.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) -e 's/@''GNULIB_MDA_GETW''@/$(GNULIB_MDA_GETW)/g' \ -e 's/@''GNULIB_MDA_PUTW''@/$(GNULIB_MDA_PUTW)/g' \ -e 's/@''GNULIB_MDA_TEMPNAM''@/$(GNULIB_MDA_TEMPNAM)/g' \ + -e 's/@''GNULIB_FREE_POSIX''@/$(GNULIB_FREE_POSIX)/g' \ < $(srcdir)/stdio.in.h > $@-t1 $(AM_V_at)sed \ -e 's|@''HAVE_DECL_FCLOSEALL''@|$(HAVE_DECL_FCLOSEALL)|g' \ @@ -171,6 +174,7 @@ stdio.h: stdio.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) -e 's|@''REPLACE_FOPEN_FOR_FOPEN_GNU''@|$(REPLACE_FOPEN_FOR_FOPEN_GNU)|g' \ -e 's|@''REPLACE_FPRINTF''@|$(REPLACE_FPRINTF)|g' \ -e 's|@''REPLACE_FPURGE''@|$(REPLACE_FPURGE)|g' \ + -e 's|@''REPLACE_FREE''@|$(REPLACE_FREE)|g' \ -e 's|@''REPLACE_FREOPEN''@|$(REPLACE_FREOPEN)|g' \ -e 's|@''REPLACE_FSEEK''@|$(REPLACE_FSEEK)|g' \ -e 's|@''REPLACE_FSEEKO''@|$(REPLACE_FSEEKO)|g' \ diff --git a/modules/vaprintf b/modules/vaprintf new file mode 100644 index 0000000000..0ec945c95d --- /dev/null +++ b/modules/vaprintf @@ -0,0 +1,31 @@ +Description: +Simplified variant of vasprintf and asprintf. + +Files: +lib/vaprintf.c +lib/aprintf.c + +Depends-on: +stdio-h +vaszprintf +malloc-posix +xsize +stdarg-h +errno-h + +configure.ac: +gl_STDIO_MODULE_INDICATOR([vaprintf]) +m4_ifdef([AM_XGETTEXT_OPTION], + [AM_][XGETTEXT_OPTION([--flag=aprintf:1:c-format])]) + +Makefile.am: +lib_SOURCES += vaprintf.c aprintf.c + +Include: +<stdio.h> + +License: +LGPLv2+ + +Maintainer: +all -- 2.52.0
>From 471bf820b4844721fd7176165717d49eead0b8a2 Mon Sep 17 00:00:00 2001 From: Bruno Haible <[email protected]> Date: Wed, 11 Mar 2026 18:23:43 +0100 Subject: [PATCH 2/2] vaprintf: Add tests. * tests/test-vaprintf.c: New file, based on tests/test-xvasprintf.c. * modules/vaprintf-tests: New file. --- ChangeLog | 4 ++ modules/vaprintf-tests | 13 ++++ tests/test-vaprintf.c | 133 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 modules/vaprintf-tests create mode 100644 tests/test-vaprintf.c diff --git a/ChangeLog b/ChangeLog index a597434c63..71d77dc037 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2026-03-11 Bruno Haible <[email protected]> + vaprintf: Add tests. + * tests/test-vaprintf.c: New file, based on tests/test-xvasprintf.c. + * modules/vaprintf-tests: New file. + vaprintf: New module. * lib/stdio.in.h (_GL_ATTRIBUTE_DEALLOC_FREE): New macro, copied from lib/string.in.h. diff --git a/modules/vaprintf-tests b/modules/vaprintf-tests new file mode 100644 index 0000000000..1405b56b12 --- /dev/null +++ b/modules/vaprintf-tests @@ -0,0 +1,13 @@ +Files: +tests/test-vaprintf.c +tests/macros.h + +Depends-on: +streq + +configure.ac: + +Makefile.am: +TESTS += test-vaprintf +check_PROGRAMS += test-vaprintf + diff --git a/tests/test-vaprintf.c b/tests/test-vaprintf.c new file mode 100644 index 0000000000..21f0c6e1d5 --- /dev/null +++ b/tests/test-vaprintf.c @@ -0,0 +1,133 @@ +/* Test of vaprintf() and aprintf() functions. + Copyright (C) 2007-2026 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/>. */ + +/* Written by Bruno Haible <[email protected]>, 2026. */ + +#include <config.h> + +/* Tell GCC not to warn about the specific edge cases tested here. */ +#if _GL_GNUC_PREREQ (4, 3) +# pragma GCC diagnostic ignored "-Wformat-zero-length" +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +# pragma GCC diagnostic ignored "-Wformat-security" +#endif + +#include <stdio.h> + +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> + +#include "macros.h" + +static char * +my_aprintf (const char *format, ...) +{ + va_list args; + char *ret; + + va_start (args, format); + ret = vaprintf (format, args); + va_end (args); + return ret; +} + +static void +test_vaprintf (void) +{ + char *result; + + for (int repeat = 0; repeat <= 8; repeat++) + { + result = my_aprintf ("%d", 12345); + ASSERT (result != NULL); + ASSERT (streq (result, "12345")); + free (result); + } + + { + /* Silence gcc warning about zero-length format string. */ + const char *empty = ""; + result = my_aprintf (empty); + ASSERT (result != NULL); + ASSERT (streq (result, "")); + free (result); + } + + result = my_aprintf ("%s", "foo"); + ASSERT (result != NULL); + ASSERT (streq (result, "foo")); + free (result); + + result = my_aprintf ("%s%s", "foo", "bar"); + ASSERT (result != NULL); + ASSERT (streq (result, "foobar")); + free (result); + + result = my_aprintf ("%s%sbaz", "foo", "bar"); + ASSERT (result != NULL); + ASSERT (streq (result, "foobarbaz")); + free (result); +} + +static void +test_aprintf (void) +{ + char *result; + + for (int repeat = 0; repeat <= 8; repeat++) + { + result = aprintf ("%d", 12345); + ASSERT (result != NULL); + ASSERT (streq (result, "12345")); + free (result); + } + + { + /* Silence gcc warning about zero-length format string, + and about "format not a string literal and no format" + (whatever that means) . */ + const char *empty = ""; + result = aprintf (empty, empty); + ASSERT (result != NULL); + ASSERT (streq (result, "")); + free (result); + } + + result = aprintf ("%s", "foo"); + ASSERT (result != NULL); + ASSERT (streq (result, "foo")); + free (result); + + result = aprintf ("%s%s", "foo", "bar"); + ASSERT (result != NULL); + ASSERT (streq (result, "foobar")); + free (result); + + result = aprintf ("%s%sbaz", "foo", "bar"); + ASSERT (result != NULL); + ASSERT (streq (result, "foobarbaz")); + free (result); +} + +int +main (_GL_UNUSED int argc, char *argv[]) +{ + test_vaprintf (); + test_aprintf (); + + return test_exit_status; +} -- 2.52.0
