4
\$\begingroup\$

The requirement is to define a macro which takes a single argument that is a string literal.

My first try at it was to surround it with empty string literals (I got this from Modern C):

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

Now if I do:

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;
}

It fails to compile, which was the expected behavior:

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

Then I proceeded to ask a question on StackOverflow and found instances where this would fail:

SV()
SV(/*comment*/)
SV(-)
SV("abc" - "def")

So I added some more expressions , suggested by @n. m. could be an AI, to it:

#define SV(str)    ("" str "", (str)[0], sizeof(str) - 1)

Now it fails for these 4 edge-cases as well. Yet there are still things like:

// I believe the second one is defined-behavior. 
SV("abcd" - sizeof "e");  
SV("abcd" + sizeof "e"); 

for which it would compile cleanly. Whilst I am satisfied with the final macro and it serves my use-case, I believe there might still be some improvements it could use.

How can it be made more robust?

Note: I am looking for a solution that would work with at least C99.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ This was a simple use-case. A better use case is for a debug_print(s, x) wrapper around fprintf(stderr, s, x) where s is required to be a string literal for security purposes. \$\endgroup\$
    – Harith
    Mar 28 at 16:15
  • 1
    \$\begingroup\$ The use-case seems similar to Stringizing. You'll still end up with situations where you can't stringify empty arguments or comments. \$\endgroup\$
    – Rish
    Mar 29 at 2:39

1 Answer 1

3
\$\begingroup\$

My first thought is that the name SV is utterly opaque. If the intent is to be a kind of static strlen(), then name it appropriately - e.g. STR_LEN.

I'm assuming that we're providing an aid to Murphy rather than a defence against Machiavelli, so we only need to guard against the misuses likely by someone who doesn't realise a string literal is required. The code looks adequate in that respect.

There is a particular case where a valid literal is provided but the result differs from that of strlen(), and it's when the literal contains \0, stopping strlen() at that point. However, that's an unlikely action for a C programmer.

I do get too many warnings when it's used correctly:

291296.c:3:30: warning: left-hand operand of comma expression has no effect [-Wunused-value]
    3 | #define SV(str)    ("" str "", (str)[0], sizeof(str) - 1)
      |                              ^
291296.c:13:21: note: in expansion of macro ‘SV’
   13 |     printf("%zu\n", SV("abcd"));
      |                     ^~
291296.c:3:40: warning: left-hand operand of comma expression has no effect [-Wunused-value]
    3 | #define SV(str)    ("" str "", (str)[0], sizeof(str) - 1)
      |                                        ^
291296.c:13:21: note: in expansion of macro ‘SV’
   13 |     printf("%zu\n", SV("abcd"));
      |                     ^~
291296.c:3:30: warning: left-hand operand of comma expression has no effect [-Wunused-value]
    3 | #define SV(str)    ("" str "", (str)[0], sizeof(str) - 1)
      |                              ^
291296.c:14:21: note: in expansion of macro ‘SV’
   14 |     printf("%zu\n", SV(""));
      |                     ^~
291296.c:3:40: warning: left-hand operand of comma expression has no effect [-Wunused-value]
    3 | #define SV(str)    ("" str "", (str)[0], sizeof(str) - 1)
      |                                        ^
291296.c:14:21: note: in expansion of macro ‘SV’
   14 |     printf("%zu\n", SV(""));
      |                     ^~

I think we can eliminate those by using conditional operator instead of comma:

#define SV(str)  ((str)[0] ? sizeof("" str "") - 1 : 0)

Or perhaps evaluate str[0] in unevaluated context, which then makes both your final examples well-defined (though still unhelpful):

#define SV(str)  (sizeof("" str "") - sizeof((str)[0]))

Thinking further, we could develop this so that it gives the correct string length for wide strings (rather than byte count):

#define SV(str)  (sizeof("" str "") / sizeof((str)[0]) - 1)

Reminder: the concatenation of a prefixed and an unprefixed string literal takes the type of the prefixed one.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.