Question

I have different structures and I want to write a general framework to get a sorted list of elements from say a hashtable or list of these structures in C. For example, if I have a structure definition as

struct XYZ{ 
 int a;
 char b[20];
 long time;
}

Suppose I have a list of elements of this structure type. I need to have a functionality, by which I can sort this list by any field of the structure and the field would be as given by user. This kind of use case has to be available for other such structures also and hence this sorting framework should be generic. I was thinking of storing different metadata related to this structure in form of a different auto generated structure using some script. This metadata would store information related to each element in the given structure. This information would include type of the structure(viz integer, string or date: types distinct while performing sorting operations) and a way to access this element in the structure. Now my question is what should ideally be the way to access these elements. One straight forward way is to generate some getter functions for each of these and set function pointers of the getter functions in the autogenerated metadata structures. But having a getter function to access each member element would mean a function call for each element access. Would this deter performance of the application ? Can there be any other way to do this? Any other framework ideas to implement the required use case also welcome.

Was it helpful?

Solution

As the comments suggested, the most straightforward approach would be to use qsort and a callback for each member of the struct.
You could use the offsetof macro to create a more generic, single callback.

The dangers of a single callback is that you have to change the function whenever the struct changes. It would also require you to create a global variable, which isn't ideal either. Of course, if you use separate callbacks, you have to create a new function, too. However, you don't have a global, and forgetting to create a new function is something that is far easier to debug and fix.

But just as an example, here's a possible approach for a generic qsort callback:

#include <stddef.h>

static size_t member_offset;

int struct_cmp(const void *a, const void *b)
{
    switch(member_offset)
    {
         case 0://or case offsetof(struct XYZ, a):
             return ((struct XYZ *)a)->a - ((struct XYZ *) b)->a;
         case offsetof(struct XYZ, b):
             return strcmp(
                 ((struct XYZ *)a)->b,
                 ((struct XYZ *)b)->b
             );
         case offsetof(struct XYZ, c):
             return ((struct XYZ *)a)->c - ((struct XYZ *) b)->c;
         default:
             fprintf(stderr, "Invalid offset value %d\n", member_offset);
             exit( EXIT_FAILURE );//possible bug
    }
}

int main ( void )
{
    struct XYZ foo[10];//create array of structs
    size_t offsets[3] = { 0, offsetof(struct XYZ, b), offsetof(struct XYZ, c)};
    for (int i=0;i<3;++i)
    {
        member_offset = offsets[i];//omit this, and you're in trouble!
        qsort(
            foo,
            sizeof foo,
            sizeof *foo,
            struct_cmp
        );
    }
    return 0;
}

As you can already see, if the struct has, say 10 member fields, the callback is going to look like an unholy mess. Owing to using a global variable, it's also bound to be more vulnerable. Not initializing the global variable, or changing the state of the global variable while another thread is sorting...
Honestly: just add a function for each member, but perhaps create a single function where you keep the qsort calls:

void sort_structs(struct XYZ *structs[], size_t count, size_t member_offset)
{//Pass POINTER to array
    switch (member_offset)
    {
        case 0://same cases as above:
            qsort(
                *structs,
                count,
                sizeof *structs[0],//or **structs
                a_sort_callback
            );
            break;
        case offsetof( struct XYZ, b):
            qsort(
                *structs,
                count,
                sizeof *structs[0],
                b_sort_callback
            );
            break;
        case offsetof( struct XYZ, c):
            qsort(
                *structs,
                count,
                sizeof *structs[0],
                c_sort_callback
            );
            break;
        default:
            fprintf(
                stderr,
                "Either %d is invalid offset, or you haven't implemented a callback"
                member_offset
            );
            exit( EXIT_FAILURE );
    }
}

OTHER TIPS

As Joachim Pileborg pointed out, there is a function called qsort() in the C Standard lib. You can use it for exactly your purpose. But then you'd have to know what type of array you have and use an appropriate sort function for them.

If, for any reasons, you have distinct struct types you want to be sorted (that would work if you do it via pointers), each of them should provide a key function for extracting the value you want to be sorted for.

If you do so, you effectively build an OOP framework in C.

But the better way is to have a uniform array and provide just a sort function for each type you want to sort.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top