[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>
signature.asc
Description: PGP signature
