-1

I found on this thread the following function to shuffle any kind of data types:

#define NELEMS(x)  (sizeof(x) / sizeof(x[0]))

/* arrange the N elements of ARRAY in random order.
 * Only effective if N is much smaller than RAND_MAX;
 * if this may not be the case, use a better random
 * number generator. */
static void shuffle(void *array, size_t n, size_t size) {
    char tmp[size];
    char *arr = array;
    size_t stride = size * sizeof(char);

    if (n > 1) {
        size_t i;
        for (i = 0; i < n - 1; ++i) {
            size_t rnd = (size_t) rand();
            size_t j = i + rnd / (RAND_MAX / (n - i) + 1);

            memcpy(tmp, arr + j * stride, size);
            memcpy(arr + j * stride, arr + i * stride, size);
            memcpy(arr + i * stride, tmp, size);
        }
    }
}

I have been testing and it seems to work fine but I'm having a hard time understanding how and why it works.

  1. How and why doesn't it swap an element of the array directly on array
  2. How come it is possible to assign array to a char*: char *arr = array;
  3. The offset i * stride or j * stride in memcpy functions is bigger than the total size of the array (sizeof(array)). How come the pointer arithmetic works here?
fassn
  • 299
  • 1
  • 5
  • 15
  • 1
    You can't swap an element directly. Not all processors have a memory-memory swap `XCHG` instruction. – Weather Vane May 30 '19 at 18:55
  • 1
    It might be worthwhile for you to get hold of a C book. – Antti Haapala -- Слава Україні May 30 '19 at 19:02
  • @AnttiHaapala Is there one you would recommend? – fassn May 31 '19 at 13:25
  • 1
    @fassn https://stackoverflow.com/questions/562303/the-definitive-c-book-guide-and-list – Antti Haapala -- Слава Україні May 31 '19 at 13:26
  • A key point not mentioned in the answers is that Standard C does not allow you to do pointer arithmetic using variables of type `void *`. GCC does allow it as an extension — and it assumes that the size of the type pointed at is `1`, the same as `char`. Since `void *` and `char *` have a very tight relationship. See C11 [§6.2.5 Types ¶28](http://port70.net/~nsz/c/c11/n1570.html#6.2.5p28) — _A pointer to void shall have the same representation and alignment requirements as a pointer to a character type._ and references footnote 48) _[…continued…]_ – Jonathan Leffler Jan 01 '22 at 20:21
  • _[…continuation…]_ and the footnote says _The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions._ The conversion of the `void *` to `char *` allows pointer arithmetic to work correctly in conforming compilers (as well as GCC and Clang). – Jonathan Leffler Jan 01 '22 at 20:22

2 Answers2

1

For better understanding I will shuffle the answering order:

2 - array is a pointer of type void. In C, pointers may be assigned to and from pointers of type void*. Any pointer to an object may be converted to type void* without loss of information. If the result is converted back to the original pointer type, the original pointer is recovered.

1 - It does not work the same for single elements, you do not have a generic type that can be assigned to any type. So the code is switching the content of pointed memory.

3 - n is the number of elements in array, while size is the size of a single element in the array. stride = size * sizeof(char); means stride is equal to size, as sizeof(char) equals 1. The size of the array sizeof(array) equals n * size - the number of elements in the array multiplied with the size of an element. Since both i and j are less than n, i * stride and j * stride will never be greater than the memory used by the array. I wonder though why the use of this stride, as far as I know sizeof(char) is always 1.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
user3346850
  • 254
  • 4
  • 17
0
  1. array is of type void*. It is just a pointer with no interpretation, so we don't know what data type it points to. The number of elements is given int n and the size of each in size. It is done this way so the function can be used with arrays of any type.
  2. char *arr = array; is interpreting the void pointer as pointing to a sequence of bytes.
  3. sizeof(array) gives the size of the pointer, not the size of the actual array. The actual size in bytes is i * size.

The shuffle algorithm appears to be the Fisher-Yates Shuffle.

I'll also note that * sizeof(char) is pointless, since sizeof(char) is 1 by definition.

Fred Larson
  • 60,987
  • 18
  • 112
  • 174