0

I'm attempting to create a program which employs the use of multiple threads to find how many diagonal sums of the grid created from an input text file match a given number passes to the program.

I compile proj4.c and main.c like this in order to access the main function in main.c using the provided Makefile in a unix environment:

compile:
    gcc -Wall -pedantic-errors proj4.c -g -c -pthread
    gcc -Wall -pedantic-errors main.c -g -c
    gcc -Wall -pedantic-errors main.o proj4.o -g -o proj4.out -pthread

run:
    ./proj4.out in1.txt out1.txt 10 1

clean:
    rm *.out rm *.o

I keep getting a segmentation error on line 18 of the following code:

proj4.c:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "proj4.h"

// Function to read the grid from the input file
void initializeGrid(grid *g, char *fileName) {
    FILE *file = fopen(fileName, "r");
    if (!file) {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }

    fscanf(file, "%u", &g->n);

    g->p = (unsigned char **)malloc(g->n * sizeof(unsigned char *));
    for (unsigned int i = 0; i < g->n; i++) {
        // vvv (line 18) vvv
        g->p[i] = (unsigned char *)malloc(g->n * sizeof(unsigned char));
        for (unsigned int j = 0; j < g->n; j++) {
            fscanf(file, " %c", &g->p[i][j]);
        }
    }
    fclose(file);
}

// Define a struct to pass data to the worker function
typedef struct {
    grid *input;
    unsigned long s;
    grid *output;
    int thread_id;
    int total_threads;
} worker_args;

// Worker thread function
void *worker(void *args) {
    worker_args *w_args = (worker_args *)args;
    grid *input = w_args->input;
    unsigned long s = w_args->s;
    grid *output = w_args->output;
    int thread_id = w_args->thread_id;
    int total_threads = w_args->total_threads;

    unsigned int n = input->n;

    // Calculate the range of elements each thread should work on
    unsigned int work_size = (n * (n - 1)) / (2 * total_threads);
    unsigned int start = thread_id * work_size;
    unsigned int end = (thread_id + 1) * work_size;

    if (thread_id == total_threads - 1) {
        end = n * (n - 1) / 2;
    }

    for (unsigned int idx = start; idx < end; idx++) {
        int row, col;

        // Find row value without using sqrt
        row = n - 2;
        while (idx <= (row * (row + 1)) / 2) {
            row--;
        }

        col = idx - (row * (row + 1)) / 2;

        output->p[row][col] = (input->p[row][col] + input->p[col][row + 1]) % s;
    }

    return NULL;
}

// Function to find the diagonal sums
void diagonalSums(grid *input, unsigned long s, grid *output, int t) {
    pthread_t *threads = (pthread_t *)malloc(t * sizeof(pthread_t));
    worker_args *thread_args = (worker_args *)malloc(t * sizeof(worker_args));

    for (int i = 0; i < t; i++) {
        thread_args[i].input = input;
        thread_args[i].s = s;
        thread_args[i].output = output;
        thread_args[i].thread_id = i;
        thread_args[i].total_threads = t;

        if (pthread_create(&threads[i], NULL, worker, (void *)&thread_args[i]) != 0) {
            perror("Error creating thread");
            exit(EXIT_FAILURE);
        }
    }

    for (int i = 0; i < t; i++) {
        if (pthread_join(threads[i], NULL) != 0) {
            perror("Error joining thread");
            exit(EXIT_FAILURE);
        }
    }

    free(threads);
    free(thread_args);
}

// Function to write the grid to the output file
void writeGrid(grid *g, char *fileName) {
    FILE *file = fopen(fileName, "w");
    if (!file) {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }

    for (unsigned int i = 0; i < g->n; i++) {
        for (unsigned int j = 0; j < g->n; j++) {
            fprintf(file, "%c ", g->p[i][j]);
        }
        fprintf(file, "\n");
    }

    fclose(file);
}

// Function to free the grid
void freeGrid(grid *g) {
    for (unsigned int i = 0; i < g->n; i++) {
        free(g->p[i]);
    }
    free(g->p);
}

main.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/time.h>
#include "proj4.h"

/*
 * Do not modify anything in this file. 
 */

/*
 * Helper function for main. 
 * Check for some errors and print error messages 
 * for command line arguments passed to main.
 * If an error is found, the program terminates.
 */
void errorCheck(int argc, char **argv) {
    bool errorFound = false;
    if (argc != 5) {
        printf("Usage error: ./proj4.out inputFile outputFile s t\n");
       errorFound = true;
    }
    else if (access(argv[1], F_OK) != 0) {
        printf("Error accessing %s in the present working directory\n", argv[1]);
        errorFound = true;
    } else {
        int t = atoi(argv[4]);
        if (t < 1 || 3 < t) {
            printf("Error: t must be between 1 and 3 (inclusive)\n");
            errorFound = true;
        }
    }
    if (errorFound)
        exit(0);
}

/*
 * This program should be compiled to ./proj4.out using the provided
 * Makefile, and it will process five command line arguments.
 *   ./proj.out input.txt output.txt s t
 *      input.txt is the name of a file in the present working directory that 
 *        contains a n-by-n grid of digits (1 through 9),
 *        where n >= 1.
 *      output.txt is the name of a file in the present working directory to save
 *        the output of all of the diagonal sums. If the file does not exist,
 *        then this file will be created in the present working directory.
 *      s is the sum for the diagonal sums.
 *      t is the number of threads (1 <= t <= 3) to use
 *        to compute the diagonal sums. If t is 1, then only the 
 *        main thread will be used. If 2 <= t <= 3, then the main
 *        thread and (t - 1) POSIX thread(s) will be used to compute
 *        the diagonal sums.
 * This program will only time the call to diagonalSums.
 *
 */
int main(int argc, char **argv) {
    errorCheck(argc, argv);
    char *inputFile = argv[1];
    char *outputFile = argv[2];
    unsigned long sum = (unsigned long)atol(argv[3]);
    int t = atoi(argv[4]);
    grid g, diagonalSumsOutput;
    initializeGrid(&g, inputFile);

    printf("Computing the diagonal sums equal to %ld in a %d-by-%d grid using %d thread(s).\n",
           sum, g.n, g.n, t);
    struct timeval start, end;    // start and end time
    unsigned long e_usec; // elapsed microseconds
    gettimeofday(&start, 0); // mark the start time
    diagonalSums(&g, sum, &diagonalSumsOutput, t);
    gettimeofday(&end, 0);        // mark the end time
    e_usec = ((end.tv_sec * 1000000) + end.tv_usec) -
             ((start.tv_sec * 1000000) + start.tv_usec);
    printf("Elapsed time for computing the diagonal sums using %d thread(s): %lf seconds.\n",
           t, e_usec / 1000000.0);

    printf("Writing the diagonal sums equal to %ld to the file %s.\n",
           sum, outputFile);
    writeGrid(&diagonalSumsOutput, outputFile);
    freeGrid(&g);
    freeGrid(&diagonalSumsOutput);
    printf("Program is complete. Goodbye!\n");
    return 0;
}

proj4.h:

#ifndef PROJ4_H
#define PROJ4_H

/*
 * The struct grid_t contains a pointer p to a 2D array of 
 * unsigned chars with n rows and n columns stored on
 * the heap of this process. Once this is initialized properly,
 * p[i][j] should be a valid unsigned char for all i = 0..(n-1)
 * and j = 0..(n-1).
 * Do not alter this typedef or struct in any way.
 */
typedef struct grid_t {
    unsigned int n;
    unsigned char **p;
} grid;

/*
 * Initialize g based on fileName, where fileName
 * is a name of file in the present working directory
 * that contains a valid n-by-n grid of digits, where each
 * digit in the grid is between 1 and 9 (inclusive).
 * Do not alter this function prototype in any way.
 */
void initializeGrid(grid *g, char *fileName);

/*
 * This function will compute all diagonal sums in input that equal s using
 * t threads, where 1 <= t <= 3, and store all of the resulting
 * diagonal sums in output. Each thread should do
 * roughly (doesn't have to be exactly) (100 / t) percent of the 
 * computations involved in calculating the diagonal sums. 
 * This function should call (or call another one of your functions that calls)
 * pthread_create and pthread_join when 2 <= t <= 3 to create additional POSIX
 * thread(s) to compute all diagonal sums. 
 * Do not alter this function prototype in any way.
 */
void diagonalSums(grid *input, unsigned long s, grid *output, int t);

/*
 * Write the contents of g to fileName in the present
 * working directory. If fileName exists in the present working directory, 
 * then this function should overwrite the contents in fileName.
 * If fileName does not exist in the present working directory,
 * then this function should create a new file named fileName
 * and assign read and write permissions to the owner. 
 * Do not alter this function prototype in any way.
 */
void writeGrid(grid *g, char *fileName);

/*
 * Free up all dynamically allocated memory used by g.
 * This function should be called when the program is finished using g.
 * Do not alter this function prototype in any way.
 */
void freeGrid(grid *g);

/*
 * You may add any additional function prototypes and any additional
 * things you need to complete this project below this comment. 
 * Anything you add to this file should be commented. 
 */
#endif

I tried these commands:

./proj4.out in5.txt out5.txt 1222 3; diff out5.txt correctOut5.txt | wc -c;

I get this error:

Segmentation fault (core dumped) diff: out5.txt: No such file or directory

I expected roughly this as an output (Time usage will vary):

Computing the diagonal sums equal to 1222 in a 3567-by-3567 grid using 3 thread(s).
Elapsed time for computing the diagonal sums using 3 thread(s): 61.737591 seconds.
Writing the diagonal sums equal to 1222 to the file out5.txt.
Program is complete. Goodbye!

I need help understanding why the Segmentation fault is occurring and what I can do to fix it.

Edit: Since in5.txt is extremely large because it is part of a school project, I shall provide in1.txt which also results in the same error:

56535
82355
15459
69251
61338
chqrlie
  • 131,814
  • 10
  • 121
  • 189
Zodiac
  • 9
  • 2
  • 1
    And which line is line 18? Can you please [edit] your question to add a comment on that line? And if you used a debugger to catch the crash, also please include the values of all involved variables. – Some programmer dude Apr 22 '23 at 07:35
  • 1
    And please try to create a [mre] to show us instead. Scale down the program you have until the crash disappears, perhaps even start over with an empty `main` function and add ***small*** pieces of code that you build and test one by one, until the crash happens again. Then you can much more easily isolate the problem for your [mre]. For example if the crash happens in the `initializeGrid` function then we really don't need anything else of your code. But an example of the input file might be good to see. – Some programmer dude Apr 22 '23 at 07:37
  • I also recommend you add checks for what `fscanf` [*returns*](https://en.cppreference.com/w/c/io/fscanf#Return_value). And some kind of input validation to make sure that for example the size you read into `g->n` is valid and in a reasonable range. – Some programmer dude Apr 22 '23 at 07:39
  • 2
    On a different note, in C you [don't have to (and really shouldn't) cast the result of `malloc`](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc/605858). Or other functions returning `void *`. – Some programmer dude Apr 22 '23 at 07:42
  • @Someprogrammerdude What should I use instead of malloc to create a double pointer c array as seen in the implementation above? – Zodiac Apr 22 '23 at 07:48
  • 2
    @Zodiac - It is the *cast* that is wrong, not the use of malloc. In C (unlike in C++!) a `void*` return is convertible to another pointer type without a cast. (And in C++ you shoudln't use malloc anyway). – BoP Apr 22 '23 at 07:53
  • 1
    When the crash happens, what is the value of `g->n`? What is the value of `i`? Where is `g->p` pointing, it's not a `NULL` pointer? – Some programmer dude Apr 22 '23 at 08:08
  • When I created [my own](https://godbolt.org/z/T1q9cda7z) [mre] it at least builds cleanly (except an unrelated warning). And reading the code doesn't give anything. It should work, unless you have bad input for `g->n`, or the first `malloc` call fails (likely because of bad input) and returns a `NULL` pointer. So what is the file containing? What does it look like? And what does `fscanf(file, "%u", &g->n)` *return* (not the value of `g->n`, but the actual `fscanf` function return)? – Some programmer dude Apr 22 '23 at 08:15
  • What's does the backtrace say from your core file? You should run it through valgrind, too. – Allan Wind Apr 22 '23 at 08:18
  • Btw, prefer passing a variable rather than a type to `sizeof`. Less duplication. – Allan Wind Apr 22 '23 at 08:21
  • You are allocating a ton of memory. You should check that `malloc()` succeeded. – Allan Wind Apr 22 '23 at 08:34
  • It segfaults for me on `output->p[row][col] = (input->p[row][col] + input->p[col][row + 1]) % s;`, btw, which is not line 18 – Allan Wind Apr 22 '23 at 09:06
  • If the example file you show is correct, then the first line is `56535` which is read into `g->n`. That says you will read `56535 * 56535` characters, i.e. `3196206225` characters. That seems like a ***lot*** (it's around 3 ***giga*** bytes). And probably not correct. I'm guessing you should read `5 * 5` (i.e. `25`) characters. So either you have misunderstood something in the file format, or the file format isn't what is specified. – Some programmer dude Apr 22 '23 at 10:07

1 Answers1

1

The sample file you provide is invalid, it should contain the grid size as the first number, 5 to be consistent with the 5x5 matrix present in the file.

The function initializeGrid should test for fscanf() and malloc() failures and report them with concise error messages to avoid undefined behavior on invalid or missing input.

Here is a modified version:

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

// Function to read the grid from the input file
// note: argument fileName should be defined as const char *fileName
void initializeGrid(grid *g, char *fileName) {
    FILE *file = fopen(fileName, "r");
    if (!file) {
        fprintf(stderr, "Cannot open file %s: %s\n", fileName, strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (fscanf(file, "%u", &g->n) != 1) {
        fprintf(stderr, "Cannot read grid size\n");
        fclose(file);
        exit(EXIT_FAILURE);
    }

    if (g->n <= 0 || g->n > 10000) {
        fprintf(stderr, "Invalid grid size: %u\n", g->n);
        fclose(file);
        exit(EXIT_FAILURE);
    }

    g->p = malloc(sizeof(*g->p) * g->n);
    if (g->p == NULL) {
        fprintf(stderr, "Out of memory for grid array\n");
        fclose(file);
        exit(EXIT_FAILURE);
    }
    for (unsigned int i = 0; i < g->n; i++) {
        g->p[i] = malloc(sizeof(*g->p[i]) * g->n);
        if (g->p[i] == NULL) {
            fprintf(stderr, "Out of memory for grid row %u\n", i);
            fclose(file);
            exit(EXIT_FAILURE);
        }
        for (unsigned int j = 0; j < g->n; j++) {
            if (fscanf(file, " %c", &g->p[i][j]) != 1) {
                fprintf(stderr, "Missing data for grid[%u][%u]\n", i, j);
                fclose(file);
                exit(EXIT_FAILURE);
            }
        }
    }
    fclose(file);
}

Sample file:

5
56535
82355
15459
69251
61338
chqrlie
  • 131,814
  • 10
  • 121
  • 189