New functions [v]aprintf(3)

Alejandro Colomar alx@kernel.org
Fri Mar 13 23:31:55 GMT 2026


[Adding libc-alpha@, and Joseph]

Hi all,

On 2026-03-12T15:19:03-0700, Paul Eggert wrote:
> On 2026-03-12 15:07, Alejandro Colomar wrote:
> > Paul, do you have an opinion?  Also, do you think we should propose
> > aprintf() for glibc before trying with WG14?
> 
> I'd rather avoid the stdc_ prefix as well. aprintf sounds good to me.
> 
> Can't hurt to propose it to glibc first. At least we should get a review.

I presented a proposal to the C Committee for adding the functions
[v]aprintf(3) in ISO C2y.  The most recent committee document is n3750
<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3750.txt>, although
I have a more updated version, which I've pasted at the bottom of this
email, which contains some minor wording fixes, and also has more
information about prior art.

They are similar to vasprintf(3), but have a simpler interface, which
for most users is enough, and much easier to use.  The complexity of
[v]asprintf(3) is notable, and it has resulted in different
implementations having slightly different behavior (mainly about what
happens on error), which can confuse programmers, and even cause
portaibility bugs.  aprintf(3) is as simple as it can be, and it is
based on strdup(3), with the prototype being:

	char *aprintf(const char *restrict fmt, ...);

The proposed pair of APIs, called [v]aprintf(3), are part of Plan9's
libc --where they call them [v]smprint(2), with the 'm' standing for
malloc()--, and also part of many projects.  gnulib has added it in
recent days, after I proposed it.

The committee showed strong interest in the API, with the vote results
being 14 yes, 4 no, and 3 abstentions, for taking n3750 with a different
name.  However, the committee couldn't agree on a name.  The vote on the
specific name was 10 yes, 8 no, and 2 abstentions.  The 'no's were
because they're worried that the name is not reserved, and that
implementations might not be able to take it.  Then people went on to
propose weird name such as strprintf(), and others that don't make much
sense.

Recently, when the _Countof() operator was standardized, I learnt that
naming is something that the C Committee isn't good for.  It would have
been called _Lengthof() if I hadn't insisted, and we'd be having bugs
because people confuse the length of a string with the length of the
buffer that holds it (which happens to be off by one.  So, this time I'd
rather come to major libc implementations and ask them to implement the
good name, thus showing the committee that it's something that can
actually be done.

I commented the other names that the committee had proposed, and gnulib
maintainers did strongly reject them, showing preference for
[v]aprintf(3), and immediately implementing it.  Now let's try with
glibc.  Can we please implement [v]aprintf(3) for the general public in
glibc?

I've pasted the most recent version of the proposal, which contains a
specification at the bottom.

I've already written a manual page for them, which I've also pasted
below the proposal.

What do you people think?  Should we add this pair of APIs?


Have a lovely night!
Alex

---
$ cat ./alx-0007.txt
Name
	alx-0007r9 - add a malloc(3)-based sprintf(3) variant

Principles
	-  Codify existing practice to address evident deficiencies.
	-  Enable secure programming

Category
	Standardize existing libc APIs

Author
	Alejandro Colomar <alx@kernel.org>

	Cc: Christopher Bazley <chris.bazley.wg14@gmail.com>
	Cc: Joseph Myers <josmyers@redhat.com>
	Cc: Robert Seacord <rcseacord@gmail.com>

History
	<https://www.alejandro-colomar.es/src/alx/alx/wg14/alx-0007.git/>

	r0 (2025-03-17):
	-  Initial draft.

	r1 (2025-05-05):
	-  tfix.
	-  ffix.
	-  Rebase on n3550.
	-  Add vaprintf().
	-  Add [v]awprintf().

	r2 (2025-06-01; n3575):
	-  Add more rationale to avoid asprintf(3), now that it's POSIX.

	r2-2 (2025-06-27):
	-  Use va_list in prototypes.
	-  Take into account inserted null bytes in the wording.

	r3 (2025-06-29):
	-  Merge r2 and r2-2.

	r4 (2025-07-02; n3630):
	-  Use va_copy(3) in the implementation of vaprintf().

	r5 (2025-07-04; n3660):
	-  Add paragraph about va_list in the v* variants.

	r6 (2025-09-05; n3750):
	-  Fix memory leak.
	-  Mention TR 24731-2.
	-  Define awprintf() in terms of swprintf(3) instead of
	   aprintf().

	r7 (2026-01-21):
	-  tfix
	-  Fix error handling (vsnprintf can return any negative int).

	r8 (2026-02-25):
	-  Fix bugs in implementation of vaprintf().
	-  Specify awprintf() in terms of aprintf() and swprintf(3),
	   instead of swprintf(3) and malloc(3).  The previous wording
	   was problematic, because swprintf(3) has a size argument
	   that the wording didn't take into account.
	-  CC Robert.

	r9 (2026-03-14):
	-  Document no limitation on INT_MAX.
	-  Document that while not being a reserved name, it should be
	   okay.
	-  Show other APIs with this or a similar name in existing
	   projects.

Rationale
	There's need for a strdup(3) variant that writes a formatted
	string as if by sprintf(3).  Or conversely, a sprintf(3) variant
	that allocates memory as if by malloc(3).

	Let's also add a wide-string version of it, even though there
	are no existing implementations of it (AFAICS).

Prior art
	Projects have come up with APIs for this over time.  GNU and the
	BSDs have it as asprintf(3), but there seems to be consensus
	that this API isn't very well designed; evidence of this is that
	the behavior is slightly different in the various
	implementations, and has changes through history.

	Another design is closer to strdup(3).  Plan9 has smprint(2),
	which behaves basically like strdup(3), except for formatting
	the string.  This API matches the internal APIs implemented in
	projects like the Linux kernel (kvasprintf()) and shadow-utils
	(aprintf()).  This one has the advantage that the attributes
	such as [[gnu::malloc(free)]] can be applied to it.

	It is common to use such APIs together with code that calls
	strdup(3).  Here's an example from shadow utils:

		src/userdel.c-1062-	if (prefix[0]) {
		src/userdel.c:1063:		user_home = xaprintf("%s/%s", prefix, pwd->pw_dir);
		src/userdel.c-1064-	} else {
		src/userdel.c-1065-		user_home = xstrdup(pwd->pw_dir);
		src/userdel.c-1066-	}

	This kind of code tends to favour the Plan9 API variant in
	comparison with the GNU variant which doesn't fit well in
	surrounding code.

	Here's an example implementation of the proposed API:

		[[gnu::malloc(free)]] [[gnu::format(printf, 1, 2)]]
		char *
		aprintf(const char *restrict fmt, ...)
		{
			char     *p;
			va_list  ap;

			va_start(ap, fmt);
			p = vaprintf(fmt, ap);
			va_end(ap);

			return p;
		}

		[[gnu::malloc(free)]] [[gnu::format(printf, 1, 0)]]
		char *
		vaprintf(const char *restrict fmt, va_list ap)
		{
			int      size, len;
			char     *p;
			va_list  ap2;

			va_copy(ap2, ap);
			len = vsnprintf(NUL, 0, fmt, ap2);
			va_end(ap2);
			if (len < 0 || len == INT_MAX)
				return NULL;

			size = len + 1;
			p = MALLOC(size, char);
			if (p == NULL)
				return NULL;

			if (vsnprintf(p, size, fmt, ap) < 0) {
				free(p);
				return NULL;
			}

			return p;
		}

	Another benefit of this API is that it is not limited to INT_MAX
	anymore.  The API is not limited by the EOVERFLOW error from
	POSIX's [v]asprintf(3), since there's no int return value.

Design choices
	-  Return the newly allocated array as in Plan9.

	-  Use the name aprintf().  smprintf() could be accidentally
	   misread as snprintf(), and one might forget that it
	   allocates.  Miswriting is less likely, since it has a
	   different number of arguments.

	   Also, it is common to have a leading 'a' in the name of
	   functions that allocate as if by a call to malloc(3).

	   While it is in use in several projects, most of them are
	   precisely with these semantics, so they only need to stop
	   defining it when using C2y.  In other cases, the prototype
	   being different would make the failure a hard error at
	   compile time, which is an acceptable consequence.

	   While aprintf(3) is not a reserved name, it's close-enough to
	   libc APIs, that programmers are expected to expect that libc
	   might eventually take that name.

	   Here are a few other projects that define it, with a similar
	   or exact name:

		  libxml2.9       char *trio_aprintf (const char *format, ...);
		  (copied into libxslt, wine)

		  wget            char *aprintf (const char *fmt, ...)

		  curl            char *curl_maprintf(const char *format, ...)
		  (copied into mysql-8.0)

		  zlib            char *aprintf(char *fmt, ...) [only an example]
		  (copied into tcl8.6, tcl9.0, boost1.83, boost1.88, binutils-gold)

	   The only different prototype exists in the 'rcs' package:

		  rcs             void aprintf (FILE *iop, char const *fmt, ...)
		  (Here the 'a' stands for aborting upon error.  It's
		   more common to use an 'x' for that meaning...)

	-  asprintf(3) (POSIX.1-2024; also TR 24731-2) may be
	   interesting to implement some C++ stuff optimally (it avoids
	   a strlen(3) call), but if implementations want it, they are
	   free to provide it as an implementation detail.  We don't
	   need to standardize an API that we wouldn't recommend using,
	   or programmers would do well blaming us for providing
	   dangerous APIs in the standard library.

Proposed wording
	Based on N3550.

    7.24.6  Input/output <stdio.h> :: Formatted input/output functions
	## New section after 7.24.6.7 ("The sprintf function"):

	+7.24.6.7+1  The <b>aprintf</b> function
	+
	+Synopsis
	+1	#include <stdio.h>
	+	char *aprintf(const char *restrict format, ...);
	+
	+Description
	+2	The <b>aprintf</b> function
	+	is equivalent to <b>sprintf</b>,
	+	except the output is written
	+	in a space allocated
	+	as if by a call to <b>malloc</b>.
	+
	+Returns
	+3	The <b>aprintf</b> function returns
	+	a pointer to the first character of the allocated space.
	+	The returned pointer can be passed to <b>free</b>.
	+	On error,
	+	the <b>aprintf</b> function returns a null pointer.

	## New section after 7.24.6.14 ("The vsprintf function"):

	+7.24.6.14+1  The <b>vaprintf</b> function
	+
	+Synopsis
	+1	#include <stdio.h>
	+	char *vaprintf(const char *restrict format, va_list arg);
	+
	+Description
	+2	The <b>vaprintf</b> function
	+	is equivalent to
	+	<b>aprintf</b>,
	+	with the varying argument list replaced by <tt>arg</tt>.
	+
	+3
	+	The <tt>va_list</tt> argument to this function
	+	shall have been initialized by the <b>va_start</b> macro
	+	(and possibly subsequent <b>va_arg</b> invocations).
	+	This function does not invoke the <b>va_end</b> macro.343)

    7.33.2  Formatted wide character input/output functions
	## New section after 7.33.2.4 ("The swprintf function"):

	+7.33.2.4+1  The <b>awprintf</b> function
	+
	+Synopsis
	+1	#include <wchar.h>
	+	wchar_t *awprintf(const wchar_t *restrict format, ...);
	+
	+Description
	+2	The <b>awprintf</b> function
	+	is equivalent to <b>aprintf</b>,
	+	except that it formats a wide string,
	+	as if by a call to <b>swprintf</b>.
	+
	+Returns
	+3	The <b>awprintf</b> function returns
	+	a pointer to the first wide character of the allocated space.
	+	The returned pointer can be passed to <b>free</b>.
	+	On error,
	+	the <b>awprintf</b> function returns a null pointer.

	## New section after 7.33.2.8 ("The vswprintf function"):

	+7.33.2.8+1  The <b>vawprintf</b> function
	+
	+Synopsis
	+1	#include <wchar.h>
	+	wchar_t *vawprintf(const wchar_t *restrict format, va_list arg);
	+
	+Description
	+2	The <b>vawprintf</b> function
	+	is equivalent to
	+	<b>awprintf</b>,
	+	with the varying argument list replaced by <tt>arg</tt>.
	+
	+3
	+	The <tt>va_list</tt> argument to this function
	+	shall have been initialized by the <b>va_start</b> macro
	+	(and possibly subsequent <b>va_arg</b> invocations).
	+	This function does not invoke the <b>va_end</b> macro.407)

---
$ MANWIDTH=72 man aprintf | cat
aprintf(3)              Library Functions Manual             aprintf(3)

NAME
     aprintf, vaprintf - allocate and print formatted string

LIBRARY
     gnulib - The GNU Portability Library

SYNOPSIS
     #include <stdio.h>

     char *aprintf(const char *restrict fmt, ...);
     char *vaprintf(const char *restrict fmt, va_list ap);

DESCRIPTION
     The  functions  aprintf() and vaprintf() are analogs of sprintf(3)
     and vsprintf(3), except that their output is written  in  a  space
     allocated as if by a call to malloc(3).

     This  pointer should be passed to free(3) to release the allocated
     storage when it is no longer needed.

RETURN VALUE
     On success, these functions return a pointer to the first  charac‐
     ter  of the formatted string.  On error, -1 is returned, and errno
     is set to indicate the error.

ERRORS
     See sprintf(3) and malloc(3).

ATTRIBUTES
     For an explanation of the terms used in this section, see  attrib‐
     utes(7).
     ┌───────────────────────────────┬───────────────┬────────────────┐
     │ Interface                     │ Attribute     │ Value          │
     ├───────────────────────────────┼───────────────┼────────────────┤
     │ aprintf(), vaprintf()         │ Thread safety │ MT‐Safe locale │
     └───────────────────────────────┴───────────────┴────────────────┘

STANDARDS
     None.

HISTORY
     gnulib 202607.

SEE ALSO
     free(3), malloc(3), sprintf(3), strdup(3), asprintf(3)

Linux man‐pages 6.17‐74‐g3f... 2026‐03‐11                    aprintf(3)

-- 
<https://www.alejandro-colomar.es>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://sourceware.org/pipermail/libc-alpha/attachments/20260314/6091e5ae/attachment-0001.sig>


More information about the Libc-alpha mailing list