Hans
Hans

Reputation: 49

qsort type casting pointers in c

This is a simple code to practice qsort function in C.

int compareF(const void *a, const void *b) {
    // return (*(int**)a)[0] - (*(int**)b)[0];  
    const int *pr1 = *(const int **)a;
    const int *pr2 = *(const int **)b;
    return pr1[0] - pr2[0];
}

int main() {
    int *array[] = {(int[]){2,2,3}, (int[]){1,2,1},(int[]){1,3,3},(int[]){0,2,3},(int[]){1,2,0}};
    qsort(array, 5, sizeof(int[3]), compareF);
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }
  return (0);
}

I don't grasp line 3 and 4 of why it is type-casted this way with pointers. This is my understanding. Firstly, it doesn't matter how we use syntax whether we set new variables to save parameters or not like those two return states are just same. Secondly, it needs to be type cased since parameters are void type so function does not know the type. First * is for dereferencing parameter a so that It has an access to the address a. And we use int** because..?

There's 2d integer array in main function. CompareF function only takes array[0] and array[1] as parameter each time in my understanding. So I don't get why it is typed cased with two **.

Same code with only one line of code modified

int array[3][2]= {{1,4},{3,6},{2,8}};

when array is defined this way, is type casting happened in same way?

const int *pr1 = *(const int[])a;
const int *pr2 = *(const int[])b;

or

const int *pr1 = (const int*)a;
const int *pr2 = (const int*)b;

First case doesn't work and second one does. But doesn't it need * to dereference? like

const int *pr1 = *(const int*)a;
const int *pr2 = *(const int*)b;

Upvotes: 0

Views: 320

Answers (2)

Vlad from Moscow
Vlad from Moscow

Reputation: 310990

For starters this call of qsort:

qsort(array, 5, sizeof(int[2]), compareF);

is incorrect. The third parameter is the size of elements of the array. As the type of elements of the array is int * then the third parameter must be sizeof( int * ) or that is the same sizeof( *array ):

qsort(array, 5, sizeof( *array ), compareF);

The function qsort passes pointers to elements of the array to the comparison function compareF as pointers of the type const void *.

As the elements if the array have the type int * then a pointer to an element of the array has the type int **.

So within the function compareF you need to cast the pointers of the type const void * to types const int **. Dereferencing such a pointer like:

const int *pr1 = *(const int **)a;

you will get an element of the original array.

As the elements of the array point to first elements of compound literals that have the type int[3] then for example the expression pr1[0] within the comparison function gives the first element of a compound literal, the expression pr1[1] gives the second element of the compound literal and so on.

In general using this return statement:

return pr1[0] - pr2[0];

is incorrect because the supplied expression can produce an overflow for the signed integer type int.

It is more interesting to sort the elements (pointers to first elements of compound literals) of the original array comparing all elements of compound literals.

Here is a demonstration program:

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

enum { M = 3 };

int compareF( const void *a, const void *b ) 
{
    const int *pr1 = *( const int ** )a;
    const int *pr2 = *( const int ** )b;
    
    size_t i = 0;
    while ( i != M && pr1[i] == pr2[i] ) ++i;
    
    return i == M ? 0 : ( pr2[i] < pr1[i] ) - ( pr1[i] < pr2[i] );
}

int main(void) 
{
    int * array[] = 
    {
        (int[M]){2,2,3}, 
        (int[M]){1,2,1},
        (int[M]){1,3,3},
        (int[M]){0,2,3},
        (int[M]){1,2,0}
    };
    
    const size_t N = sizeof( array ) / sizeof( *array );
    
    qsort( array, N, sizeof( *array ), compareF );
    
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < M; j++ )
        {
            printf( "%d ", array[i][j] );
        }
        
        putchar( '\n' );
    }
}

The program output is

0 2 3 
1 2 0 
1 2 1 
1 3 3 
2 2 3 

As you can see if first elements of two compound literals are equal each other then second elements are compared and so on.

If you have a two-dimensional array declared like

int array[3][2]= {{1,4},{3,6},{2,8}};

then it means that the element type is in turn array type int[2]. So the function call will look like

qsort(array, sizeof( array ) / sizeof( *array ), sizeof( int[2] ), compareF);

And pointer to an element of the array will have the type int ( * )[2].
So dereferencing the pointer within the comparison function you will get an array of the type int[2] (an element of the original array) that that used as an expression is converted implicitly to pointer to its first element.

Here is a demonstration program.

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

enum { M = 2 };

int compareF( const void *a, const void *b ) 
{
    const int *pr1 = *( const int ( * )[M] )a;
    const int *pr2 = *( const int ( * )[M] )b;
    
    size_t i = 0;
    while ( i != M && pr1[i] == pr2[i] ) ++i;
    
    return i == M ? 0 : ( pr2[i] < pr1[i] ) - ( pr1[i] < pr2[i] );
}

int main(void) 
{
    int array[][M] = 
    {
        { 1, 4 }, { 3, 6 }, { 2, 8 }
    };
    
    const size_t N = sizeof( array ) / sizeof( *array );
    
    qsort( array, N, sizeof( *array ), compareF );
    
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < M; j++ )
        {
            printf( "%d ", array[i][j] );
        }
        
        putchar( '\n' );
    }
}

The program output is:

1 4 
2 8 
3 6 

Pay attention to that the comparison function shall return 0 when two compared elements are equal each other. Relative to the function compareF it means that after the loop:

    size_t i = 0;
    while ( i != M && pr1[i] == pr2[i] ) ++i;

i will be equal to M because all elements of the compared one-dimensional arrays are equal each other.

Upvotes: 2

dbush
dbush

Reputation: 223972

The compare function passed to qsort is called anytime any two array elements are to be compared, and it is taking the address of each of those array elements.

You have an array of int * that is being sorted, so the address of each of those elements has type int **. That matches the type that the parameters of compareF are casted to. Dereferencing those pointers then gives us a value of type int * which is what is actually contained in the array.

Also, the third parameter to qsort is not correct. The array elements do not have type int[2] but instead have type int *. If an int is 4 bytes and a pointer is 8 bytes then they happen to be the same, but you can't depend on that.

Upvotes: 3

Related Questions