[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 <[email protected]>

        Cc: Christopher Bazley <[email protected]>
        Cc: Joseph Myers <[email protected]>
        Cc: Robert Seacord <[email protected]>

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>

Attachment: signature.asc
Description: PGP signature

Reply via email to