Search code examples
cgccstring-literalsstatic-assert

C: Assert that an expression is a string literal


I would like a static assertion to be sure that a given expression is a string literal. I tried this:

#define SAME_TYPES(x, y)                    __builtin_types_compatible_p(typeof(x), typeof(y))
#define IS_ARRAY(x)                         !SAME_TYPES((x), &(x)[0])
#define IS_COMPILE_TIME_STR(x)              IS_ARRAY(x) && #x[0] == '"'
#define ASSERT_COMPILE_TIME_STR(x)          static_assert(IS_COMPILE_TIME_STR(x), "Only static allocated compile time strings are allowed, but got this: " #x)

/*
 * ASSERT_COMPILE_TIME_STR("hey"); <-- this is fine
 * ASSERT_COMPILE_TIME_STR(1234);  <-- this should fail
 */

Unfortunately, ASSERT_COMPILE_TIME_STR does not work, because #x[0] == '"' does not seem to be a compile-time-constant.

Is there some other way to achieve this? It just has to compile with gcc, so any kind of extensions are fine:)

Thanks a lot


Solution

  • C allows consecutive string constants which the compiler combines into a single constant. You can exploit this by attempting to put a string constant in line with the expression in question.

    #define IS_STR_CONT(x) (void)((void)(x),&(""x""))
    

    A few notes about the above:

    • The (x) on the left side of the comma operator helps to catch certain types of non-expressions such as 0+
    • Taking the address ensures that the result is an lvalue
    • The void cast on both the left side of the comma operator and the full expression prevent warnings about an expression with no effect.

    The above accepts this with no warning or error:

    IS_STR_CONT("hello");
    

    While generating one or more errors with these:

    IS_STR_CONT(123);
    IS_STR_CONT(0+);
    IS_STR_CONT(+0);
    int i=0;
    IS_STR_CONT((i++,"xxx"));
    IS_STR_CONT(!"abc");
    IS_STR_CONT(4 + "abc");
    IS_STR_CONT(*"abc");
    IS_STR_CONT("ad" == "abc");
    

    This isn't perfect, but should catch most cases.