Forcing a macro argument to be a string literal

81 views Asked by At

Whilst reviewing some code here, I saw a macro where the argument was required to be a string literal.

And I discovered this macro (from Jenn's Gustedt Modern C) which claimed to fulfill the following constraints:

  • Is a character pointer
  • Must be non-null
  • Must be immutable
  • Must be 0-terminated

If I have:

#define SV(str)    (sizeof(str) - 1)

int main(void)
{
    static const char *const s = "hello";
    char word[] = "hello";
    double a = 0.0f;
    double *d = &a;
    
    printf("%zu\n", SV(NULL));
    printf("%zu\n", SV(word));
    printf("%zu\n", SV(d));
    printf("%zu\n", SV(s));
    
    return 0;
}

The code compiles correctly and issues no errors or warnings, and the output is:

7
5
7
7

But that changes if I use empty string literals in its expansion "" str "":

#define SV(str)         (sizeof("" str "") - 1)

int main(void)
{
    static const char *const s = "hello";
    char word[] = "hello";
    double a = 0.0f;
    double *d = &a;

    printf("%zu\n", SV(NULL));
    printf("%zu\n", SV(word));
    printf("%zu\n", SV(d));
    printf("%zu\n", SV(s));

    return 0;
}

Now if I compile this, I get:

macro_str.c: In function ‘main’:
macro_str.c:4:33: error: called object is not a function or function pointer
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                 ^~
macro_str.c:19:21: note: in expansion of macro ‘SV’
   19 |     printf("%zu\n", SV(NULL));
      |                     ^~
macro_str.c:4:40: error: expected ‘)’ before string constant
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                ~       ^~
macro_str.c:19:21: note: in expansion of macro ‘SV’
   19 |     printf("%zu\n", SV(NULL));
      |                     ^~
macro_str.c:20:24: error: expected ‘)’ before ‘word’
   20 |     printf("%zu\n", SV(word));
      |                        ^~~~
macro_str.c:4:36: note: in definition of macro ‘SV’
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                    ^~~
macro_str.c:4:32: note: to match this ‘(’
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                ^
macro_str.c:20:21: note: in expansion of macro ‘SV’
   20 |     printf("%zu\n", SV(word));
      |                     ^~
macro_str.c:21:24: error: expected ‘)’ before ‘d’
   21 |     printf("%zu\n", SV(d));
      |                        ^
macro_str.c:4:36: note: in definition of macro ‘SV’
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                    ^~~
macro_str.c:4:32: note: to match this ‘(’
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                ^
macro_str.c:21:21: note: in expansion of macro ‘SV’
   21 |     printf("%zu\n", SV(d));
      |                     ^~
macro_str.c:22:24: error: expected ‘)’ before ‘s’
   22 |     printf("%zu\n", SV(s));
      |                        ^
macro_str.c:4:36: note: in definition of macro ‘SV’
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                    ^~~
macro_str.c:4:32: note: to match this ‘(’
    4 | #define SV(str)         (sizeof("" str "") - 1)
      |                                ^
macro_str.c:22:21: note: in expansion of macro ‘SV’
   22 |     printf("%zu\n", SV(s));
      |                     ^~
make: *** [<builtin>: macro_str] Error 1

My question is: Are there any edge-cases or loopholes where this would fail? If so, how can I modify it to avoid them?

2

There are 2 answers

1
Eric Postpischil On

Are there any edge-cases or loopholes where this would fail?

These compile without complaint:

printf("%zu\n", SV());
printf("%zu\n", SV(/*comment*/));
printf("%zu\n", SV(-));
printf("%zu\n", SV("abc" - "def"));
0
KamilCuk On

How about:

#define SV(str)   _Generic(&("" str "")  \
      , char(*)[sizeof(str)]: sizeof(str) - 1  \
      , const char(*)[sizeof(str)]: sizeof(str) - 1 \
      )


#include <stddef.h>
#include <stdio.h>
int main() {
    printf("%zu\n", SV("test"));
    printf("%zu\n", SV("test" "test"));

    static const char *const s = "hello";
    char word[] = "hello";
    double a = 0.0f;
    double *d = &a;
    printf("%zu\n", SV(NULL));
    printf("%zu\n", SV(word));
    printf("%zu\n", SV(d));
    printf("%zu\n", SV(s));
    printf("%zu\n", SV());
    printf("%zu\n", SV(/*comment*/));
    printf("%zu\n", SV(-));
    printf("%zu\n", SV("abc" - "def"));
}