Search code examples
cstructmemcpybubble-sort

Numerically bubble sort a dynamically created struct array


I was trying to figure this out, but now I'm at my wits end.

This is the most relevant link I can find to my problem, but it doesn't help as the struct is a single field, and thus can use strcpy rather than memcpy (for structs with more than one field).

This is the final code changes, the only one I could get the cleanly compile.

#include <stdio.h>
#include <errno.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>

#define DIR_SIZE    400
#define FILE_SIZE   500

struct _data
{
    long int size;
    char file[FILE_SIZE + 1];
};

int fill_struct (struct _data **data, char *dir);
void sort_struct (struct _data **data, int count);

// Used for error reporting.
char *program_name = NULL;

void sort_struct (struct _data **data, int count)
{
// Declare variables.
    int i = {0};
    int j = {0};
    struct _data temp = {0};

// Bubble sort struct.
    for (i = 0; i < count; i++)
    {
        for (j = 0; j < count - i; j++)
        {
            if ((*data[j]).size > (*data[j + 1]).size)
            {
                memcpy(&temp, (&data)[j], sizeof(struct _data));
                memcpy(&data[j], &data[j + 1], sizeof(struct _data));
                memcpy(&data[j + 1], &temp, sizeof(struct _data));
            }
        }
    }
}

int main (int argc, char *argv[])
{
// Declare variables.
    int count = {0};
    struct _data *data = NULL;

// Get program name for error reporting.
    program_name = basename(argv[0]);

// Check for correct number of arguments.
    if(argc != 2)
    {
        fprintf(stderr, "usage: %s DIRECTORY\n", program_name);
        exit(EXIT_FAILURE);
    }

// Get all file info from directory.
    count = fill_struct(&data, argv[1]);

// Sort the struct based on size.
    sort_struct(&data, count);

// Free allocated memory.
    free(data);
// Exit gracefully.
    exit(EXIT_SUCCESS);
}

int fill_struct (struct _data **data, char *dir)
{
// Declare variables.
    int count = 0;
    DIR *dp = NULL;
    struct dirent *ep = NULL;
    struct _data *temp = NULL;
    struct stat file_info = {0};
    char file[FILE_SIZE + 1] = {0};

// Open directory for reading.
    if((dp = opendir(dir)) == NULL)
    {
        fprintf(stderr, "%s: fill_struct error: opendir failed (%s) (%s)\n", program_name, dir, strerror(errno));
        exit(EXIT_FAILURE);
    }

// Loop through all entries.
    while((ep = readdir(dp)) != NULL)
    {
// Skip everything but files.
        if(ep->d_type != DT_REG)
            continue;

// Increase count.
        count++;

// Build filename path.
        strcpy(file, dir);

// Add slash if needed.
        if(dir[strlen(dir) - 1] != '/')
            strcat(file, "/");

// Add filename.
        strcat(file, ep->d_name);

// Get file info.
        if(stat(file, &file_info) == -1)
        {
            fprintf(stderr, "%s: fill_struct error: stat failed (%s) (%s)\n", program_name, file, strerror(errno));
            exit(EXIT_FAILURE);
        }

// Allocate more memory for additional records.
        if((temp = realloc(*data, sizeof(struct _data) * count)) == NULL)
        {
            fprintf(stderr, "%s: fill_struct error: realloc failed\n", program_name);
            free(*data);
            exit(EXIT_FAILURE);
        }

// Store file data.
        strcpy(temp[count - 1].file, file);
        temp[count - 1].size = file_info.st_size;

// Change pointer on success.
        *data = temp;
    }

// Return total count of records.
    return(count);
}

Although it compiles cleanly, running it causes a Segmentation fault (core dumped)

Before, I was trying to use (*data)[x] instead of (&data)[x], but I could never get it to compile cleanly.


Solution

  • There are few problems:

    In the following code:

    if ((*data[j]).size > (*data[j + 1]).size)
    {
        memcpy(&temp, (&data)[j], sizeof(struct _data));
        memcpy(&data[j], &data[j + 1], sizeof(struct _data));
        memcpy(&data[j + 1], &temp, sizeof(struct _data));
    }
    

    data is pointer to pointer. data[j] is a pointer, so &data[j] is go back to pointer to pointer, not just a pointer as memcpy expect to get.

    when you have a pointer to pointer you should first turn it to a pointer by * prefix then use it as regular pointer. to avoid a dimention error use it inside a parentheses, like: (*data)

    It should be:

    if ((*data)[j].size > (*data)[j + 1].size)
    {
        memcpy(&temp, (*data)[j], sizeof(struct _data));
        memcpy((*data)[j], (*data)[j + 1], sizeof(struct _data));
        memcpy((*data)[j + 1], &temp, sizeof(struct _data));
    }
    

    Why in the first place you are using pointer to pointer?

    You don't have 2 dimentions array, and you are not changing the allocation. Therefore you shouldn't mess with pointer to pointer. so the function should be declare as:

    void sort_struct (struct _data *data, int count)
    

    To swap between to instance of a struct you can use regular assignment instead of memcpy, like:

    temp = data[j];
    data[j] = data[j+1];
    data[j+1]= temp;
    

    or with the pointer to pointer version if you insist...

    temp = (*data)[j];
    (*data)[j] = (*data)[j+1];
    (*data)[j+1]= temp;