marmistrz
marmistrz

Reputation: 6392

Scanf: detect that the input was too long

We can easily limit the length of the input accepted by scanf:

char str[101];
scanf("%100s", str);

Is there any efficient way to find out that the string was trimmed? We could, for example, report an error in such case.

We could read "%101s" into char strx[102] and check with strlen() but this involves extra cost.

Upvotes: 2

Views: 3217

Answers (4)

Zan Lynx
Zan Lynx

Reputation: 54325

Use the %n conversion to write the scan position to an integer. If it was 100 past the beginning then the string was too big.

I find that %n is useful for all kinds of things.

I thought the above was plenty of information for anyone who had read the scanf docs / man page and had actually tried it.

The idea is that you make your buffer and your scan limit bigger than whatever size string you expect to find. Then if you find a scan result that is exactly as big as your scan limit you know it is an invalid string. Then you report an error or exit or whatever it is that you do.

Also, if you're about to say "But I want to report an error and continue on the next line but scanf left my file in an unknown position."
That is why you read a line at a time using fgets and then use sscanf instead of scanf. It removes the possibility of ending the scan in the middle of the line and makes it easy to count line numbers for error reporting.

So here is the code that I just wrote:

#include <stdio.h>
#include <stdlib.h>

int scan_input(const char *input) {
        char buf[101];
        int position = 0;
        int matches = sscanf(input, "%100s%n", buf, &position);
        printf("'%s' matches=%d position=%d\n", buf, matches, position);
        if (matches < 1)
                return 2;
        if (position >= 100)
                return 3;
        return 0;
}

int main(int argc, char *argv[]) {
        if (argc < 2)
                exit(1);
        const char *input = argv[1];
        return scan_input(input);
}

And here is what happens:

$ ./a.out 'This is a test string'
'This' matches=1 position=4
$ ./a.out 'This-is-a-test-string'
'This-is-a-test-string' matches=1 position=21
$ ./a.out '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'
'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' matches=1 position=100

Upvotes: 4

giusti
giusti

Reputation: 3538

You could use fgets() to read an entire line. Then you verify if the newline character is in the string. However, this has a few disadvantages:

  1. It will consume the entire line, and maybe that's not what you want. Notice that fgets() is not equivalent to scanf("%100s") -- the latter only reads until the first blank character appears;
  2. If the input stream is closed before a newline character is supplied, you will be undecided;
  3. You have to go through the array to search for the newline character.

So the better option seems to be as such:

char str[101];
int c;
scanf("%100s", str);
c = getchar();
ungetc(c, stdin);
if (c == EOF || isspace(c)) {
    /* successfuly read everything */
}
else {
    /* input was too long */
}

This reads the string normally and checks for the next character. If it's a blank or if the stream has been closed, then everything was read.

The ungetc() is there in case you don't want your test to modify the input stream. But it's probably unnecessary.

Upvotes: 2

chux
chux

Reputation: 153447

fgets() is a better way to go, read the line of user input and then parse it.

But is OP still wants to use scanf()....

Since it is not possible to "detect that the input was too long" without attempting to read more than the n maximum characters, code needs to read beyond.

unsigned char sentinel;
char str[101];
str[0] = '\0';

if (scanf("%100s%c", str, &sentinel) == 2) {
  ungetc(sentential, stdin);  // put back for next input function
  if (isspace(sentential) NoTrimOccurred();
  else TrimOccurred();
 else {
   NoTrimOccurred();
 }

Upvotes: 1

Sourav Ghosh
Sourav Ghosh

Reputation: 134316

A very rough but easy way of doing this would be, adding a getchar() call after the scanf().

scanf() leaves the newline into the input buffer after reading the actual input. In case, the supplied input is less than the maximum field width, getchar() would return the newline. Otherwise, the first unconsumed input will be returned.

That said, the ideal way of doing it is to actually read a bit more than the required value and see if anything appears in the buffer area. You can make use of fgets() and then, check for the 100th element value to be a newline or not but this also comes with additional cost.

Upvotes: 0

Related Questions