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