Name n3575 - 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 Cc: Christopher Bazley Cc: Joseph Myers History r0 (2025-03-17): - Initial draft. r1 (2025-05-05): - tfix. - ffix. - Rebase on n3550. - Add vaprintf(). - Add [v]awprintf(). r2(2025-06-01): - Add more rationale to avoid asprintf(3), now that it's POSIX. Description 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. POSIX.1-2024 has standardized this API. 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) { char *p; size_t size, len; len = vsnprintf(NUL, 0, fmt, ap); if (len == -1) return NULL; size = len + 1; p = MALLOC(size, char); if (p == NULL) return NULL; if (vsnprintf(p, size, fmt, ap) == -1) return NULL; return p; } 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). - asprintf(3) (POSIX.1-2024) 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 :: Formatted input/output functions ## New section after 7.24.6.7 ("The sprintf function"): +7.24.6.7+1 The aprintf function + +Synopsis +1 #include + char *aprintf(const char *restrict format, ...); + +Description +2 The aprintf function + is equivalent to sprintf, + except the output is written + in a space allocated + as if by a call to malloc. + +Returns +3 The aprintf function returns + a pointer to the first character of the new string. + The returned pointer can be passed to free. + On error, + the aprintf function returns a null pointer. ## New section after 7.24.6.14 ("The vsprintf function"): +7.24.6.14+1 The vaprintf function + +Synopsis +1 #include + char *vaprintf(const char *restrict format, ...); + +Description +2 The vaprintf function + is equivalent to + aprintf, + with the varying argument list replaced by arg. 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 awprintf function + +Synopsis +1 #include + wchar_t *awprintf(const wchar_t *restrict format, ...); + +Description +2 The awprintf function + is equivalent to + aprintf, + except that it handles wide strings. ## New section after 7.33.2.8 ("The vswprintf function"): +7.33.2.8+1 The vawprintf function + +Synopsis +1 #include + wchar_t *vawprintf(const wchar_t *restrict format, ...); + +Description +2 The vawprintf function + is equivalent to + awprintf, + with the varying argument list replaced by arg.