1

This is a program which takes input from the user ie: roll.no., name, and his physics, chemistry & maths marks and prints them.

If %[^\n]s is used for taking string input, it doesn't work in this program.

This programs works fine

#include <stdio.h>
#include <string.h>

void main()
{
    int roll_no;
    char student[1000];
    float physics, maths, chemistry;
    printf("Enter your roll no. : ");
    scanf("%d", &roll_no);
    fflush(stdin);
    printf("\nEnter your name : ");
    fflush(stdin);
    scanf("%s", student);
    fflush(stdin);
    printf("\nEnter your physics : ");
    scanf("%f", &physics);
    printf("\nEnter your maths : ");
    scanf("%f", &maths);
    printf("\nEnter your chemistry : ");
    scanf("%f", &chemistry);
    
    printf("Student's Name : %s\nPhysics Marks : %.2f Chemistry Marks : %.2f Maths Marks : %.2f",
           student, physics, chemistry, maths);
}

In order to take student's full name (first name and surname) I later used scanf("%[^\n]s", student); instead of scanf("%s", student);

When I used %[^\n]s for student array, the program takes roll no. input from the user then skips the student scanf. It directly jumps to physics scanf for taking physics marks. fflush(stdin) has also been used to prevent scanf malfunctioning while taking multiple data types but still it doesn't seem to work

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Pratyush
  • 11
  • 1
  • 2
    Please note that passing an input-only stream (like `stdin`) to `fflush` is explicitly mentioned in the C specification as leading to *undefined behavior*. And for the compilers that support it (IIRC only one of the three major compilers actually do), then it's not needed for the code you show. – Some programmer dude Sep 02 '23 at 09:55
  • 2
    `fflush(stdin);` may not do what you're hoping. In `%[^\n]s` the trailing s is incorrect. [scanf() leaves the newline character in the buffer](https://stackoverflow.com/questions/5240789/scanf-leaves-the-newline-character-in-the-buffer) is worth a read for the correct way to handle this. – Retired Ninja Sep 02 '23 at 09:56
  • 2
    And there is no `%[...]s` format, only `%[...]`. Note that the trailing `s` is *not* part of the format specifier. If you use it, then `scanf` expects an actual `s` character as input. – Some programmer dude Sep 02 '23 at 09:57
  • 1
    For future questions, please show us the code that actually fails, the one you have problem with and want to ask about. The shown code doesn't have the problem you're asking about, it's not a [mre]. – Some programmer dude Sep 02 '23 at 10:02
  • 1
    Prevent overrun specifying max character count for strings. Eg `scanf( "%999[^\n]", name );` – Fe2O3 Sep 02 '23 at 10:03
  • 1
    As for your problem of reading whole lines, create your own portable "flush" function. But note that it's not needed everywhere you currently use `fflush`. Then use [`fgets`](https://en.cppreference.com/w/c/io/fgets) to read the whole line. – Some programmer dude Sep 02 '23 at 10:03
  • 1
    Always check the return value from `scanf` (the number of items satisified). Then you'll see that after entering a name with a space in it, `scanf("%f", &physics);` has failed. – Weather Vane Sep 02 '23 at 10:13
  • "If %[^\n]s is used..." Your probable `"%[^\n]s"` should be `" %999[^\n]"` (three changes). – Weather Vane Sep 02 '23 at 10:16
  • On Linux with a terminal emulator, the [GNU readline](https://en.wikipedia.org/wiki/GNU_Readline) library could be useful. Be sure to enable all warnings and debug info in your compiler: with [GCC](https://gcc.gnu.org/) compile your `program.c` with `gcc -Wall -Wextra -g program.c -o exercise.bin` then use [GDB](https://www.sourceware.org/gdb/) for debugging. Study for inspiration source code of existing open source programs (e.g. [GNU bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell))...) – Basile Starynkevitch Sep 02 '23 at 11:53

1 Answers1

3

There are multiple issues and misunderstandings in your question:

  • there is no %[^\n]s conversion specification, the trailing s is not part of the conversion, it acts as a character s that must matched an input character, which will not happen since %[^\n] only stops before a newline or at end of file.

  • fflush(stdin) has undefined behavior. It does not flush the input stream. You can read and discard the remaining characters in the input line including the newline with a loop:

        int c;
        while ((c = getchar()) != EOF && c !+ '\n')
            continue;
    
  • the difference between %s and %[^\n] is the behavior on whitespace characters: %s ignores leading white space, including newlines, then reads and stores characters into the destination array until white space is read and pushed back into the input stream. Conversely %[^\n] reads and stores any character except a newline, which is pushed back.

  • the consequence of the above behavior is scanf("%s", student) ignores the pending newline left by scanf("%d", &roll_no) and any other leading whitespace, then reads further input until the next whitespace character is found, which is left pending in the input stream. scanf("%[^\n]", student) stops immediately and returns 0 because the pending newline is not ignored and does not match the specification. To skip this leading whitespace, you should use a space in the scanf format string: scanf(" %[^\n]", student)

  • both %s and %[^\n] can store an arbitrary number of characters into the destination array if the input contains a long sequence of matching characters. This is a classic security flaw that can be exploited by attackers using carefully constructed input lines. You should pass the maximum number of characters to store as scanf("%999s", student) or scanf(" %999[^\n]", student);

  • you should always test the return value of scanf() to detect invalid or missing input and avoid undefined behavior when using the still uninitialized destination variables.

  • the prototype for main without arguments is int main(void).

Here is a modified version:

#include <stdio.h>

int main(void) {
    int roll_no;
    char student[1000];
    float physics, maths, chemistry;

    printf("Enter your roll no.: ");
    if (scanf("%d", &roll_no) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your name: ");
    if (scanf(" %999[^\n]", student) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your physics: ");
    if (scanf("%f", &physics) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your maths: ");
    if (scanf("%f", &maths) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }
    printf("\nEnter your chemistry: ");
    if (scanf("%f", &chemistry) != 1) {
        fprintf(stderr, "invalid input\n");
        return 1;
    }

    printf("Student Name: %s\n"
           "Physics Marks: %.2f Chemistry Marks: %.2f Maths Marks: %.2f",
           student, physics, chemistry, maths);
    return 0;
}

You can try and avoid code duplication and reprompt the user in case of error by using custom input functions such as in the modified version below:

#include <stdio.h>

int get_integer(const char *prompt, int *dest, int min, int max) {
    for (;;) {
        int val, c, res;
        printf("%s: ", prompt);
        res = scanf("%d", &val);
        while ((c = getchar()) != EOF && c != '\n')
            continue;
        if (res == 1) {
            if (val >= min || val <= max) {
                *dest = val;
                return 1;
            }
            printf("value %d out of range [%d..%d]\n", val, min, max);
        } else
        if (res == EOF || c == EOF) {
            printf("unexpected end of file\n");
            return 0;
        } else {
            printf("invalid input\n");
        }
    }
}

int get_float(const char *prompt, float *dest) {
    for (;;) {
        float val;
        int c, res;
        printf("%s: ", prompt);
        res = scanf("%f", &val);
        while ((c = getchar()) != EOF && c != '\n')
            continue;
        if (res == 1) {
            *dest = val;
            return 1;
        } else
        if (res == EOF || c == EOF) {
            printf("unexpected end of file\n");
            *dest = 0;
            return 0;
        } else {
            printf("invalid input\n");
        }
    }
}

int get_string(const char *prompt, char *dest, size_t size) {
    for (;;) {
        int c;
        size_t n = 0, i = 0;
        printf("\n%s: ", prompt);
        while ((c = getchar()) != EOF && c != '\n') {
            if (i + 1 < size)
                dest[i++] = (char)c;
            n++;
        }
        if (i < size) {
            dest[i] = '\0';
        }
        if (n == 0) {
            if (c == EOF) {
                printf("unexpected end of file\n");
                return 0;
            }
            printf("string cannot be empty\n");
        } else {
            return 1;
        }
    }
}

int main(void) {
    int roll_no;
    char student[1000];
    float physics, maths, chemistry;

    if (!get_integer("Enter your roll no.", &roll_no, 0, 10000)
    ||  !get_string("Enter your name", student, sizeof student)
    ||  !get_float("Enter your physics marks", &physics)
    ||  !get_float("Enter your maths marks", &maths)
    ||  !get_float("Enter your chemistry marks", &chemistry)) {
        return 1;
    }

    printf("Student Name: %s\n"
           "Physics Marks: %.2f Chemistry Marks: %.2f Maths Marks: %.2f\n",
           student, physics, chemistry, maths);
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • *You can try and avoid code duplication and reprompt the user in case of error by using custom input functions such as those promoted by the Harvard course cs50.* Just don't **ever** use or recreate the horror of CS50's confusing `typedef char *string` abomination. Obfuscating C string handling like that creates serious misconceptions about a lot of fundamental C concepts. – Andrew Henle Sep 02 '23 at 12:12
  • @AndrewHenle: I agree with you, this typedef is misleading and toxic. Yet the input wrappers are interesting. I shall modify the answer and provide examples instead of promoting cs50. – chqrlie Sep 02 '23 at 12:21