You assume right: arithmetics on void pointers is not defined by the C Standard.
The program you are looking at uses a common compiler extension supported by gcc, clang, tcc and many others, that implements arithmetics on void pointers as if they were byte pointers. With this extension, v + (left * size) behaves as
(void *)((char *)v + (left * size))
So the declaration void *vl = (char *)(v + (left * size)); is equivalent to:
void *vl = (char *)((void *)((char *)v + (left * size)));
Note that the whole expression is cast implicitly to (void *) in C.
This declaration can be simplified as:
void *vl = (char *)v + left * size;
This is probably what the programmer meant to write and their mistake went unreported because the compiler allows void * arithmetics with exactly the same effect.
Regarding your third question, the swap function exchanges the contents of the memory blocks pointed to by v1 and v2 using a local variable length array buffer of size bytes. qsort is usually called with a rather small element size, so this approach is OK, but calling qsort with an array of very long elements (more than a few megabytes) is allowed and could cause a stack overflow.
Here is a safer implementation:
void swap(void *v1, void *v2, size_t size) {
unsigned char *p1 = v1;
unsigned char *p2 = v2;
while (size >= 8) {
char buffer[8];
memcpy(buffer, p1, sizeof buffer);
memcpy(p1, p2, sizeof buffer);
memcpy(p2, buffer, sizeof buffer);
p1 += sizeof buffer;
p2 += sizeof buffer;
size -= sizeof buffer;
}
if (size > 0) {
if (size >= 4) {
char buffer[4];
memcpy(buffer, p1, sizeof buffer);
memcpy(p1, p2, sizeof buffer);
memcpy(p2, buffer, sizeof buffer);
p1 += sizeof buffer;
p2 += sizeof buffer;
size -= sizeof buffer;
}
while (size > 0) {
unsigned char temp = *p1;
*p1 = *p2;
*p2 = temp;
p1++;
p2++;
size--;
}
}
}
Also note these remarks:
The standard library function qsort has a different prototype:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
Using int for sizes and index values is not recommended. left * size might overflow for a large array on systems with 64-bit size_t and 32-bit int: The _qsort function assumes that left * size and right * size are within the range of type size_t and at most the size of the array pointed to by v, yet this size might exceed INT_MAX, causing the int multiplication to overflow with undefined behavior. The correct type is size_t and a sanity check at the beginning of the function can be used to detect invalid arguments.