Search code examples
cundefinedinfinite-loopundefined-behaviorinfinite

Problems regarding an multiple choice question program


I have created a program to generate the result of a multiple choice exam. The program was supposed to show the total number of mistakes, blank answers and the number of the question which were answered incorrectly. For the following input: 6

1..223 (Here . means blank answer)

123124

The output was supposed to be:

Your result:

Mistakes: 3

Blanks: 2

Your mistakes are following:

4 5 6

Your blanks are following:

2 3

But the code shows undefined behavior. It seems to go through infinite loop. Expecting solution to my problem shortly. Thanks in advance.

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



typedef struct node
{
    char data;
    struct node* next;
}node;

void printNode(node* head)
{
    node* local = head;
    int i = 0;
    if(local -> data == 0)
    {
        printf("0");
        return;
    }

    while(local != NULL)
    {
        if(i == 3)
        {
            i = 0;
            printf("\n");
        }


        printf("%d\t", local -> data);
        local = local -> next;
        ++i;

    }
}

void freeNode(node** head)
{
    node* temp = (*head);

    while((*head) != NULL)
    {
        (*head) = (*head) -> next;
        free(temp);
        temp = (*head);
    }
}
int main()
{
    int n, i, flagB, flagM, blnk, mstk;
    blnk = mstk = flagB = flagM = 0;

    printf("Enter the number of questions: ");
    scanf("%d", &n);

    char ques[n], ans[n];

    if(n == 0)
        return 0;

    node* headM = (node*)malloc(sizeof(node));

    node* nodeM;

    node* headB = (node*)malloc(sizeof(node));

    node* nodeB;

    printf("Enter your given answers: ");
    fflush(stdin);
    for(i = 0; i < n; ++i)
    {
        scanf("%c", &ques[i]);
    }

    fflush(stdin);
    ques[n] = '\0';

    printf("Enter the solution: ");

    for(i = 0; i < n; ++i)
    {
        scanf("%c", &ans[i]);
    }

    ans[n] = '\0';

    for(i = 0; i < n; ++i)
    {
        if(ques[i] == '.')
        {
            ++blnk;
            if(flagB == 0)
            {
                headB -> data = i + 1;
                headB -> next = NULL;
                nodeB = headB;
                continue;
            }

            nodeB -> next = (node*)malloc(sizeof(node));
            nodeB = nodeB -> next;
            nodeB -> data = i + 1;
            nodeB-> next = NULL;
            flagB = 1;
        }
        else if(ques[i] != ans[i])
        {
            ++mstk;

            if(flagM == 0)
            {
                headM -> data = i + 1;
                headM -> next = NULL;
                nodeM = headM;

                continue;
            }

            nodeM -> next = (node*)malloc(sizeof(node));
            nodeM = nodeM -> next;
            nodeM -> data = i;
            nodeM-> next = NULL;
            flagM = 1;

        }

    }



    printf("Your result:\n\tMistakes: %d\n\tBlanks: %d\n", mstk, blnk);

    printf("Your mistakes are follwing:\n");
    printNode(headM);

    printf("\nYour blanks are follwing:\n");
    printNode(headB);

    freeNode(&headM);
    freeNode(&headM);

    return 0;
}

Solution

  • Here are some additional thoughts. What makes your code very convoluted and hard to debug and keep the logic straight is you are mixing your linked-list Add function within the logic of your blanks and mistakes and using special conditions to handle adding the first node and subsequent nodes. This make things difficult to test and debug. If you need to add nodes to a linked-list, then write an add() function that you can thoroughly test and debug before putting it to use in your code.

    Your VLAs ques and ans are too short to hold a string of n characters, at minimum they must be n + 1 characters long to provide storage for the nul-termining character that marks the end of the string. Ideally, you will make them at least 2-character longer to also hold the '\n' which will allow you to take input with fgets() rather than looping scanf() a character at a time -- which is just nuts.

    You do not need to pass the address of the pointer to freeNode() simply pass a pointer. Sure freeNode() will receive a copy of the pointer -- but it will contain the original address -- and since you don't have to make any changes to that pointer available back to the caller, there is no need to pass the address of the pointer (there won't be any list left to worry about when you are done...)

    So putting those pieces together, adding an add() function to add to your linked lists (See Linus on Understanding Pointers for why a pointer-to-pointer is used to iterate to the end), and adding a simple empty_stdin() function to remove the '\n' left in stdin from reading n with scanf() before making calls to fgets() later for ques and ans, you could do:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /* simple function to empty stdin to end-of-line */
    void empty_stdin (void)
    {
        int c = getchar();
        
        while (c != '\n' && c != EOF)
            c = getchar();
    }
    
    typedef struct node
    {
        int data;
        struct node *next;
    } node;
    
    node *add(node **head, int v)
    {
        node **ppn = head,                      /* pointer to pointer to head */
              *pn = *head,                      /* pointer to head */
              *newn = malloc (sizeof *newn);    /* allocate new node */
     
        if (!newn) {                            /* validate allocation */
            perror ("malloc-node");
            return NULL;
        }
        newn->data = v;                         /* initialize members values */
        newn->next = NULL;
     
        while (pn) {                            /* iterate to end of list */
            ppn = &pn->next;
            pn = pn->next;
        }
     
        return *ppn = newn;                     /* add & return new node */
    }
    
    void printNode (node *head)
    {
        for (; head; head = head->next)
            printf (" %d", head->data);
        putchar ('\n');
    }
    
    void freeNode(node *head)
    {
        while (head != NULL)
        {
            node *victim = head;
            head = head->next;
            free(victim);
        }
    }
    
    int main()
    {
        int n, i, blnk, mstk;
        blnk = mstk = 0;
        node *headM = NULL;         /* declare pointers and initialize NULL */
        node *headB = NULL;
    
        printf ("Enter the number of questions: ");
        /* you must VALIDATE every user-input */
        if (scanf ("%d", &n) != 1) {
            fputs ("error: invalid integer input.\n", stderr);
            return 1;
        }
        empty_stdin();              /* remove '\n' (and any other chars from user) */
                                    /* before calling fgets() below */
        
        if (n == 0)                 /* check 0 BEFORE VLA declaration */
            return 0;
        
        char ques[2*n], ans[2*n];   /* declare question/answer VLAs, don't skimp */
        
        printf("Enter your given answers: ");
    
        if (!fgets(ques, sizeof ques, stdin))   /* read ques from stdin */
            return 1;
    
        ques[strcspn(ques, "\r\n")] = 0;        /* trim '\n' from end of ques */
        
        printf("Enter the solution: ");
    
        if (!fgets(ans, sizeof ans, stdin))     /* read ans from stdin */
            return 1;
    
        ans[strcspn(ans, "\r\n")] = 0;          /* ditto for ans */
        
        for(i = 0; i < n; ++i)                  /* loop n times */
        {
            if(ques[i] == '.')                  /* if blank */
            {
                add (&headB, i + 1);            /* add to list headB */
                ++blnk;                         /* increment counter */
            }
            else if(ques[i] != ans[i])          /* if mistake */
            {
                add (&headM, i + 1);            /* add to list headM */
                ++mstk;                         /* increment counter */
            }
        }
    
        printf ("Your result:\n\tMistakes: %d\n\tBlanks: %d\n"
                "Your mistakes are following:\n", mstk, blnk);
        printNode(headM);
    
        printf("\nYour blanks are following:\n");
        printNode(headB);
    
        freeNode(headM);    /* no need to pass the address of the pointer to free */
        freeNode(headB);    /* there won't be a list left when freeNode is done */
    
        return 0;
    }
    

    There is a lot there, so go through it slowly.

    Example Use/Output

    $ ./bin/llquestions
    Enter the number of questions: 6
    Enter your given answers: 1..223
    Enter the solution: 123124
    Your result:
            Mistakes: 2
            Blanks: 2
    Your mistakes are following:
     4 6
    
    Your blanks are following:
     2 3
    

    (note: in 1..223 and 123124, 5 is not a mistake, the 2 is in the correct position at the end)

    Look things over and let me know if you have further questions.