Compile-Time and Runtime-Safe Replacement for “printf” (2015)
GCC&#x27;s printf annotation can give you compile-time safety.<p>You can get run-time type checking in C using C99 variadic macros and C11 _Generic expressions. Here&#x27;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.<p><pre><code>  #include &lt;assert.h&gt;
  #include &lt;errno.h&gt;
  #include &lt;stdarg.h&gt; &#x2F;* va_list va_arg va_end va_start *&#x2F;
  #include &lt;stdio.h&gt;
  #include &lt;string.h&gt;
  
  #include &lt;netinet&#x2F;in.h&gt;
  
  #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 (&amp;(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, &quot;&quot;, 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, &quot;(int)&quot;, lim);
        break;
      case STRFMT_UINT:
        va_arg(ap, unsigned);
        strlcat(dst, &quot;(unsigned)&quot;, lim);
        break;
      case STRFMT_LONG:
        va_arg(ap, long);
        strlcat(dst, &quot;(long)&quot;, lim);
        break;
      case STRFMT_ULONG:
        va_arg(ap, unsigned long);
        strlcat(dst, &quot;(unsigned long)&quot;, lim);
        break;
      case STRFMT_LLONG:
        va_arg(ap, long long);
        strlcat(dst, &quot;(long long)&quot;, lim);
        break;
      case STRFMT_ULLONG:
        va_arg(ap, unsigned long long);
        strlcat(dst, &quot;(unsigned long long)&quot;, lim);
        break;
      case STRFMT_FLOAT:
        va_arg(ap, double);
        strlcat(dst, &quot;(float)&quot;, lim);
        break;
      case STRFMT_DOUBLE:
        va_arg(ap, double);
        strlcat(dst, &quot;(double)&quot;, lim);
        break;
      case STRFMT_LDOUBLE:
        va_arg(ap, long double);
        strlcat(dst, &quot;(long double)&quot;, lim);
        break;
      case STRFMT_CHAR_P:
        va_arg(ap, char *);
        strlcat(dst, &quot;(char *)&quot;, lim);
        break;
      case STRFMT_SCHAR_P:
        va_arg(ap, signed char *);
        strlcat(dst, &quot;(signed char *)&quot;, lim);
        break;
      case STRFMT_UCHAR_P:
        va_arg(ap, unsigned char *);
        strlcat(dst, &quot;(unsigned char *)&quot;, lim);
        break;
      case STRFMT_VOID_P:
        va_arg(ap, void *);
        strlcat(dst, &quot;(void *)&quot;, lim);
        break;
      case STRFMT_STRUCT_IN_ADDR_P:
        va_arg(ap, struct in_addr *);
        strlcat(dst, &quot;(struct in_addr *)&quot;, lim);
        break;
      case STRFMT_STRUCT_IN6_ADDR_P:
        va_arg(ap, struct in6_addr *);
        strlcat(dst, &quot;(struct in6_addr *)&quot;, lim);
        break;
      case STRFMT_END_P:
        va_arg(ap, struct strfmt_end *);
        break;
      default:
        fprintf(stderr, &quot;unknown type:%d\n&quot;, (int)type);
        error = EINVAL;
        goto error;
      }
  
      if (type != STRFMT_END_P) {
        strlcat(dst, exp, lim);
        strlcat(dst, &quot;, &quot;, 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, &amp;error, &quot;(fmt)&quot;,
      &quot;string literal&quot;,
      buf,
      const_buf,
      (long double)0.0,
      (&amp;(struct in_addr){ INADDR_LOOPBACK, }),
      (uint64_t)64,
      __builtin_nan(&quot;&quot;)
    );
    puts(buf);
  
    return 0;
  }</code></pre>