5 ms·
Compile-Time and Runtime-Safe Replacement for “printf” (2015)
- wahern 10y agoGCC's printf annotation can give you compile-time safety. You can get run-time type checking in C using C99 variadic macros and C11 _Generic expressions. Here's a proof-of-concept I threw together earlier this year in a moment of boredom. I only tested it with a few versions of clang and GCC. #include <assert.h> #include <errno.h> #include <stdarg.h> /* va_list va_arg va_end va_start */ #include <stdio.h> #include <string.h> #include <netinet/in.h> #define STRFMT_CALL(F, ...) F(__VA_ARGS__) #define STRFMT_NARG_(a, b, c, d, e, f, g, h, i, j, N,...) N #define STRFMT_NARG(...) STRFMT_NARG_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) #define STRFMT_PASTE(x, y) x##y #define STRFMT_XPASTE(x, y) STRFMT_PASTE(x, y) #define STRFMT_STRINGIFY_(s) #s #define STRFMT_STRINGIFY(s) STRFMT_STRINGIFY_(s) enum strfmt_type { STRFMT_NONE = 0, STRFMT_INT, STRFMT_UINT, STRFMT_LONG, STRFMT_ULONG, STRFMT_LLONG, STRFMT_ULLONG, STRFMT_FLOAT, STRFMT_DOUBLE, STRFMT_LDOUBLE, STRFMT_CHAR_P, STRFMT_SCHAR_P, STRFMT_UCHAR_P, STRFMT_VOID_P, STRFMT_STRUCT_IN_ADDR_P, STRFMT_STRUCT_IN6_ADDR_P, STRFMT_END_P, }; #define STRFMT_END (&(struct strfmt_end){ 0 }) struct strfmt_end { int _; }; extern int strfmt_unknown_expression_type; #define strfmt_badtype(X) strfmt_unknown_expression_type #define strfmt_promote(X) ((0)? 0 : (X)) #define strfmt_typeof_p_g(X, Q, def) \ _Generic(strfmt_promote(X), \ Q char *: STRFMT_CHAR_P, \ Q signed char *: STRFMT_SCHAR_P, \ Q unsigned char *: STRFMT_UCHAR_P, \ Q void *: STRFMT_VOID_P, \ Q struct in_addr *: STRFMT_STRUCT_IN_ADDR_P, \ Q struct in6_addr *: STRFMT_STRUCT_IN6_ADDR_P, \ Q struct strfmt_end *: STRFMT_END_P, \ default: (def)) #define strfmt_typeof_p(X) \ strfmt_typeof_p_g((X), const, \ strfmt_typeof_p_g((X), , strfmt_badtype(X))) #define strfmt_typeof(X) \ _Generic(strfmt_promote(X), \ int: STRFMT_INT, \ unsigned: STRFMT_UINT, \ long: STRFMT_LONG, \ unsigned long: STRFMT_ULONG, \ long long: STRFMT_LLONG, \ unsigned long long: STRFMT_ULLONG, \ float: STRFMT_FLOAT, \ double: STRFMT_DOUBLE, \ long double: STRFMT_LDOUBLE, \ default: strfmt_typeof_p(X)) #define STRFMT_EXP1(X) strfmt_typeof(X), STRFMT_STRINGIFY(X), (X) #define STRFMT_EXP2(X, ...) STRFMT_EXP1(X), STRFMT_EXP1(__VA_ARGS__) #define STRFMT_EXP3(X, ...) STRFMT_EXP1(X), STRFMT_EXP2(__VA_ARGS__) #define STRFMT_EXP4(X, ...) STRFMT_EXP1(X), STRFMT_EXP3(__VA_ARGS__) #define STRFMT_EXP5(X, ...) STRFMT_EXP1(X), STRFMT_EXP4(__VA_ARGS__) #define STRFMT_EXP6(X, ...) STRFMT_EXP1(X), STRFMT_EXP5(__VA_ARGS__) #define STRFMT_EXP7(X, ...) STRFMT_EXP1(X), STRFMT_EXP6(__VA_ARGS__) #define STRFMT_EXP8(X, ...) STRFMT_EXP1(X), STRFMT_EXP7(__VA_ARGS__) #define STRFMT_EXP9(X, ...) STRFMT_EXP1(X), STRFMT_EXP8(__VA_ARGS__) #define STRFMT_EXPAND(...) STRFMT_CALL(STRFMT_XPASTE(STRFMT_EXP, STRFMT_NARG(__VA_ARGS__)), __VA_ARGS__) #define strfmt_(dst, lim, error, fmt, ...) strfmt((dst), (lim), (error), (fmt), STRFMT_EXPAND(__VA_ARGS__)) #define strfmt(...) strfmt_(__VA_ARGS__, STRFMT_END) size_t (strfmt)(void *dst, size_t lim, int *_error, const char *fmt, ...) { enum strfmt_type type; const char *exp; va_list ap; int error; *_error = 0; strlcpy(dst, "", lim); va_start(ap, fmt); do { type = va_arg(ap, enum strfmt_type); exp = va_arg(ap, char *); switch (type) { case STRFMT_INT: va_arg(ap, int); strlcat(dst, "(int)", lim); break; case STRFMT_UINT: va_arg(ap, unsigned); strlcat(dst, "(unsigned)", lim); break; case STRFMT_LONG: va_arg(ap, long); strlcat(dst, "(long)", lim); break; case STRFMT_ULONG: va_arg(ap, unsigned long); strlcat(dst, "(unsigned long)", lim); break; case STRFMT_LLONG: va_arg(ap, long long); strlcat(dst, "(long long)", lim); break; case STRFMT_ULLONG: va_arg(ap, unsigned long long); strlcat(dst, "(unsigned long long)", lim); break; case STRFMT_FLOAT: va_arg(ap, double); strlcat(dst, "(float)", lim); break; case STRFMT_DOUBLE: va_arg(ap, double); strlcat(dst, "(double)", lim); break; case STRFMT_LDOUBLE: va_arg(ap, long double); strlcat(dst, "(long double)", lim); break; case STRFMT_CHAR_P: va_arg(ap, char *); strlcat(dst, "(char *)", lim); break; case STRFMT_SCHAR_P: va_arg(ap, signed char *); strlcat(dst, "(signed char *)", lim); break; case STRFMT_UCHAR_P: va_arg(ap, unsigned char *); strlcat(dst, "(unsigned char *)", lim); break; case STRFMT_VOID_P: va_arg(ap, void *); strlcat(dst, "(void *)", lim); break; case STRFMT_STRUCT_IN_ADDR_P: va_arg(ap, struct in_addr *); strlcat(dst, "(struct in_addr *)", lim); break; case STRFMT_STRUCT_IN6_ADDR_P: va_arg(ap, struct in6_addr *); strlcat(dst, "(struct in6_addr *)", lim); break; case STRFMT_END_P: va_arg(ap, struct strfmt_end *); break; default: fprintf(stderr, "unknown type:%d\n", (int)type); error = EINVAL; goto error; } if (type != STRFMT_END_P) { strlcat(dst, exp, lim); strlcat(dst, ", ", lim); } } while (type != STRFMT_END_P); va_end(ap); return strlen(dst); error: va_end(ap); return (*_error = error), 0; } int main(void) { char buf[1024]; const char *const const_buf = buf; int error; strfmt(buf, sizeof buf, &error, "(fmt)", "string literal", buf, const_buf, (long double)0.0, (&(struct in_addr){ INADDR_LOOPBACK, }), (uint64_t)64, __builtin_nan("") ); puts(buf); return 0; }