15

How can I use a variable to specify the max number of chars scanf() should read in?

For example using printf() you can use the * like so

#define MAXVAL 5
printf("Print at maximum MAXVAL chars: %.*s\n", MAXVAL, "myStringHere");

This will only print 5 chars, how can I make scanf only read in MAXVAL? MAXVAL must be used as the length specifier. I cannot simply do

scanf("%5s", string);

Right now I can only think of reading into a large array using scanf then using ssprintf to store the string into my length limited string. Using a length specifier would be so much easier however.

CS Student
  • 1,613
  • 6
  • 24
  • 40
  • 1
    Size reduce of `-1` that amount because `scanf` add a NUL character reading for that string(`%s`). E.g. `scanf("%4s", string);` – BLUEPIXY Aug 20 '14 at 17:19
  • @user3121023 I think he wants to use `MAXVAL` instead of hardcoding a value in the format string. – Filipe Gonçalves Aug 20 '14 at 17:19
  • 2
    @mafso `fgets` breaks on newlines, `scanf` breaks on whitespace. @CSStudent you can create format specifier in code. – clcto Aug 20 '14 at 17:21
  • @FilipeGonçalves You are correct, I need to use `MAXVAL` – CS Student Aug 20 '14 at 17:24
  • @clcto "scanf breaks on whitespace" ignores the effect of the format parameter. The function `scanf()` does not necessarily break on white-space as it depends on the format. `scanf("%5s")` will consume leading white-space (not break on w-s), then scan non-white-space characters until 1) white-space (does break on w-s) 2) 5 `char` read 3) EOF or 4) IOError. – chux - Reinstate Monica Aug 20 '14 at 21:04
  • possible duplicate of [Using C preprocessor to construct a string literal for scanf?](http://stackoverflow.com/questions/2740039/using-c-preprocessor-to-construct-a-string-literal-for-scanf) – Joshua Taylor Aug 31 '14 at 22:37

4 Answers4

25

You can use the C preprocessor to help you with that.

#define STR2(x) #x
#define STR(X) STR2(X)
scanf("%" STR(MAXVAL) "s", string);

The processor combines "%" STR(MAXVAL) "s" to "%5s"

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Thats quite a clever method. Interesting use of the preprocessor – CS Student Aug 20 '14 at 17:26
  • 1
    I've just tried this method, whats the need in using 2 `#defines`? Can't I just use `#define STR(x) #x` and then use `scanf("%" STR(MAXVAL) "s", string);` ? – CS Student Aug 20 '14 at 17:53
  • 3
    @CSStudent, please take a look at http://stackoverflow.com/questions/16989730/c-stringify-how-does-it-work. – R Sahu Aug 20 '14 at 17:57
  • After reading through the answers to that question I'm still confused. I don't see why 2 `defines` are needed. Am I missing something obvious? – CS Student Aug 20 '14 at 18:04
  • If you don't use two levels, `"%" STR(MAXVAL) "s"` will be transformed to `%MAXVALs`, not `%5s`. – R Sahu Aug 20 '14 at 18:11
  • It seemed to have worked using gcc with only one define, to test I used: `#define STR(x) #x #define MAX 50` and `printf("one define: %%" STR(MAX) "s");` and it output `one define: %50s` I used `%%` so the % char would be printed to stdout. – CS Student Aug 20 '14 at 18:20
  • I don't know how you are getting the above output. Checkout this example at http://ideone.com/aaw2bD. – R Sahu Aug 20 '14 at 18:49
  • 7
    @CSStudent: The two definitions are necessary so that the argument is expanded properly. That is, if `STR(X)` was simply defined as `#x`, then `STR(MAXVAL)` would expand to `#MAXVAL`, meaning the `scanf` call would expand to `scanf("%" "MAXVAL" "s", string );`, which is not what you want. By using the second define, `MAXVAL` will expand to 5 before being stringized. – John Bode Aug 20 '14 at 19:07
  • @JohnBode Cheers John once again – CS Student Aug 20 '14 at 20:21
  • The same approach (but perhaps with better macro names was used in the answer to a very similar question from 2010: [Using C preprocessor to construct a string literal for scanf?](http://stackoverflow.com/q/2740039/1281433) – Joshua Taylor Aug 31 '14 at 22:39
5
#include <stdio.h>

#define MAXLEN 5
#define S_(x) #x
#define S(x) S_(x)

int main(void){
    char string[MAXLEN+1];

    scanf("%" S(MAXLEN) "s", string);
    printf("<%.*s>\n", MAXLEN, string);
    return 0;
}
BLUEPIXY
  • 39,699
  • 7
  • 33
  • 70
4

Kernighan and Pike recommend using snprintf() to create the format string. I developed this idea into a method that safely reads strings:

void scan_string(char *buffer, unsigned length) {
    char format[16]; // Max int (10) + %, s & null-terminator (3)
                     // Rounded up to the closes power of 2
    snprintf(format, sizeof(format), "%%%ds", length - 1);
    scanf(format, buffer);
}

int main() {
  char str[5];
  scan_string(str, sizeof(string));
  printf("%s\n", str);
}
Daniel Trugman
  • 8,186
  • 20
  • 41
2

You can't. You need to use something other than scanf(). A good and popular choice is fgets(), although its semantics are slightly different: fgets() will read a line of input, whereas scanf() with %s will read whitespace separated sequences of characters.

To use fgets(), you'd want something like:

fgets(string, MAXVAL, stdin);

If for some reason you really want to use scanf(), have a look at this question: How to prevent scanf causing a buffer overflow in C?

Community
  • 1
  • 1
Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
  • This is probably the best option to maintain good readability but I think I will go with the preprocessor trickery. So I can still use `scanf()` – CS Student Aug 20 '14 at 17:30