1

I need a function to read a file name, with a max length of MAX_FILE_NAME_SIZE, which is a symbolic constant, I did this the following way:

char * readFileName()
{
    char format[6];

    char * fileName = malloc(MAX_FILE_NAME_SIZE * sizeof(fileName[0]));

    if(fileName== NULL)
        return NULL;

    sprintf(format, "%%%ds", MAX_FILE_NAME_SIZE-1);
    scanf(format, fileName);

    fileName= realloc(fileName, strlen(fileName)*sizeof(fileName[0]));

    return fileName;
}

I'd really like to get read of the sprintf part (and also the format vector), what's the cleanest and most efficient way to do this?

YoTengoUnLCD
  • 600
  • 7
  • 15
  • 1
    See [How to prevent `scanf()` causing a buffer overflow in C](https://stackoverflow.com/questions/1621394/) for one place where this is advocated as a method — citing [The Practice of Programming](http://www.cs.princeton.edu/~bwk/tpop.webpage/) as a source for the technique. If the size if truly fixed, then the `sprintf()` isn't so necessary, but if the sizes of the strings can vary, then it is the best way to go. – Jonathan Leffler Oct 22 '15 at 03:15

1 Answers1

3

Solution

You can make a little Preprocessor hack:

#define MAX_BUFFER  30
#define FORMAT(s)   "%" #s "s"
#define FMT(s)      FORMAT(s)

int main(void)
{
    char buffer[MAX_BUFFER + 1];

    scanf(FMT(MAX_BUFFER), buffer);

    printf("string: %s\n", buffer);
    printf("length: %d\n", strlen(buffer));
    return 0;
}

The FORMAT and FMT macros are necessary for the preprocessor to translate them correctly. If you call FORMAT directly with FORMAT(MAX_BUFFER), it will translate into "%" "MAX_BUFFER" "s" which is no good.

You can verify that using gcc -E scanf.c. However, if you call it through another macro, which will effectively resolve the macro names for you and translate to "%" "30" "s", which is a fine format string for scanf.


Edit

As correctly pointed out by @Jonathan Leffler in the comments, you can't do any math on that macro, so you need to declare buffer with plus 1 character for the NULL terminating byte, since the macro expands to %30s, which will read 30 characters plus the null byte.

So the correct buffer declaration should be char buffer[MAX_BUFFER + 1];.


Requested Explanation

As asked in the comments, the one macro version won't work because the preprocessor operator # turns an argument into a string (stringification, see bellow). So, when you call it with FORMAT(MAX_BUFFER), it just stringifies MAX_BUFFER instead of macro-expanding it, giving you the result: "%" "MAX_BUFFER" "s".

Section 3.4 Stringification of the C Preprocessor Manual says this:

Sometimes you may want to convert a macro argument into a string constant. Parameters are not replaced inside string constants, but you can use the ‘#’ preprocessing operator instead. When a macro parameter is used with a leading ‘#’, the preprocessor replaces it with the literal text of the actual argument, converted to a string constant. Unlike normal parameter replacement, the argument is not macro-expanded first. This is called stringification.

This is the output of the gcc -E scanf.c command on a file with the one macro version (the last part of it):

int main(void)
{
    char buffer[30 + 1];

    scanf("%" "MAX_BUFFER" "s", buffer);

    printf("string: %s\n", buffer);
    printf("length: %d\n", strlen(buffer));
    return 0;
}

As expected. Now, for the two levels, I couldn't explain better than the documentation itself, and in the last part of it there's an actual example of this specific case (two macros):

If you want to stringify the result of expansion of a macro argument, you have to use two levels of macros.

 #define xstr(s) str(s)
 #define str(s) #s
 #define foo 4
 str (foo)
      ==> "foo"
 xstr (foo)
      ==> xstr (4)
      ==> str (4)
      ==> "4"

s is stringified when it is used in str, so it is not macro-expanded first. But s is an ordinary argument to xstr, so it is completely macro-expanded before xstr itself is expanded (see Argument Prescan). Therefore, by the time str gets to its argument, it has already been macro-expanded.


Resource

Community
  • 1
  • 1
Enzo Ferber
  • 3,029
  • 1
  • 14
  • 24
  • This can work, with some caveats. One of the caveats is that the array size needs to be `MAX_BUFFER+1` because the length specified in `scanf()` is the length without the null terminator. Another is that the parameter to the macro must be a simple number; it cannot be an expression (so `MAX_BUFFER-1` is not OK). However, if these are dealt with (which means that the first issue needs to be addressed in the answer), then this works for a fixed-size input. If the size can vary, the `sprintf()` solution from the question is the best choice. – Jonathan Leffler Oct 22 '15 at 04:39
  • @JonathanLeffler Thank you, I didn't pay attention to that. Edited. – Enzo Ferber Oct 22 '15 at 10:12
  • Hmm, I don't really understand why the two macros method works, but not with a single macro, could you please explain it a bit more in detail? – YoTengoUnLCD Oct 22 '15 at 14:25
  • @YoTengoUnLCD Please check my last edit. I added a full explanation and manual reference. – Enzo Ferber Oct 22 '15 at 14:42
  • @YoTengoUnLCD Does that solve your problem and clear your doubts? – Enzo Ferber Oct 22 '15 at 14:54
  • Ohhh I get it, thanks a lot Enzo! Very nice explanation. – YoTengoUnLCD Oct 22 '15 at 16:08