Search code examples
cmatrixinputerror-handling

C: Problem with reading in a matrix via User Input


I am trying to write a simple C program, where the user can enter a 3 x 2 matrix. The input is expected to be row-wise in the form (the length of the input row is assumed to be fitting, i.e. 2)

3,4

Additionally, I want to limit the input numbers to be between 0 and 1000. I achieve this by first parsing the input via a function called readIntegerList in my function getMatrixInput and it works when used in isolation. However, when I use scanf (with an unrelated target), my number bound check suddenly triggers, without entering any input for the matrix.

Below you can see my code, where I isolated the problem:

#include <stdio.h>
#include <stdlib.h>
    
void printMatrix(int *matrix, int rows, int columns)
{
    for (int i = 0; i < rows; i = i + 1)
    {
        for (int j = 0; j < columns; j = j + 1)
            printf("%d ", matrix[i * columns + j]);
    
        printf("\n");
    }
    
    printf("\n");
}
    
void readIntegerList(char *list, int *array, int length)
{
    const int basis = 10;
    char ciphers[10] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    int number = 0;
    char character;
    int i = 0;
    int number_index = 0;
    
    while ((character = list[i]) != '\n')
    {
        if (character != ',')
        {
            // Here we use the elementary mathematical fact that numbers are usually represented in the decimal system.
            for (int j = 0; j < 10; j = j + 1)
                if (character == ciphers[j])
                    number = number * basis + j;
        } else
        if (character == ',')
        {
            array[number_index] = number;
            number_index = number_index + 1;
            number = 0;
        }
    
        i = i + 1;
    }
    
    array[number_index] = number;
}
    
void getMatrixInput(int *destinations, int *input_row, int rows, int columns)
{
    const int lower_bound = 0;
    const int upper_bound = 1000;
    int row = 0;
    
    while (row < rows)
    {
        printf("Enter the matrix row [row: %d]:\n", row);
        printf(" > ");
        int input_is_valid = 1;
        char input_string[100];
        fgets(input_string, 100, stdin);
    
        readIntegerList(input_string, input_row, columns);
        // Here we check if the input row is valid or not.
        int input_number;
    
        for (int i = 0; i < columns; i = i + 1)
        {
            input_number = input_row[i];
    
            if (input_number < lower_bound || input_number > upper_bound)
            {
                printf("Wrong input, the number %d is out of range (0 to %d)!\n", input_number, upper_bound);
                input_is_valid = 0;
            }
        }
    
        // Here we update the matrix, given that the input row was valid.
        if (input_is_valid == 1)
        {
            for (int column = 0; column < columns; column = column + 1)
                destinations[row * columns + column] = input_row[column];
    
            row = row + 1;
        }
    }
}
    
int getSomeNumber()
{
    int number = -1;
    const int lower_bound = 1;
    const int upper_bound = 9;
    int input_is_valid = 0;
    
    while (input_is_valid == 0)
    {
        printf("Enter some number:\n");
        printf(" > ");
        scanf("%d", &number);
    
        if (number < lower_bound || number > upper_bound)
            printf("Wrong input!\n");
        else
            input_is_valid = 1;
    }
    
    return number;
}
    
int main()
{
    int rows = 3;
    int columns = 2;
    int number = getSomeNumber();    // when I delete this line the program works
    int matrix_length = rows * columns;
    int *input_destinations = malloc(columns * sizeof(int));
    int *matrix = malloc(matrix_length * sizeof(int));
    getMatrixInput(matrix, input_destinations, rows, columns);
    printMatrix(matrix, 3, 2);
    free(matrix);
    return 0;
}

I suppose that some string is not terminated correctly via the scanf, but I can not find the error. Could you please help me?

Edit: I added screenshots to clarify my problem.

enter image description here

enter image description here


Solution

  • The problem is the classic bug from mixing scanf and fgets calls: scanf("%d", &number); attempts to read an integer in base 10: it skips any whitespace, reads an optinal '+' or '-' sign, then reads all digits, converting the value to an int and stops at the first character that is not a digit. Hence the trailing newline is left pending in stdin and the first fgets() in the while loop in getMatrixInput() reads an empty line, causing the matrix to be corrupted.

    You can fix this problem by reading the first line with fgets() and parsing the number with sscanf() or strtol(). You can use strtol() to parse the subsequent lines too instead of hand written code.

    Here is a modified version:

    #include <errno.h>
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
        
    void printMatrix(const int *matrix, int rows, int columns)
    {
        for (int i = 0; i < rows; i = i + 1) {
            for (int j = 0; j < columns; j = j + 1) {
                printf("%d ", matrix[i * columns + j]);
            }
            printf("\n");
        }
        printf("\n");
    }
        
    int readIntegerList(const char *str, int *array, int length)
    {
        const char *p = str;
        char *endp;
        long number;
        for (int i = 0; i < length; i++) {
            errno = 0;
            number = strtol(p, &endp, 10);  // accept decimal only
            if (p == endp || errno != 0 || number < INT_MIN || number > INT_MAX) {
                // return a short count on errors:
                // the caller will print an error message
                break;
            }
            array[i] = number;
            p = endp;
            while (isspace((unsigned char)*p))
                p++;
            if (*p == ',')
                p++;
        }
        return i;
    }
        
    void getMatrixInput(int *destinations, int rows, int columns)
    {
        const int lower_bound = 0;
        const int upper_bound = 1000;
        int row = 0;
        
        while (row < rows) {
            printf("Enter the matrix row [row: %d]:\n", row);
            printf(" > ");
            int input_is_valid = 1;
            char input_string[100];
            int input_row[columns];
            int columns_read;
    
            if (!fgets(input_string, 100, stdin)) {
                printf("missing input\n");
                exit(1);
            }
        
            columns_read = readIntegerList(input_string, input_row, columns);
            if (columns_read != columns) {
                printf("Invalid input: missing numbers\n");
                continue;
            }
    
            // Here we check if the input row is valid or not.
            for (int i = 0; i < columns; i++) {
                if (input_row[i] < lower_bound || input_row[i] > upper_bound) {
                    printf("Invalid input: the number %d is out of range (%d to %d)!\n",
                           input_row[i], lower_bound, upper_bound);
                    input_is_valid = 0;
                }
            }
        
            // Here we update the matrix, given that the input row was valid.
            if (input_is_valid == 1) {
                for (int i = 0; i < columns; i++)
                    destinations[row * columns + i] = input_row[i];
                row = row + 1;
            }
        }
    }
        
    int getSomeNumber(void)
    {
        char buf[80];
        int number;
        const int lower_bound = 1;
        const int upper_bound = 9;
        
        for (;;) {
            printf("Enter some number:\n");
            printf(" > ");
            if (!fgets(buf, sizeof buf, stdin)) {
                printf("missing input\n");
                exit(1);
            }
            if (sscanf(buf, "%d", &number) != 1) {
                printf("invalid input: %s\n", buf);
            } else
            if (number < lower_bound || number > upper_bound) {
                printf("invalid input: number must be between %d and %d!\n", 
                       lower_bound, upper_bound);
            } else {
                return number;
            }
        }
    }
        
    int main(void)
    {
        int rows = 3;
        int columns = 2;
        int number = getSomeNumber();    // when I delete this line the program works
        int matrix_length = rows * columns;
        int *matrix = calloc(matrix_length, sizeof(int));
        getMatrixInput(matrix, rows, columns);
        printMatrix(matrix, rows, columns);
        free(matrix);
        return 0;
    }