Вопрос

I am a beginner programmer in C who wants to get used to terminology and pointers. I have found the following working function prototype while searching for a way to sort the elements of a numerical array. The function was qsort and it utilized pointers. Now what I understood is that the word "const" ensures that the values a and b are unchanged but not the pointers. Correct me if I am wrong here. My questions are:

  1. Why do we use void * the function can we not use int * from the start?
  2. How does the construction *(int*)a in the return part work?
  3. Why does the qsort algorithm needs this many arguments?

    int compare (const void *a, const void *b)
    {
         return ( *(int*)a - *(int*)b ); 
    }
    

Many thanks for the answers. PS: That is a pretty complicated task for me.

Это было полезно?

Решение

  1. qsort was made this way so it could be used as a generic sorter. If it would use int from the start it could only be used to compare integers. This way you could also, for example, sort strings by passing strcmp as the compare function to qsort.
  2. *(int*)a casts a to a pointer-to-int and then dereferences it, so you get the integer stored at a. Note that this doesn't change a or the value that a points to.
  3. qsort requires 4 arguments: the array to sort, the number of elements in that array and the size of the elements and finally the compare function. It needs all this information, because again, it is made to be as generic as possible.

    It needs the number of elements because in C pointers don't carry information about the size of the buffer that follows them. And it needs to know the size of each element so it can pass the elements to the compare function correctly. For examle, to compare ints you would pass sizeof(int) as the size parameter. To compare strings you would use sizeof(char *).

ADDIT as suggested by H2CO3 the reason for using const void * is to indicate that the compare function may not change the value pointed to by a and b. This, of course, to ensure that sorting the array doesn't suddenly change the values in the array. And, as H2CO3 said, it would be cleaner to cast to (const int *) so that you cannot accidentally change the value after casting it:

return *(const int *)a - *(const int *)b;

You could also get rid of the cast with:

int compare(const void * a, const void * b){
    const int * ia = a;
    const int * ib = b;

    return *ia - *ib;
}

depending on your tastes regarding casts. (I prefer to avoid them)

Finally, to clarify the asterisks:

*(int *)a
^     ^
|     └ cast to integer pointer
└ dereference (integer) pointer

Другие советы

Now what I understood is that the word "const" ensures that the values a and b are unchanged but not the pointers

You understood wrong.

 const int *a;  

declare a as pointer to constant int type. This means that the word const ensures that you can't modify the value of the variable a points to by modifying *a.

Why do we use void * the function can we not use int * from the start?

void * is used to point any type of variable.

How does the construction *(int*)a in the return part work?

*(int *) is used to cast a as a pointer to int and then dereferencing it to get the value stored at location it points to.

The other answers are excellent. I just want to add that it's ofter easier to read if you are very clear in your callback function.

int compare (const void *a, const void *b)
{
     return ( *(int*)a - *(int*)b ); 
}

becomes

int compare (const void *a, const void *b)
{
    int ia = *(int *)a;
    int ib = *(int *)b;
    return ia - ib;
}

In this case it's not too important but as your compare funcion gets complex, you may want to get your variables to "your type" before doing the compare.

Since you asked in the comment below, here is a very step by step version:

int compare (const void *a, const void *b)
{
    int *pa = (int *)a;
    int *pb = (int *)b;
    int ia = *pa;
    int ib = *pb;
    return ia - ib;
}

qsort() function is an example of a generic algorithm that was implemented as a general-purpose routine. The idea is to make it useful for sorting arbitrary objects, not just int or float. Because of that (and because of the C language design), qsort() resorts to taking a comparison function as a parameter that accepts two generic (in C sense) pointers. It is up to that function (provided by qsort() user) to cast these pointers to correct type, perform correct comparison and return an indication of ordering.

Similarly, since qsort() doesn't know beforehand how large objects are, it takes the object size as a parameter. As far as qsort() is concerned, the objects are blobs of bytes of equal size contiguously arranged in memory.

Finally, since neither of the operations qsort() performs can cause an error, it doesn't return an error code. Actually there is a situation where qsort() might fail, which is illegal parameters passed to it, but in a tradition of many other standard C library routines, it does not guarantee any error checking on parameters promising undefined behavior in such a case.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top