83

I'm looking for a way to convert a preprocessor token to a string.

Specifically, I've somewhere got:

#define MAX_LEN 16

and I want to use it to prevent buffer overrun:

char val[MAX_LEN+1]; // room for \0
sscanf(buf, "%"MAX_LEN"s", val);

I'm open to other ways to accomplish the same thing, but standard library only.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
davenpcj
  • 12,508
  • 5
  • 40
  • 37

6 Answers6

132

See Using FILE and LINE to Report Errors:

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)

So your problem can be solved by doing sscanf(buf, "%" TOSTRING(MAX_LEN) "s", val);.

Tim Sylvester
  • 22,897
  • 2
  • 80
  • 94
Dan
  • 1,981
  • 1
  • 14
  • 18
  • 4
    why cascading 2 macros? Wouldn't be one TOSTRING enough? – feedc0de Oct 05 '17 at 12:52
  • See answer below about double-expansion stringification. The C/C++ preprocessor is dirty and ugly and I believe without a standard. So why it requires two instead of one I could not say, because I don't want to do the research. – Dan Oct 05 '17 at 15:42
  • 19
    @Daniel Brunner A single macro will paste the token itself, yielding literally `"%" "MAX_LEN" "%"` a second macro causes the token *value* to be pasted, e.g., `"16"` because the `TOSTRING` macro makes the final code equivalent to `STRINGIFY(16)`. – Tim Sylvester Feb 02 '18 at 02:46
  • 1
    @TimSylvester I wish I could give you rep for that comment! :) – Matthieu Apr 25 '19 at 15:54
  • @TimSylvester That is so confusing. – user16217248 May 06 '23 at 20:14
24

I found an answer online. ( https://gcc.gnu.org/onlinedocs/gcc-3.4.3/cpp/Stringification.html )

#define VERSION_MAJOR 4
#define VERSION_MINOR 47

#define VERSION_STRING "v" #VERSION_MAJOR "." #VERSION_MINOR

The above does not work but hopefully illustrates what I would like to do, i.e. make VERSION_STRING end up as "v4.47".

To generate the proper numeric form use something like

#define VERSION_MAJOR 4
#define VERSION_MINOR 47

#define STRINGIZE2(s) #s
#define STRINGIZE(s) STRINGIZE2(s)
#define VERSION_STRING "v" STRINGIZE(VERSION_MAJOR) \
"." STRINGIZE(VERSION_MINOR)

#include <stdio.h>
int main() {
    printf ("%s\n", VERSION_STRING);
    return 0;
}
davenpcj
  • 12,508
  • 5
  • 40
  • 37
  • 2
    Re *"I found an answer online."*: Can you provide a reference? Related: *[Plagiarism flag and moderator tooling has launched to Stack Overflow!](https://meta.stackoverflow.com/questions/423749/)* – Peter Mortensen May 21 '23 at 10:21
9

This should work:

 sscanf(buf, "%" #MAX_LEN "s", val);

If not, it'll need to "double expansion" trick:

 #define STR1(x)  #x
 #define STR(x)  STR1(x)
 sscanf(buf, "%" STR(MAX_LEN) "s", val);
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
James Curran
  • 101,701
  • 37
  • 181
  • 258
4

You should use the double-expansion stringification macro trick. Or just have a

#define MAX_LEN    16
#define MAX_LEN_S "16"

char val[MAX_LEN+1];
sscanf(buf, "%"MAX_LEN_S"s", val);

and keep it in sync. (That's a bit of a bother, but as long as the definitions are right next to each other, you'll probably remember.)

Actually, in this particular case, wouldn't strncpy suffice?

strncpy(val, buf, MAX_LEN);
val[MAX_LEN] = '\0';

If it were printf, though, this would be easier:

sprintf(buf, "%.*s", MAX_LEN, val);
Artefact2
  • 7,516
  • 3
  • 30
  • 38
ephemient
  • 198,619
  • 38
  • 280
  • 391
  • 1
    Yeah, I wish the * or another symbol were available for length-limiting scanf routines with an argument. sprintf in that form works better than strncpy, imo, because strncpy won't terminate the string if it overruns the buffer, and writes extra data if the string is too short. – davenpcj Dec 20 '08 at 03:43
1

While some of the previous answers "work", personally I'd recommend just using a simple string API instead of the dreck that comes in libc.

There are a number of portable APIs, some of which are also optimized for ease of inclusion in your project ... and some like ustr have a tiny space overhead and support for stack variables.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
James Antill
  • 2,825
  • 18
  • 16
1

In my example, the format to generate is %16s%16s%d:

#include <iostream>

#define MAX_LEN 16

#define AUX(x) #x
#define STRINGIFY(x) AUX(x)

int main() {
    char buffer[] = "Hello World 25";
    char val[MAX_LEN+1];
    char val2[MAX_LEN+1];
    int val3;

    char format[] = "%" STRINGIFY(MAX_LEN) "s" "%" STRINGIFY(MAX_LEN) "s" "%d";
    int result = sscanf(buffer, format, val, val2, &val3);
    std::cout<< val << std::endl;
    std::cout<< val2 << std::endl;
    std::cout<< val3 << std::endl;
    std::cout<<"Filled: " << result << " variables" << std::endl;
    std::cout << "Format: " << format << std::endl;
}

Output

Output

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Joma
  • 3,520
  • 1
  • 29
  • 32
  • Please review *[Why not upload images of code/errors when asking a question?](https://meta.stackoverflow.com/questions/285551/)* (e.g., *"Images should only be used to illustrate problems that* ***can't be made clear in any other way,*** *such as to provide screenshots of a user interface."*) and [do the right thing](https://stackoverflow.com/posts/61806583/edit) (it covers answers as well). Thanks in advance. – Peter Mortensen May 06 '23 at 20:13