Robust, portable implementation of asprintf()
, vasprintf()
.
Thoroughly tested.
- 🚀 Fast - performance akin other implementations
- 📦 Simple - zero dependencies, lightweight
(
37 lines
,566 bytes
) and ISO C99 compatible. - 🔒 Robust - safety-first, with substantial unit testing.
- ⚙️ Compatible - interop across different systems, drop-in
asprintf
,vasprintf
replacement for glibc, *BSD libc, musl libc and many more.
Wanted to have a version of asprintf
that is simple, robust and just works
everywhere. Needed it to be backward-compatible with existing libc
libraries,
consistent across diverse platforms and systems. It should also focus on
correctness, minimize unexpected behavior, be thoroughly tested, be easy to read
and stick to both C99 and POSIX.1-2024.
This process also gave me the opportunity to review existing implementations and deepen my understanding of pointers and systems programming.
Important
asprintf()
and vasprintf()
are now included in
POSIX.1-2024, following Austin Group Bug
#1496. You can read the
latest version at posix-asprintf. For systems supporting POSIX.1-2024
or
later, Neved4/asprintf
is no longer necessary. For systems limited to
POSIX.1-2017
or under, it offers a practical solution.
If you are building asprintf
, ensure you have:
If you have clib installed, run:
$ clib install Neved4/asprintf
If you don't have the above, start by cloning the repository:
$ git clone https://github.com/Neved4/asprintf
Once you've cloned the repository, build by executing:
$ make asprintf
Alternatively, if you have zig
:
$ zig cc asprintf.c -t asprintf
// asprintf, vasprintf - print to allocated string
#include "asprintf.h"
int asprintf(char **strp, const char *fmt, ...);
int vasprintf(char **strp, const char *fmt, va_list ap);
Consider a getconf()
function to retrieve a config
specified path, that
supports both XDG_CONFIG_HOME
and fallbacks:
char *getconf() {
const char *file = "tz.conf", *home = getenv("HOME"),
*xdg_config_home = getenv("XDG_CONFIG_HOME");
char *config = NULL;
// Path building logic
return config;
}
After which we'll have to lay down our path building logic.
if (access("tz.conf", F_OK) != -1) {
config = strdup("tz.conf");
} else if (xdg_config_home) {
size_t len = strlen(xdg_config_home) + strlen("twc") + strlen(file) + 3;
config = (char *)malloc(len);
if (config != NULL) {
snprintf(config, len, "%s/%s/%s", xdg_config_home, "twc", file);
}
} else if (home) {
size_t len = strlen(home) + strlen(".config/twc") + strlen(file) + 3;
config = (char *)malloc(len);
if (config != NULL) {
snprintf(config, len, "%s/%s/%s", home, ".config/twc", file);
}
}
if (access("tz.conf", F_OK) != -1) {
config = strdup("tz.conf");
} else if (xdg_config_home) {
asprintf(&config, "%s/%s/%s", xdg_config_home, "twc", file);
} else if (home) {
asprintf(&config, "%s/%s/%s", home, ".config/twc", file);
}
To run all the tests against asprintf
execute the following command:
cc test.c asprintf.c -o test && ./test
To use your system's default asprintf(3)
, run this instead:
cc test.c -o test && ./test
To link it against other asprintf
implementations, run:
cc test.c /path/to/asprintf.c -o test && ./test
Any of the above will output something like:
Ok "Memory allocation"
Ok "Empty string as input"
Ok "String formatting variations"
Ok "Special characters in format string"
Err "Boundary cases for integers"
// More tests
-----------
Passing: 4
Failed: 1
Total: 5
To compile the binary inside a Docker image, run:
docker build .
Runs on Linux, macOS and *BSD systems on both x86_64
and arm64
. Builds with clang
, gcc
, tcc
, zig
and any
other compiler that supports C99 or later.
Should be compatible with glibc, GLib, FreeBSD libc, musl libc asprintf
.
asprintf
is compatible with both POSIX.1-2024,1 and C99.2
asprintf
is licensed under the terms of the MIT License.
See the LICENSE file for details.
- cosmopolitan/asprintf.c
- dietlibc
- glibc/asprintf.c
- musl/asprintf.c
- newlib/asprintf.c
- picolibc/asprintf.c
- uclibc/asprintf.c
- akharrou/ft_asprintf
- andrelmbackman/ft_printf
- d4wae89d498/ft_asprintf
- dragonblocks/asprintf
- eiszapfen2000/asprintf
- finwo/c-asprintf
- jkaivo/asprintf
- jwerle/asprintf.c
- libressl/portable
- lordmulder/asprintf-msvc
- n-nino/vc6-asprintf
- naev/naev/asprintf
- tap3edit/mp/mpasprintf
- timbaker/asprintf
- weiss/c99-snprintf
- Chris Wellons: How to Write Portable C Without Complicating Your Build
- Fedora Defensive Coding Guide: The C Programming Language
- FreeBSD Developers' Handbook: Chapter 3. Secure Programming
- GNU Autoconf: Portability of C Functions
- GNU Autoconf: Portable C and C++ Programming
- Oracle Developer's Guide to Solaris: Security Considerations When Using C Functions
Footnotes
-
IEEE Std 1003.1-2024: Standard for Information Technology — Portable Operating System Interface (POSIX®),
ISO/IEC DIS 9945. URL: https://pubs.opengroup.org/onlinepubs/9799919799/ ↩ -
ISO/IEC 9899: Standard for Information Technology — Programming languages — C, ISO/IEC 9899:2023.
URL: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf ↩