1

I'm a beginner in C, and I've got problem I can't figure out, and wasn't able to find a solution on other threads here.

I'm trying to read integers from a keyboard input/ txt file with the following code:

int grades[MAX_GRADES_LENGTH]={0}, histogram[HISTOGRAM_SIZE]={0};
int maxGradesHistogramBucket=0, median=0, gradesLength=0;
double avg=0.0;
int grade=0;

printf("Enter grades:\n");
while (scanf("%d",&grade) != EOF)
{
    grades[gradesLength]=grade;
   gradesLength=gradesLength+1;
}

I'm supposed to set these "grades" in the grades[] array and count the length of the array along the loop.

Somehow the loop is misbehaving, it seems that some inputs are ok with the loop, but for some inputs(most of them actually) the scanf() doesn't get the EOF, whether it's an actual end of file, or the ^D command in the terminal.

I've heard the scanf() is not the most reliable method to read data, but unfortunately I can't use anything else since we haven't learned any other method in class so we can only use scanf() on our homework.

I've tried to change the!= EOF with == 1 and its all the same.

for the input

100 0 90 10 80 20 70 30 60 40 50

for example it works fine. but for the input:

0 50 100

the loop is infinite.

I'm using a macbook pro btw (if it matters).

Any ideas?

Avishay Cohen
  • 19
  • 1
  • 4
  • What is the value of `MAX_GRADES_LENGTH`? – chux - Reinstate Monica Dec 30 '14 at 23:27
  • 1
    Please provide complete example. – kestasx Dec 30 '14 at 23:29
  • Add `if ([gradesLength >= MAX_GRADES_LENGTH) break;` before `grades[gradesLength]=grade;` to while loop to insure code does not read too much. – chux - Reinstate Monica Dec 30 '14 at 23:33
  • The EOF char on my console is Ctrl-Z not Ctrl-D and this posted code does work (although not perfect), similar to answer from @DirkKoopman. – Weather Vane Dec 30 '14 at 23:43
  • 1
    If you're on a MacBook Pro, the EOF character is control-D unless you've deliberately remapped it. Control-Z suspends the running program, but that means "sends SIGSTOP" and puts it into suspended animation. The shell prompts again, but the program is not dead. You should be able to see it with `jobs` — if you've been using control-Z very much, you might be in for a surprise. You can check your character mappings with `stty -a` and look in the 'cchars' section of the output at the end. On my Mac, the output includes: `cchars: … eof = ^D; … erase = ^?; intr = ^C; … susp = ^Z; …`. – Jonathan Leffler Dec 30 '14 at 23:53
  • @JonathanLeffler on a PC with MS os. – Weather Vane Dec 30 '14 at 23:57
  • Sure would be nice if OP provide more data. – chux - Reinstate Monica Dec 31 '14 at 00:05
  • @WeatherVane: agreed that on MS Windows the control-Z character is EOF, but the question says "I'm using a macbook pro btw (if it matters)". Consequently, 'Control-Z is EOF' only applies if you've futzed with `stty` or you're running a VM of some sort that is running Windows after all. – Jonathan Leffler Dec 31 '14 at 00:12
  • @Jonathan Leffler I wan't going to go and an buy a macbook so I told anyone else who won't, that it's ctrl-z, just like you belaboured another point to noobs in YOUR answer. – Weather Vane Dec 31 '14 at 00:23
  • scanf returns the number of items read. When would it ever return EOF? – EvilTeach Dec 31 '14 at 00:39

2 Answers2

3

If you type a letter instead of a number, scanf() will return 0 (as in, "zero successfully converted numbers") and not EOF (as in, "there was no data left to read"). The correct test is to ensure that you got the expected number of values — in this case, 1:

while (scanf("%d", &grade) == 1)

If you need to know whether you got to EOF or got no result (but reading the rest of the line might clear the problem), then capture the return value from scanf():

int rc;
while ((rc = scanf("%d", &grade)) == 1)
{
}
if (rc != EOF)
    …read the rest of the line, or at least the next character, before resuming the loop…

And, if you really want to, you could then write:

int rc;
while ((rc = scanf("%d", &grade)) != EOF)
{
    if (rc == 1)
        grades[gradesLength++] = grade;
    else
    {
        printf("Discarding junk: ");
        int c;
        while ((c = getchar()) != EOF && c != '\n')
            putchar(c);
        putchar('\n');
        if (c == EOF)
            break;
    }
}

The code in the else clause could plausibly be put into a function. It might also report the messages to standard error rather than standard output. It is courteous to let the user know what it was that you objected to. You could stop before newline with a different test (&& !isdigit(c) && c != '+' && c != '-', using isdigit() from <ctypes.h>). However, the user doesn't have a chance to re-edit the stuff they put after the letters, so you may be going to misinterpret their input. It is probably better just to throw away the rest of the line of input and let them start over again.


As chux noted, after reading a character that could be part of an integer, that character needs to be put back into the input stream. Therefore, if I were going to analyze the rest of the line and restart scanning at the first data that could actually be part of an integer, I'd consider using something like:

#include <ctype.h>

static inline int could_be_integer(int c)
{
    return isdigit(c) || c == '+' || c == '-');
}

and then the else clause might be:

else
{
    printf("Discarding junk: ");
    int c;
    while ((c = getchar()) != EOF && c != '\n' && !could_be_integer(c))
        putchar(c);
    putchar('\n');
    if (could_be_integer(c))
        ungetc(c, stdin);
    else if (c == EOF)
        break;
}

This gets messy, as you can see. Sometimes (as Weather Vane noted in a comment), it is easier to read a line with fgets() and then use sscanf() in a loop (see How to use sscanf() in a loop?). Be wary of suggestions about Using fflush(stdin); it isn't automatically wrong everywhere, but it won't work on a MacBook Pro under normal circumstances.

On the whole, simply ignoring the rest of the line of input is usually a better interface decision.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • or discard input with `fflush(stdin)` – Weather Vane Dec 30 '14 at 23:55
  • @WeatherVane: I don't work on systems where that works, so I don't do it. See [Using `fflush(stdin)`](http://stackoverflow.com/q/2979209) for the details (noting the comments to the question as much as the accepted answer — which is, in my humble opinion, wrong when the platform is Microsoft Windows and the runtime library is the MS C runtime library). – Jonathan Leffler Dec 30 '14 at 23:59
  • 1
    Detail: Stopping when `isdigit(c) ...` needs a following `unget()`. – chux - Reinstate Monica Dec 31 '14 at 00:00
  • 1
    @chux: `ungetc()`, but yes. All the more reason for chomping until newline. – Jonathan Leffler Dec 31 '14 at 00:01
  • All this twiddling required with `scanf()` shows why I don't even use it. Far better the `fgets()` with a blank entry (a newline) indicating no more input. – Weather Vane Dec 31 '14 at 00:02
  • @WeatherVane: fair enough; I would normally just stop on the mismatch and not bother with the error reporting, but it is a conscious decision to do that. I'm trying to educate the questioner on what the options are. – Jonathan Leffler Dec 31 '14 at 00:04
  • Good move, his question required `scanf()`. – Weather Vane Dec 31 '14 at 00:06
  • `scanf("%s", discardstring);` would also work to report on and clear the input buffer. – Weather Vane Dec 31 '14 at 00:10
  • @WeatherVane: So would `scanf("%*c");` — there are many ways of dealing with the issue. If you wish to write your own encyclopedic answer on the options, please don't let me stop you, but additional comments in a similar vein are not really going to be welcome here. It's not that you're wrong; you're just making my answer more complex than I want it to be. – Jonathan Leffler Dec 31 '14 at 00:16
  • That didn't stop you adding a remark about `fgets()` in your answer after I mentioned it. – Weather Vane Dec 31 '14 at 00:43
  • @WeatherVane: No; but that was intended to be the last such edit. I've just added credit to you — omitting that was careless. – Jonathan Leffler Dec 31 '14 at 00:46
1

It works for me.

I enclosed your snippet thus:

#include <stdio.h>
#include <errno.h>

#define MAX_GRADES_LENGTH 20
#define HISTOGRAM_SIZE 20

main()
{

    int grades[MAX_GRADES_LENGTH]={0}, histogram[HISTOGRAM_SIZE]={0};
    int maxGradesHistogramBucket=0, median=0, gradesLength=0;
    double avg=0.0;
    int grade=0;
    int i;

    printf("Enter grades:\n");
    while (scanf("%d",&grade) != EOF)
    {
        grades[gradesLength]=grade;
        gradesLength=gradesLength+1;
    }

    if (errno)
        perror("grade");

    for (i = 0; i < gradesLength; ++i) {
        printf("%d\n", grades[i]);
    }
}

and ran it:

$ a.out
Enter grades:
100 0 90 10 80 20 70 30 60 40 50
100
0
90
10
80
20
70
30
60
40
50
$ a.out
Enter grades:
0 50 100
0
50
100
$

Perhaps you are looking in the wrong place. Maybe the bug is in your output routine?

Personally, if had to do this, given some ambiquity over what scanf returns when, and without rewriting it, then this small change is probably more reliable:

int i, r;

printf("Enter grades:\n");
while ((r = scanf("%d",&grade)) > 0)
Dirk Koopman
  • 131
  • 5
  • Try entering 'a' as one of the grades. The code fails horribly then. – Jonathan Leffler Dec 30 '14 at 23:44
  • @Jonathan scanf is a horrible routine but, for what it is worth, my man page says this for "Return Code" - "The value EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs. EOF is also returned if a read error occurs, in which case the error indicator for the stream (error(3)) is set, and errno is set indicate the error." – Dirk Koopman Dec 30 '14 at 23:53
  • And yes, scanf *will* SEGV, but then that is one of many reasons why one *very* rarely uses scanf. But that wasn't the specific question. If the data is entered as per the question, then the output is as shown. – Dirk Koopman Dec 31 '14 at 00:01
  • Which man page on which system? Let's check: POSIX (C) [`scanf()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/scanf.html) specifies: _ If the input ends before the first conversion (if any) has completed, and without a matching failure having occurred, EOF shall be returned._ But what's a 'matching failure'? I'm working through the specification — but I'm tolerably certain that for a letter where a digit (number) is expected, `scanf()` returns 0, not EOF. – Jonathan Leffler Dec 31 '14 at 00:02
  • Yes if text was entered instead of a number, 0 is returned and the text remains in the input buffer. – Weather Vane Dec 31 '14 at 00:05
  • 1
    First of all, thanks for the help. This code is part of my homework and I was promised that the input will be a set of integers so I don't have to worry about anything else. It seems that the code just as t is works fine, but not with rest of the code like you mentioned so the issue is probebly somewhere else. I'll just try to find it from scratch. Again I appreciate your help. – Avishay Cohen Dec 31 '14 at 16:28