1

When can I pass variable's value to a macro for stringifying?

For example the code taken from this post works with a constant-defined macro.

#define MAX_STRING_LENGTH 20
#define STRINGIFY(x) STRINGIFY2(x)
#define STRINGIFY2(x) #x

{
  ...
  char word[MAX_STRING_LENGTH+1];     
  scanf("%" STRINGIFY(MAX_STRING_LENGTH) "s", word);
  ...
}

However I cannot use it with a variable such as:

{
  ...
  int val = 20;
  char word[MAX_STRING_LENGTH+1];     
  scanf("%" STRINGIFY(val) "s", word);
  ...
}

since the compilation is successful with this warning:

warning: invalid conversion specifier 'v' [-Wformat-invalid-specifier]
    scanf("%" STRINGIFY(var) "s", word);
           ~~~^~~~~~~~~~~~~
test2.c:4:22: note: expanded from macro 'STRINGIFY'
#define STRINGIFY(x) STRINGIFY2(x)
                     ^
test2.c:5:23: note: expanded from macro 'STRINGIFY2'
#define STRINGIFY2(x) #x
                      ^
<scratch space>:466:2: note: expanded from here
"var"
 ^
1 warning generated

but the run of the code does not wait for any input.

On the contrary in this other post it was possible to pass a variable to this macro:

#define PRINT(int) printf(#int "%d\n",int)
...
int var =8;
PRINT(var);

What is the difference between the two cases? How can I modify the first one so that it accepts also variables?

I tried using %d inside the macro but I was not successful.

roschach
  • 8,390
  • 14
  • 74
  • 124

2 Answers2

5

The preprocessor always operates on tokens only.

A macro is not a function. You don't pass it a variable (by value). You pass a token sequence. In STRINGIFY(MAX_STRING_LENGTH) the token sequence is MAX_STRING_LENGTH, and in STRINGIFY(val) it's the token sequence val.

MAX_STRING_LENGTH is itself a macro, and due to how STRINGIFY is defined to work, the macro will be expanded by the preprocessor before turning it a string literal. So 20 is in turn the token which gets # applied to it, and it produces "20" as a string literal.

On the other hand val is not a macro, the preprcosseor is not going to expand it. It's going to keep the token sequence as val. The fact val is the name of a variable with some value means nothing to the preprocessor, it only cares about tokens. So val is transformed into the literal "val".

The example you brought from another post worked because it expanded to this:

printf("var" "%d\n", var);

The variable name in #int turns into a literal, there is no magic that lets the preprocessor read a variable's value. The fact var 8 is printed is only because var is passed as an argument to printf! It's printed at run-time by the %d specifier.

Finally, when experimenting with the preprcoessor it's always helpful to look at the source file after prpeprocessing is done, but before the file is compiled. The gcc -E flag (or equivalent for your compiler) can help you do that.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Thanks for your answer. Is there/do you know any work-around with macros to generate a string in the form `%20s` (something like maybe `"%%%ds", var`) to be passed then to a function like `scanf`? – roschach Jan 31 '19 at 11:37
  • @FrancescoBoi - There is actually. It's in the [specification of `printf`](https://en.cppreference.com/w/c/io/fprintf). You can use `%*s`. The `*` means the width is passed as an extra argument. I.e. `"%*s", val, word` – StoryTeller - Unslander Monica Jan 31 '19 at 11:43
  • 2
    @StoryTeller But that's for printf only, scanf does not support this (unfortunately!)... – Aconcagua Jan 31 '19 at 11:44
  • @Aconcagua - Nobody asked about `scanf`, but it does support `*` as the percision of `s`. I.e. `%.*s` (note the dot). The precision of `s` means the maximum buffer width. – StoryTeller - Unslander Monica Jan 31 '19 at 11:45
  • @StoryTeller See question: `scanf("%" STRINGIFY(val) "s", word);` – Aconcagua Jan 31 '19 at 11:46
  • And so does `printf` matter of fact. Since the OP wanted "maximum" length, they probably want to use precision there too. Consulting the docs is prudent on this. – StoryTeller - Unslander Monica Jan 31 '19 at 11:46
  • @Aconcagua - I did "see the question", thank you very much. There is another issue here, with the OP not being aware that `"%20s"` should be `"%.20s"`. But that is beside the point about the preprocessor. – StoryTeller - Unslander Monica Jan 31 '19 at 11:47
  • 1
    @StoryTeller Oh, need to admit, overlooked that missing period myself, too... But as far as I interprete the question, that would be exactly what OP seeks for: `scanf("%.*s", val, word);` - which actually is what I have missed myself so often, too... – Aconcagua Jan 31 '19 at 11:51
  • @Aconcagua - Dammit. I shouldn't be flapping my gums. There is no standard `.*` for scanf. Now I'm gonna be bothered all day until I figure out where I learned of that extension. – StoryTeller - Unslander Monica Jan 31 '19 at 11:56
  • @FrancescoBoi - Unlike my confused self told you, if you want a portable way to scan a string with the ability to specify buffer length dynamically... `scanf` is not the way to go. – StoryTeller - Unslander Monica Jan 31 '19 at 12:01
  • Yeah I was trying it `scanf("%.*s", val, word)`: it runs but does not write to the buffer. As for the question, it was to understand better macros: how they work and how to use them. The reason was exactly to implement a secure `scanf` with a dynamic length of buffer (I know there are other posts but wanted to explore also this way) but I didn't want to make the question general so I did not put any emphasis on it. For this reason anything is well accepted :). So `scanf("%.*s", val, word)` doesn't work: does it? – roschach Jan 31 '19 at 12:08
  • 1
    @FrancescoBoi No, it doesnt't, at least not with standard C. – Aconcagua Jan 31 '19 at 12:12
  • 1
    @FrancescoBoi - Nope. Scanf does not support dynamic buffer length specification. If you are on a POSIX system then you could use `%ms`. That will use malloc internally to allocate the buffer (and you need to pass the address of a pointer to assign to), but it's POSIX only, again. – StoryTeller - Unslander Monica Jan 31 '19 at 12:12
  • Thanks a lot guys! very helpful – roschach Jan 31 '19 at 12:13
  • I asked this question: https://stackoverflow.com/q/54461111/1714692. Apparently not even all POSIX systems, i.e. OSX, support that usage of `scanf`. – roschach Jan 31 '19 at 15:10
  • @FrancescoBoi - POSIX is a standard. And OSX does not conform. That sucks, yeah – StoryTeller - Unslander Monica Jan 31 '19 at 15:13
  • Actually formally it conforms (or should): it is POSIX system. – roschach Jan 31 '19 at 15:14
  • @FrancescoBoi - To claim one conforms and to do so can be a world apart. I mean, questions pop up on SO about compiler conformance all the time :) – StoryTeller - Unslander Monica Jan 31 '19 at 15:19
3

STRINGIFY(val) will result in "val", not the value you wanted to stringify, so you get a final format string of "%vals" ("%" "val" "s"). That's how the C preprocessor works, it does just text replacements, nothing more.

The PRINT example:

#define PRINT(int) printf(#int "%d\n", int)

PRINT(var);                // to be resolved
printf(#var  "%d\n", var); // intermediate result
printf("var" "%d\n", var); // final result, this is what the C compiler sees

But why did it work with MAX_STRING_LENGTH?

#define MAX_STRING_LENGTH 20
#define STRINGIFY(x) STRINGIFY2(x)
#define STRINGIFY2(x) #x

STRINGIFY(MAX_STRING_LENGTH)  // to be resolved
STRINGIFY2(20)                // intermediate step; STRINGIFY2 known as macro, thus:
#20                           // another intermediate step
"20"                          // final result
Aconcagua
  • 24,880
  • 4
  • 34
  • 59