qsort with structs in C: solved exercise

qsort with structs in C: solved exercise

If you searched for a solved qsort with structs in C, here is how to pass a custom comparator to qsort to sort an array of structs by different fields. qsort from <stdlib.h> accepts void * pointers in the comparator, so an explicit cast to the concrete type is required.

The comparator signature must be int cmp(const void *a, const void *b) and return negative, zero, or positive according to the relative order of a and b.

Problem statement

Define a Student struct with fields name (32-character array), grade (double), and age (int). Create an array of 5 students and sort it three ways:

  1. By grade descending (highest grade first).
  2. By name ascending (alphabetical order).
  3. By age ascending and, in case of a tie, by grade descending.

C solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_N 32

typedef struct {
    char   name[MAX_N];
    double grade;
    int    age;
} Student;

/* ── Comparators ─────────────────────────────────────────────── */

/* By grade descending */
int cmp_grade_desc(const void *a, const void *b) {
    const Student *x = (const Student *)a;
    const Student *y = (const Student *)b;
    if (y->grade > x->grade) return  1;
    if (y->grade < x->grade) return -1;
    return 0;
}

/* By name ascending */
int cmp_name_asc(const void *a, const void *b) {
    const Student *x = (const Student *)a;
    const Student *y = (const Student *)b;
    return strncmp(x->name, y->name, MAX_N);
}

/* By age ascending; tie → grade descending */
int cmp_age_grade(const void *a, const void *b) {
    const Student *x = (const Student *)a;
    const Student *y = (const Student *)b;
    if (x->age != y->age) return x->age - y->age;
    if (y->grade > x->grade) return  1;
    if (y->grade < x->grade) return -1;
    return 0;
}

/* ── Utilities ──────────────────────────────────────────────── */

void print_students(const char *title, const Student *arr, int n) {
    printf("%s:\n", title);
    for (int i = 0; i < n; i++)
        printf("  %-14s grade=%.1f  age=%d\n",
               arr[i].name, arr[i].grade, arr[i].age);
}

int main(void) {
    Student students[] = {
        {"Laura",   8.5, 21},
        {"Pedro",   6.0, 22},
        {"Sofia",   9.2, 21},
        {"Carlos",  7.8, 20},
        {"Marta",   9.2, 22},
    };
    int n = sizeof(students) / sizeof(students[0]);

    qsort(students, n, sizeof(Student), cmp_grade_desc);
    print_students("By grade (desc)", students, n);

    qsort(students, n, sizeof(Student), cmp_name_asc);
    print_students("\nBy name (asc)", students, n);

    qsort(students, n, sizeof(Student), cmp_age_grade);
    print_students("\nBy age (asc) + grade (desc)", students, n);

    return 0;
}

Expected output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
By grade (desc):
  Sofia          grade=9.2  age=21
  Marta          grade=9.2  age=22
  Laura          grade=8.5  age=21
  Carlos         grade=7.8  age=20
  Pedro          grade=6.0  age=22

By name (asc):
  Carlos         grade=7.8  age=20
  Laura          grade=8.5  age=21
  Marta          grade=9.2  age=22
  Pedro          grade=6.0  age=22
  Sofia          grade=9.2  age=21

By age (asc) + grade (desc):
  Carlos         grade=7.8  age=20
  Sofia          grade=9.2  age=21
  Laura          grade=8.5  age=21
  Marta          grade=9.2  age=22
  Pedro          grade=6.0  age=22

Common mistakes

  • Comparing doubles with a->grade - b->grade and returning the result directly: the difference may be a tiny double that gets truncated to 0 on cast to int, producing incorrect ties. Use explicit comparisons (>, <).
  • Not using const Student * in the comparator: casting from const void * to a non-const pointer is valid but generates compiler warnings; using const is correct.
  • Forgetting the third argument to qsort (sizeof(Student)): passing sizeof(Student *) (pointer size) causes qsort to misinterpret the data.
  • Not using strncmp with the MAX_N limit to compare strings in a struct: strcmp may read past the field if it is not null-terminated.

Practical use

qsort with custom comparators is the standard C tool for sorting any array of records: in-memory database tables, query results, user rankings, sorting files by name/size/date, and any scenario where the sort criterion depends on multiple fields.

Guided practice and full book

If you want a complete path with progressive difficulty:

FAQ

Is qsort stable? Does it preserve the order of equal elements?

Stability is not guaranteed. Most platform implementations use introsort (quicksort + heapsort hybrid), which is not stable. If you need stability, add the original index to the comparator as the final tiebreaker.

What is the difference between qsort and bsearch?

qsort sorts an array in place; bsearch finds an element in an already-sorted array, with O(n log n) and O(log n) complexity respectively. They are used together: first qsort, then bsearch.

Can qsort be used with an array of pointers to structs?

Yes, and it is common when elements are large. The comparator receives const void *a which is really const Student **, so the cast is *(const Student **)a. Sorting pointers instead of full structs avoids copying data during sorting.