Python variables are accessed using a handle to the variable allowing Python to manage variables by knowing their size and the data type and whether the variable is in scope or not or in use or not for garbage collection. In C source code a variable represents an actual memory location.
An array variable in Python is a handle to an array which Python stores in memory along with information about the array such as its size (number of elements), the type of the data stored in the array, etc. In C an array variable is basically a constant pointer to some memory location that contains the array elements. However there is no management data stored along with the array. The only thing in the array's memory location is the data for the array elements. The information about the array's size, data type, etc. is lost after compiling the C source code and is not available at run time.
In Python you can cut and splice and copy array and array pieces because the information about the array is available at run time. In C it is not so simple because the information about the array is not available after the source code is compiled.
The problem faced by the designer of a generalized qsort() function is that it must have an interface that allows the function to be used with a wide variety of arrays. And since the information about the array is not available at run time with C, the programmer must ask for the minimum information needed to make the qsort() function work for a wide variety of arrays.
What the programmer needs to know is the following basic information: (1) where does the array start, (2) what is the size of each element of the array, (3) how many elements are in the array, and (4) what is the comparison function to be used to determine the collating sequence to determine the order of two elements of the array.
The array is sorted in place. What that means is that you pass to the qsort() function the array and when qsort() returns the elements of the array have been sorted. This sorting is done by selecting two elements, comparing them with the comparison function provided, and then if needed swapping the two array elements. Remember that in C an array is basically a constant pointer to an area of memory. So the qsort() function is provided that address, where the array starts, and the caller expects that the array starting at that memory location is sorted when qsort() returns.
Since the comparison function is provided by the user of qsort(), the person writing the comparison function knows what the array elements looks like. The programmer uses the pointers provided by the qsort() function when it calls the comparison function to access the two array elements and decide the order of those two elements returning an indication as to which is lower in the collating sequence.
However the swapping of array elements requires the use of a temporary data area which runs back into the problem of the qsort() function when written did not know the size of the array elements. So the swap is usually done a byte at a time.
The end result of all of the various constraints due to the memory model of C is that a qsort() function will use recursion by specifying array index or array offset from the beginning of the array. So the qsort() function performs the sort on a subsection of the array by specifying the beginning and ending indices of the subsection. What is changing during the recursive function calls is just the index values.