
Compile-Time and Runtime-Safe Replacement for “printf” (2015) - xjia
http://blogs.microsoft.co.il/sasha/2015/01/29/compile-time-runtime-safe-replacement-printf/
======
wahern
GCC'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;
      }

