0

I am working on a simple program with C where I need to calculate averages and etc. My first function is to prompt the user parameters which (I'm assuming) is stored in global variables so that I can use them in the other functions such as "calculating MIPS" etc.

My problem is that when I call the function to collect input from the user, the program stops after the first prompt. There are 7 prompts in total. Does anyone know what am I doing wrong? And what are some suggestions that i can do to be able to ask all my prompts and store values? I was thinking of maybe using a while loop but I'm not sure how to terminate the loop when the values are all store in the global variables. I'm sure I'm over complicating a simple problem.

I have a snip of my program below:

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

/* declare global var's */
int intr_class = 0;
int freq = 0;
int class1 = 0;
int cpi_1 = 0;
int class2 = 0;
int cpi_2 = 0;
int class3 = 0;
int cpi_3 = 0;

void params(){

    printf("Enter the number of instruction classes: ");
    scanf("%s", intr_class);

    printf("Enter the frequency of the machine (MHz): ");
    scanf("%s", freq);

    printf("Enter CPI of class 1: ");
    scanf("%s", cpi_1);

    printf("Enter instruction count of class 1 (millions): ");
    scanf("%s", class1);

    printf("Enter CPI of class 2: ");
    scanf("%s", cpi_2);

    printf("Enter instruction count of class 2 (millions): ");
    scanf("%s", class2);

    printf("Enter CPI of class 3: ");
    scanf("%s", cpi_3);

    printf("Enter instruction count of class 3 (millions): ");
    scanf("%s", class3);
}

int main() {
    /* declare local var's */
    char menuChoice[0];

    /* until user quits, loop */
    /* print out menu list */
    printf("Menu of Options:\n______________\n"
           "a) Enter Parameters\n"
           "b) Calculate average CPI of a sequence of instructions\n"
           "c) Calculate total execution time of a sequence of instructions\n"
           "d) Calculate MIPS of a sequence of instructions\n"
           "e) Quit\n\n"
           "Enter selection: ");
    scanf("%s", menuChoice);

    /* prompt for selection & choose appropriate procedure using either a case statement of if-else if-else statements or switch scanf for character input */
    while(menuChoice[0] != 'e'){
        switch(menuChoice[0]){
            case 'a':
                params();
                break;
            case 'b':
                //avgCPI();
                break;
            case 'c':
                //calcExTime();
                break;
            case 'd':
                //calcMIPS();
                break;
            case 'e':
                exit(0);
            default:
                printf("Menu of Options:\n______________\na) Enter Parameters\nb) Calculate average CPI of a sequence of instructions\nc) Calculate total execution time of a sequence of instructions\nd) Calculate MIPS of a sequence of instructions\ne) Quit\n\nEnter selection: ");
        }
    }
    return(0);
}
Ed Heal
  • 59,252
  • 17
  • 87
  • 127
jemmamariex3
  • 107
  • 2
  • 17
  • 1
    You are using scanf wrong. scanf ("%d", &variable_name) – CIsForCookies Feb 18 '18 at 05:04
  • `scanf("%s", intr_class);` How do you expect to read an integer value with a string *format specifier*? You need to spend time with [Linux man pages : scanf (3)](http://www.manpages.info/linux/scanf.3.html) You will then probably find `scanf("%d", &intr_class);` closer to what you want. Be wary. Taking user input with `scanf` is full of pitfalls for the new C programmer. – David C. Rankin Feb 18 '18 at 05:05
  • @CIsForCookies Thank you so much for the correction! definitely made that change but my program is still terminating after the first prompt. – jemmamariex3 Feb 18 '18 at 05:08
  • You should put your menu string into an array (laid out using string concatenation, more or less as shown in the first `printf()` in the revised code), and then use that in both places. Don't Repeat Yourself — DRY. – Jonathan Leffler Feb 18 '18 at 05:08
  • 1
    Did you both add the `&` and change `%s` to `%d`? – Jonathan Leffler Feb 18 '18 at 05:09
  • 3
    `char menuChoice[0];` is also not allowed in C. No zero length arrays. Your compiler should be screaming warnings at you. – David C. Rankin Feb 18 '18 at 05:10
  • 1
    @DavidC.Rankin: GCC has funny ideas about 0-length arrays being useful. – Jonathan Leffler Feb 18 '18 at 05:10
  • Why are you using global variables. Perhaps look at this https://stackoverflow.com/questions/48721053/reading-an-enter-in-a-string/48839472#48839472 – Ed Heal Feb 18 '18 at 05:10
  • Yes, there are caveats, but the standard is kinda clear. [C11 - 6.7.6.2 Array declarators](http://port70.net/~nsz/c/c11/n1570.html#6.7.6.2) – David C. Rankin Feb 18 '18 at 05:11
  • 1
    @DavidC.Rankin: Agreed — the standard is clear, but compilers don't all implement all of the standard. In fairness, GCC will complain if you tell it to be pedantic (`-pedantic`, or `-Wpedantic`), but it should complain by default and require an option (`-fzero-length-arrays` or thereabouts) before it permits them. Clearly, my priorities are different from the developers of GCC. – Jonathan Leffler Feb 18 '18 at 05:12
  • @JonathanLeffler great tips! thanks I will do that. I did change to %d but I will read up on the ampersand. Not really sure what that will do yet. – jemmamariex3 Feb 18 '18 at 05:15
  • @EdHeal Probably a weird question: if I don't use global variables, I wouldn't be able to use those variables in the other functions no? I have about 4 other functions that will be using the same values. Thats the reason why I was thinking of using global – jemmamariex3 Feb 18 '18 at 05:19
  • 1
    The amersand is critical. It's the way to know where to store the value of the input. Without it your code stI'll invokes UB – CIsForCookies Feb 18 '18 at 05:21
  • 2
    @jemmamariex3 - Use a structure and pass a pointer to it in the argument for the function – Ed Heal Feb 18 '18 at 05:22
  • Anyway, Thanks for the help. The amperstand did the trick :) – jemmamariex3 Feb 18 '18 at 05:24
  • @EdHeal Understood. Thank you! – jemmamariex3 Feb 18 '18 at 05:25

4 Answers4

2

You encounter UB since this is not the way to use scanf.

scanf ("%d", &variable_name) is for integers, while the way do it, you use the wrong format ("%s") + pass the variable value (... variable_name) instead the variable adrress (&variable_name note the &)

CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
2

While you have found that your incorrect use of format specifiers and the need for a pointer to the variable in scanf was your primary problem, there is another aspect to using scanf properly not yet addressed.

You cannot simply write scanf ("%d", &intr_class); to take user input without validating the return to insure there was a successful conversion to integer that took place. You must do that before you can have any confidence that intr_class holds a valid value at all. (you will further want to insure that the value is within an acceptable range if you have any such requirement, before you attempt to use intr_class)

That goes for all user input regardless what function you use to take it, but it especially applies to scanf. What if in response to your prompt the user enters something wrong?, e.g.

Enter the number of instruction classes: rabbits

What then? Unless you check the return, you will never know intr_class is unset and if uninitialized, any attempt to access the value is Undefined Behavior.

Further, scanf stops reading when it encounters an error leaving any characters not read still in stdin. So what happens when you try and read something else? Let's think about an example:

Enter the frequency of the machine (MHz): 800

Does this entry succeed or fail? (answer: FAIL) Why? stdin still contains "rabbits\n800\n" (the '\n' is added every time the user presses Enter, and the only reason you don't fail every input is the '%d' format specifier consumes leading whitespace), but "rabbits" ain't whitespace...)

So you must be doubly careful using scanf for user input to account for not only whether the conversion succeeds or fails, but also for any characters left in the input buffer (either the '\n' if your next type of input does not consume leading whitespace, or any characters left in the input buffer after a failed conversion).

One way you manage this is to manually remove any characters that remain in stdin after each call to scanf. (note: should should check that the return is not EOF as well). After you call to scanf and check that the return is not EOF, you can simply call a simple function that reads and discards all remaining characters, like:

/* function to empty remaining characters in stdin before next input
 * to help avoid the pitfalls associated with using scanf for user input.
 */
void emptystdin()
{
    int c = getchar();
    for (; c != '\n' && c != EOF; c = getchar()) {}
}

Avoiding the use of global variables and the very good suggestion to use a struct to hold all parameters should also be addressed. If you define a struct (call it struct parameters_t), create and declare an instance of it in main(), (say one called parameters), then all you need to do is pass the address of parameters to your params() function and fill the values in params. You simply change the function declaration for to include a pointer to the struct type, void params (stuct parameters_t *p) and then prompt and fill p->intr_class, etc. in the params function.

To make things simple (and avoid having to type struct parameters_t for the type all the time, you can simply create a typedef (an alias) defining parameters_t as an alias for struct parameters_t to cut down on typing.

You should also change the return type from void to something that can meaningfully indicate success/failure of your input in params

Putting those pieces together in a short example, you could do something similar to the following:

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

/* declare a struct to associate all the values */
typedef struct {
    int intr_class,
        freq,
        class1,
        cpi_1,
        class2,
        cpi_2,
        class3,
        cpi_3;
} parameters_t;

/* function to empty remaining characters in stdin before next input
 * to help avoid the pitfalls associated with using scanf for user input.
 */
void emptystdin()
{
    int c = getchar();
    for (; c != '\n' && c != EOF; c = getchar()) {}
}

/* params takes pointer to (address of) a struct parameters_t and
 * prompts for and fills each value. a tmp struct is used to avoid
 * changing any values in 'p' in case of a partial fill. returns
 * address of p on success, NULL otherwise indicating error.
 */
parameters_t *params (parameters_t *p){

    parameters_t tmp = { .intr_class = 0 };
    printf ("Enter the number of instruction classes: ");
    if (scanf ("%d", &tmp.intr_class) != 1)
        return NULL;

    printf ("Enter the frequency of the machine (MHz): ");
    if (scanf ("%d", &tmp.freq) != 1)
        return NULL;

    printf ("Enter CPI of class 1: ");
    if (scanf ("%d", &tmp.cpi_1) != 1)
        return NULL;

    printf ("Enter instruction count of class 1 (millions): ");
    if (scanf ("%d", &tmp.class1) != 1)
        return NULL;

    printf ("Enter CPI of class 2: ");
    if (scanf ("%d", &tmp.cpi_2) != 1)
        return NULL;

    printf ("Enter instruction count of class 2 (millions): ");
    if (scanf ("%d", &tmp.class2) != 1)
         return NULL;

    printf ("Enter CPI of class 3: ");
    if (scanf ("%d", &tmp.cpi_3) != 1)
        return NULL;

    printf ("Enter instruction count of class 3 (millions): ");
    if (scanf ("%d", &tmp.class3) != 1)
        return NULL;

    *p = tmp;   /* assign temp values to struct p */

    return p;
}

/* simple function to print values stored in p */
void prnparams (parameters_t *p)
{
    if (!p || p->intr_class == 0) {
        fprintf (stderr, "error: parameters empty or NULL\n");
        return;
    }
    printf ("parameters:\n"
            "  instruction classes: %d\n"
            "  frequency (MHz)    : %d\n"
            "  CPI of class 1     : %d\n"
            "  class 1 inst count : %d\n"
            "  CPI of class 2     : %d\n"
            "  class 2 inst count : %d\n"
            "  CPI of class 3     : %d\n"
            "  class 3 inst count : %d\n",
            p->intr_class, p->freq, p->cpi_1, p->class1,
            p->cpi_2, p->class2, p->cpi_3, p->class3);
}

int main (void) {

    char menuchoice;
    /* declare a struct & iniailize all values zero */
    parameters_t parameters = { .intr_class = 0 };  

    for (;;) {  /* loop until user quits */

        /* print out menu list */
        printf ("\nMenu of Options:\n"
                "______________\n"
                "  a) Enter Parameters\n"
                "  b) Calculate average CPI of a sequence of instructions\n"
                "  c) Calculate total execution time of a sequence of "
                      "instructions\n"
                "  d) Calculate MIPS of a sequence of instructions\n"
                "  p) Print stored values\n"
                "  e) Quit\n\n"
                "Enter selection: ");

        if (scanf ("%c", &menuchoice) == EOF) { /* check user cancels input */
            putchar ('\n');                     /* tidy up before exit */
            break;
        }
        if (menuchoice != '\n') /* make sure user didn't just hit [Enter] */
            emptystdin();       /* remove all chars from stdin */

        switch(menuchoice){
            case 'a':
                if (!params(&parameters))
                    fprintf (stderr, "error: params() failed.\n");
                emptystdin();  /* critical here or menuchoice would be '\n' */
                break;
            case 'b':
                //avgCPI();
                break;
            case 'c':
                //calcExTime();
                break;
            case 'd':
                //calcMIPS();
                break;
            case 'p':
                prnparams (&parameters);
                break;
            case 'e':
                exit(0);
            default:
                fprintf (stderr, "error: invalid menuchoice.\n");
                break;
        }
    }

    return 0;
}

Example Use/Output

$ ./bin/scanfparams

Menu of Options:
______________
  a) Enter Parameters
  b) Calculate average CPI of a sequence of instructions
  c) Calculate total execution time of a sequence of instructions
  d) Calculate MIPS of a sequence of instructions
  p) Print stored values
  e) Quit

Enter selection: a
Enter the number of instruction classes: 10
Enter the frequency of the machine (MHz): 20
Enter CPI of class 1: 30
Enter instruction count of class 1 (millions): 40
Enter CPI of class 2: 50
Enter instruction count of class 2 (millions): 60
Enter CPI of class 3: 70
Enter instruction count of class 3 (millions): 80

Menu of Options:
______________
  a) Enter Parameters
  b) Calculate average CPI of a sequence of instructions
  c) Calculate total execution time of a sequence of instructions
  d) Calculate MIPS of a sequence of instructions
  p) Print stored values
  e) Quit

Enter selection: p
parameters:
  instruction classes: 10
  frequency (MHz)    : 20
  CPI of class 1     : 30
  class 1 inst count : 40
  CPI of class 2     : 50
  class 2 inst count : 60
  CPI of class 3     : 70
  class 3 inst count : 80

Menu of Options:
______________
  a) Enter Parameters
  b) Calculate average CPI of a sequence of instructions
  c) Calculate total execution time of a sequence of instructions
  d) Calculate MIPS of a sequence of instructions
  p) Print stored values
  e) Quit

Enter selection: e

Look tings over. There is a lot to digest when you are just learning. So drop a comment if you have any questions and I'm happy to help further.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
1

https://www.tutorialspoint.com/c_standard_library/c_function_scanf.htm

That should help you a lot to understand exactly how scanf works. As the other two people said you should be using %d instead of %s and you need an ampersand before your variable. If you have learned pointers you'll know why you need the ampersand, if not don't worry about it for now, you'll learn it later.

That link I gave you also gives you the % command for other data types. Most data types have a different letter, for example %s is for strings (char arrays) while you'll use %lf for doubles.

Hope this helps!

Matthew Kerian
  • 812
  • 5
  • 18
0

Use scanf("%d",&VariableName) for integers and scanf(" %s",&arrCHar) for character arrays (strings).

Also, you have to state the function prototype before calling a function.

ndmeiri
  • 4,979
  • 12
  • 37
  • 45