2

The problem is in the fourth scanf. Please help me understand the use of scanf("%[^\n]%*c", c).

    char a;
    char b[30];
    char c[50];
    
    scanf("%c", &a);
    scanf("%s", b);
    scanf("\n");
    
    scanf("%[^\n]%*c", c);
    
    printf("%c \n", a);
    printf("%s \n", b);
    printf("%s \n", c);
    return 0;
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 3
    This is why you need to have [the documentation](https://en.cppreference.com/w/c/io/fscanf) open when learning C. See `[set]` there. – tadman Aug 25 '23 at 15:18
  • 1
    Don't try to understand `scanf()` - just don't use it. `scanf()` is perverse and has strange corner cases that can lead to undefined behavior and/or data loss. If you're parsing text input in lines, use `fgets()` (or `getline()` if available), and then parse the text read in. – Andrew Henle Aug 25 '23 at 15:55
  • The key to using `scanf` is to understand how it handles whitespace characters, in the format string and from the input. The posted code shows a lack of that - note the `scanf("\n");` kludge to get rid of the newline that was left in the buffer. It's generally a bad idea trying to deal with *trailing* whitespace: better to deal with *leading* whitespace. That, and the kludge which the question asks about should be removed, and replaced with a leading space where needed, to filter the leading whitespace. So `scanf(" %c", &a);` and `scanf(" %[^\n]", c);` . . . – Weather Vane Aug 25 '23 at 17:21
  • ... Some explanation: most of the format specifiers for `scanf` automatically filter leading whitespace, but `%c` and `%[]` and `%n` do not. Adding a space in front of the `%` instructs `scanf` to filter leading whitespace here too. The reason is those three specifiers allow you to read every character including whitespace, but a way is provided so they behave like `%d` and `%f` and `%s` (etc) where needed. Moreover, the leading space does not just filter *one* character - it filters *any amount* of whitespace, including none. But in the kludges, there must be exactly one whitespace character. – Weather Vane Aug 25 '23 at 17:21

1 Answers1

5

%[ matches any character found in its set of characters. ] terminates the set.

  • For example, with the input hello world, %[ehlo] matches hello, stopping when it encounters the space which is not found in the set.

Starting that set with ^ inverts the match, matching any character not found in the set.

The effect: %[^\n] matches as many characters as it can, that are not the newline character.

%c matches any character. Adding *, as in %*c, prevents scanf from assigning that character to any variable.

The overall effect is that "%[^\n]%*c" reads as many characters as it can, storing those characters in a buffer until it reaches a newline character. It then consumes and discards the newline. Effectively, it reads a line of text.


Note that not specifying a maximum field-width when using %[ (or %s) leaves your program open to buffer overflows (same problem as using gets()).

To avoid potentially undefined behaviour, you should always provide the maximum number of characters to be read

char c[50];
scanf("%49[^\n]%*c", c);

which should be at most the size of the buffer minus one (to leave room for the null-terminating byte, '\0').

In this particular example, you will experience data loss if the string entered is longer than 49 characters, as %*c will consume something other than the newline.

For these reasons, it is generally advisable to instead use fgets when you want to read a line of text, as you can more easily detect oversized lines.

Oka
  • 23,367
  • 6
  • 42
  • 53
  • Also note that `scanf("%[^\n]%*c", c);` will fail and return `0` on an empty line, which will not be consumed at all. – chqrlie Aug 25 '23 at 16:16