1

I've been introduced to C this year as part of my degree, during which I have to write simple programs and test them to be idiot-proof by running them over and over again, putting nonsense variables in, etc. and I had an idea to write a program with the ability to restart itself without having to run the program again.

I've tried writing a program to perform this function (which turned out to be harder than I first thought) and I now have it working, albeit using a goto function that are frowned upon. Now the only problem I have is a while loop to check for nonsense input, that seems determined to run at least once ignoring a prompt for a valid input.

Please could someone give me an idea why this is happening? (My compiler is Dev-C++ 4.9.9.2)

int main (void)
{
  mainprogram:
            printf("\nPROGRAM START\n");

    //code copied from an exam, to check that the program performs a function
    //when ran through again

    int i,j,k;

    printf("Please enter 7:");
    scanf("%d",&i);

    printf("Please enter 4:");
    scanf("%d",&j);

    printf("Please enter 0:");
    scanf("%d",&k);
    //this is to check that the program will take input when it is restarted
    do {
        switch (i%j) {
            case 3:
                i--;
                k*=i;
                break;
            case 2:
                i--;
                k+=i;
            default:
                i--;
                k++;
                break;
        }
        printf("i is %d k is %d\n",i,k);
    } while (i>0);
    //end of copied code

    char prompt='y';
    printf("\nRestart program?");
    scanf("%c",&prompt);

    while (prompt != 'y' && prompt != 'Y' && prompt != 'n' && prompt != 'N')
    {

    //this is the problem section, when entering nonsense input, the error messages
    //display twice before pausing for input, and when restarted, the program does 
    //run again but displays the error messages once before pausing for input

       printf("\nERROR: INVALID INPUT");
       printf("\n\nRestart program?");
       prompt='y';
       scanf("%c",&prompt);
    }


    if (prompt == 'y' || prompt == 'Y') 
    {
          goto mainprogram;
          }

    //
    return 0;
}
Haris
  • 12,120
  • 6
  • 43
  • 70
  • 2
    Lost `break;` for `case 2`. – herohuyongtao Jan 25 '14 at 18:55
  • 1
    replace goto mainprogram with just another while loop: `do { ... } while (prompt != 'y' && prompt != 'Y');` – AndersK Jan 25 '14 at 18:59
  • Change `scanf("%c",&prompt);` to `scanf(" %c",&prompt);` added a space before `%c` in format string. – Grijesh Chauhan Jan 25 '14 at 19:00
  • Looks like the usual problem of scanf("%d") that does not consume the carriage returns. Try "%d\n" for your scanfs and see how it goes. – kuroi neko Jan 25 '14 at 19:02
  • @kuroineko, that's not the way to do it. If you want to eat whitespace, use `' '` not `'\n'`. That said, you don't need to consume whitespace after `"%d"`, because the following `scanf` may consume it anyway. If the following is `%c` or `%[`, just prefix it with a whitespace. This is not a _problem_ of scanf. Consuming leading whitespace when you already have your number extracted is plain silly. – Shahbaz Jan 25 '14 at 19:20

3 Answers3

0

I just reformatted your code and indeed @ herohuyongtao is right, the break; for case 2 has moved at the end of default which is not useful there.

But there's something really shocking in your code, is that you use a goto. Just remember that rule: WHENEVER YOU USE GOTO, THERE'S A BETTER WAY TO DO IT!

#include <stdio.h>

short int read_input (void) {
    printf("\nPROGRAM START\n");

    //code copied from an exam, to check that the program performs a function
    //when ran through again

    int i,j,k;

    printf("Please enter 7:");
    scanf("%d",&i);

    printf("Please enter 4:");
    scanf("%d",&j);

    printf("Please enter 0:");
    scanf("%d",&k);
    //this is to check that the program will take input when it is restarted
    do {
        switch (i%j) {
            case 3:
                i--;
                k*=i;
                break;
            case 2:
                i--;
                k+=i;
                break; // break at the right spot
            default:
                i--;
                k++;
        }
        printf("i is %d k is %d\n",i,k);
    } while (i>0);

    // getchar(); // could be inserted here (discards one char)
    // fflush(stdin); // could also do the job (discards all remaining chars in buffer)

    char prompt='y';

    // here the design choice is to let the user input whatever
    // and not updated the output until the right character is given
    // which will hide how many wrong chars has been previously in the buffer.
    printf("\nRestart program?  ");
    do {
        prompt = getchar();
    } while (prompt != 'y' && prompt != 'Y' && prompt != 'n' && prompt != 'N');

    if (prompt == 'y' || prompt == 'Y') 
        return 1;
    return 0;
}

int main() {
    while (read_input() == 1);
    return 0;
}

Now that the code is clean, for your exact problem, you will get into a carriage return problem. Basically, what you do is get input until \n is hit, but when you hit carriage return, you actually send CR + LF. So there's one character that never gets read in the input buffer, that you need to discard.

So you should first read that SO question that sums up very well your problem, and you can either:

  • add a lone getchar(); just after your //end of copied code comment,
  • or a fflush(stdin) can do the job (cf the SO Question to learn more about it) but has been designed about flushing output buffers, not input ones,
  • add a fseek(stdin,0,SEEK_END); which is a bit dirty (and non-portable) but works,
  • or change your conditions and your use of scanf to take into account that you're actually having more chars in the buffer.

In the code I gave you, I chose the most simplistic solution, which is to discard any wrong input without printing anything.

Community
  • 1
  • 1
zmo
  • 24,463
  • 4
  • 54
  • 90
  • The `fseek` method is really bad. For example, if your input is redirected from a file, you basically go to the end of the file. There's not much sense in that. Also, a _lone_ `getchar()` may not be enough, because you don't know how much whitespace there are. The best way in this case is to just use the `" %c"` format string so `scanf` would consume any whitespace there is. – Shahbaz Jan 25 '14 at 19:28
  • I agree about the fseek, that's why I only placed it as an option, not the solution, there's the `fflush()` solution I did not tell about, but the OP will be able to see it if he reads the linked question. You're also right that `scanf` by design may be leaving out many other invalid characters, and that's a design choice to discard them one way or another. I just consider that `scanf()` for reading a single char is often like killing a fly with a rocket. – zmo Jan 25 '14 at 19:34
  • `fflush` is not a solution. `fflush` on input streams has undefined behavior. It also suffers from the same problem I mentioned with `fseek`. What I'm trying to say is that it can be confusing to beginners to tell them "you can do X, but it's bad", especially when doing X is actually wrong. – Shahbaz Jan 25 '14 at 20:03
  • Of course, but it's context dependent and *can* be used with care. It all depends on where it will be used. [`scanf` is as well dangerous](http://c-faq.com/stdio/scanfprobs.html) and should not be advised lightly to beginners, so basically you're doing what you're telling me not to do! What is important is to give the RTFM that matches the advices. – zmo Jan 25 '14 at 20:09
  • While it's true that [`scanf()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/scanf.html) can be a dangerous function to use, `scanf()` is still a _valid_ funtion to use, so long as you do so (very) carefully. On the other hand, [`fflush(stdin)`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fflush.html) is inherently wrong , _by definition_, and should not be offered as a suggestion under any circumstances. – This isn't my real name Jan 30 '14 at 01:34
0
while(1){    //parent

 printf("\n\nRun program?");
 scanf("%c",&prompt);

if (prompt == 'n' || prompt == `N`)
{
  printf("\nEXITINT")
  return 0;
}



int i,j,k;

printf("Please enter 7:");
scanf("%d",&i);

printf("Please enter 4:");
scanf("%d",&j);

printf("Please enter 0:");
scanf("%d",&k);

switch (i%j)
{
case 3:
i--;
k*=i;
break;
case 2:
i--;
k+=i;
break;
default:
i--;
k++;
break;
}
printf("i is %d k is %d\n",i,k);
} //end while parent 

//end of copied code

Ansh David
  • 654
  • 1
  • 10
  • 26
0

There are a couple of ways one could restart a program, or more generally loop over some code. Of course, all of that can be done with gotos, but that makes the code hard to understand, so C has equivalent structures for dealing with the most common patterns:

execute code if and as long as condition holds

while (condition)
{
    /* code */
}

This means before executing the code, the condition is checked. If the condition holds (its value is non-zero), the code is executed and then looped back to the top. This is equivalent to:

top_of_while:
    if (!condition)
        goto done;

    /* code */
    goto top_of_while:
done:

execute code and redo while condition holds

do
{
    /* code */
} while (condition)

This means execute code first and then check for a condition. If the condition holds, executed the code again. This is equivalent to:

top_of_do_while:
    /* code */
    if (condition)
        goto top_of_do_while;

iteration

for (initialization; condition; iteration)
{
    /* code */
}

This is a kind of while loop that happens a lot, in which there is an initialization, followed by a while loop, which on the bottom changes a variable to form some sort of iteration. This is equivalent to:

initialization;
while (condition)
{
    /* code */
    iteration;
}

To restart a program, most likely you want the do-while loop, since for sure you know that the program has to execute once. However, by properly initialization the condition variable of a while loop, you can also ensure that the loop is always entered the first time. It's a matter of style and your liking.


Where to actually use goto

Many people would tell you to never use goto. This roots from the fact that overuse of goto had led to a great number of overly complicated programs, the so-called spaghetti code. The reason is that it's hard to build a mental model of a program where the execution can jump around to any other part.

However, gotos are actually very useful in C, without which error-handling becomes a huge pain in the ... neck. This useful usage of goto is for handling errors and cleanup, which are always in the bottom of the function. An example would be:

int *read_nums(int n)
{
    int *array, i;

    array = malloc(n * sizeof *array);
    if (array == NULL)
        goto exit_no_mem;

    for (i = 0; i < n; ++i)
        if (scanf("%d", &array[i]) != 1)
            goto exit_bad_input;

    return array;

exit_bad_input:
    free(array);
exit_no_mem:
    return NULL;
}

This way, the code is not cluttered (much) with error handling and cleanup is done very nicely based on how far the function has executed.

Shahbaz
  • 46,337
  • 19
  • 116
  • 182