1

I've realized now that in many of my codes, I will have 2 or 3 functions like this:

void swap(int* a, int* b)
{
    int t = *a;
    *a = *b;
    *b = t;
}

Each with their own pointer type. What I'm wondering is, if there is a way to swap two elements of an array, for example, regardless of the array type?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • @SudheeshSinganamalla well i have now, but i have no idea about that anserws notation. Things like `#define` for a function and not just to define a value like `#define BUFFER 64` – BentesDentesRentes May 28 '18 at 04:30
  • Well, [`#define` can be as complex as you like](https://msdn.microsoft.com/en-us/library/teas0593.aspx). It's like automatic copy & paste in your code. – ctietze May 28 '18 at 04:47
  • Since you asked about arrays, is this a sorting problem? If so there are much more efficient approaches than swapping large values. For example, pointers to pointers, or have doubly-linked lists and then you're just swapping fixed size things (namely pointers). – Ahmed Masud May 28 '18 at 05:06
  • @alk why close a duplicate of that question when it just asks for a built-in way, and with subpar answers? – Antti Haapala -- Слава Україні May 28 '18 at 05:25
  • See also [Error: cannot convert `double *` to `char *` for argument 1 to `void swap(char *, char *, unsigned int)`](https://stackoverflow.com/q/49871989/15168) — there's a `generic_swap()` there, too. – Jonathan Leffler Jun 03 '18 at 07:34

2 Answers2

4

Yes, but you have to tell the swap code how big the elements are:

void generic_swap(void *v1, void *v2, size_t size)
{
    char temp[size];
    memmove(temp, v1, size);
    memmove(v1, v2, size);
    memmove(v2, temp, size);
}

This uses a VLA (variable length array — a feature of C99 and an optional feature of C11) for the temporary space. The size of the local array, temp, is controlled at runtime by the function parameter size. If you don't trust your users not to request swapping multiple megabytes of data, you can use dynamic memory allocation instead, or only use dynamic memory allocation if the size is bigger than, say, 1 kilobyte.

Either:

void generic_swap(void *v1, void *v2, size_t size)
{
    size_t chunk = (size > 1024) ? 1024 : size;
    size_t offset = 0;
    char *s1 = v1;
    char *s2 = v2;
    char  temp[chunk];
    while (size > 0)
    {
        size_t length = (size > chunk) ? chunk : size;
        memmove(temp, s1 + offset, length);
        memmove(s1 + offset, s2 + offset, length);
        memmove(s2 + offset, temp, length);
        size -= length;
        offset += length;
    }
}

Or:

void generic_swap(void *v1, void *v2, size_t size)
{
    void *v3 = malloc(size);
    if (v3 != 0)
    {
        memmove(v3, v1, size);
        memmove(v1, v2, size);
        memmove(v2, v3, size);
        free(v3);
    }
}

The loop version avoids the overhead of dynamic memory allocation, and won't be much slower than copying it all in three operations. There are various ways that could be used to tune the looping code — see also the comments by rici about other ways in which you can optimize that if you find that the swapping code is a bottleneck. You're at liberty to choose a smaller size than 1024 bytes; 64 or 128 might be feasible too, and you don't necessarily need a VLA in the function.

To swap two integers:

int i = 37;
int j = 99;

swap_generic(&i, &j, sizeof(i));

To swap two arrays of char:

char data[80] = "A tabloid writer's nightmare on steroids";
char info[80] = "Obsequiousness will get you nowhere fast";

swap_generic(data, info, sizeof(data));

Etc. Note that the arrays need to be the same size — or, more accurately, the size you specify needs to be the size of the smaller of the arrays to be safe.

You can use memcpy() instead of memmove() if you are happy to live dangerously — though the danger is limited in this context. (If you swap an object with itself, you invoke undefined behaviour. Otherwise, it is safe.) Using memmove() always works; using memcpy() usually works. I prefer 'always' to 'mostly'.


Test harness for the three algorithms

Compile with, for example:

gcc -O3 -g -std=c11 -Wall -Wextra -Werror -DUSE_GENSWAP_3 swap89.c -o swap89

When run with Valgrind, the code gets a clean bill of health.

Code:

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

#if !defined(USE_GENSWAP_1) && !defined(USE_GENSWAP_2) && !defined(USE_GENSWAP_3)
#define USE_GENSWAP_1
#endif

extern void generic_swap(void *v1, void *v2, size_t size);

#ifdef USE_GENSWAP_1
void generic_swap(void *v1, void *v2, size_t size)
{
    char temp[size];
    memmove(temp, v1, size);
    memmove(v1, v2, size);
    memmove(v2, temp, size);
}
#endif

#ifdef USE_GENSWAP_2
void generic_swap(void *v1, void *v2, size_t size)
{
    size_t chunk = (size > 1024) ? 1024 : size;
    size_t offset = 0;
    char *s1 = v1;
    char *s2 = v2;
    char  temp[chunk];
    while (size > 0)
    {
        size_t length = (size > chunk) ? chunk : size;
        memmove(temp, s1 + offset, length);
        memmove(s1 + offset, s2 + offset, length);
        memmove(s2 + offset, temp, length);
        size -= length;
        offset += length;
    }
}
#endif

#ifdef USE_GENSWAP_3
void generic_swap(void *v1, void *v2, size_t size)
{
    void *v3 = malloc(size);
    if (v3 != 0)
    {
        memmove(v3, v1, size);
        memmove(v1, v2, size);
        memmove(v2, v3, size);
        free(v3);
    }
}
#endif

static size_t min_len(size_t x, size_t y) { return (x < y) ? x : y; }

static void dump_long_buffer(const char *tag, size_t length, char buffer[length])
{
    int maxpadlen = strlen(tag) + sizeof(" = ") - 1;
    printf("%s = ", tag);
    size_t offset = 0;
    int padlen = 0;
    while (length > 0)
    {
        int linelen = min_len(length, 80 - maxpadlen - sizeof("[]\n"));
        printf("%*s[%.*s]\n", padlen, "", linelen, buffer + offset);
        offset += linelen;
        length -= linelen;
        padlen = maxpadlen;
    }
}

int main(void)
{
    int i = 37;
    int j = 99;

    printf("i = %d; j = %d\n", i, j);
    generic_swap(&i, &j, sizeof(i));
    printf("i = %d; j = %d\n", i, j);

    char data[80] = "A tabloid writer's nightmare on steroids";
    char info[80] = "Obsequiousness will get you nowhere fast";

    printf("data = [%s]\ninfo = [%s]\n", data, info);
    generic_swap(data, info, sizeof(data));
    printf("data = [%s]\ninfo = [%s]\n", data, info);

    char maxibuff1[2560];
    char maxibuff2[2560];

    for (size_t k = 0; k < sizeof(maxibuff1); k++)
    {
        maxibuff1[k] = k % 64 + '!';
        maxibuff2[k] = 'z' - k % 64;
    }

    /* The aligned output is mostly the result of serendipity */
    dump_long_buffer("maxibuff1", sizeof(maxibuff1), maxibuff1);
    dump_long_buffer("maxibuff2", sizeof(maxibuff2), maxibuff2);
    generic_swap(maxibuff1, maxibuff2, sizeof(maxibuff1));
    dump_long_buffer("maxibuff1", sizeof(maxibuff1), maxibuff1);
    dump_long_buffer("maxibuff2", sizeof(maxibuff2), maxibuff2);

    return 0;
}

Sample output (the result is the same from each algorithm):

i = 37; j = 99
i = 99; j = 37
data = [A tabloid writer's nightmare on steroids]
info = [Obsequiousness will get you nowhere fast]
data = [Obsequiousness will get you nowhere fast]
info = [A tabloid writer's nightmare on steroids]
maxibuff1 = [!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`]
            [!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`]
…
            [!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`]
maxibuff2 = [zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;]
            [zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;]
…
            [zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;]
maxibuff1 = [zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;]
            [zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;]
…
            [zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;]
maxibuff2 = [!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`]
            [!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`]
…
            [!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`]
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Ok i understood the algorithm, but could you tell me what VLA is? And how would i use dynamic memory allocation with this generic swap function? – BentesDentesRentes May 28 '18 at 04:34
  • See updates. Frankly, the looped copying is likely to be pretty good. I could use an enum instead of 1024 (`enum { MAX_CHUNK_SIZE = 1024 };`) but it is barely helpful. – Jonathan Leffler May 28 '18 at 04:48
  • @rici: Isn't that what the looping code in the second implementation does — where I've defined 'a few' as 'up to 1024'? – Jonathan Leffler May 28 '18 at 05:02
  • @jonathanLeffler: Yes, now that I look at it more closely, but that's probably less efficient than doing it fewer bytes at a time, particularly if you can make the number of bytes a compile-time constant. In your code you've got your own loop and another hidden loop (and loop setup) inside the memcpy. IIRC BSD qsort uses a switch to choose between a small number of smallish sizes (depending on alignment), falling back to byte-at-a-time if the alignment is really awful; you might improve that for large objects by adding a case which can take advantage of vectorization to unroll the inner loop. – rici May 28 '18 at 05:08
  • Consider `void* restrict` pointers - should allow you to swap out memmove for memcpy and gain a bit better performance. – Lundin May 28 '18 at 06:52
0

In complement of the great answer from Jonathan Leffler, if using gcc you can do it as a macro:

#define SWAP(a,b) \
  ({  __auto_type _store = (a); \
    (a) = (b); \
    (b) = _store; })

this uses the __auto_type C extension to avoid having to supply the type, but it wont work on evry compiler see https://gcc.gnu.org/onlinedocs/gcc/Typeof.html.

It also pollutes the namespace a bit as it define a _store variable

  • Interesting extension. Does this work with arrays? You can't normally assign arrays. – Jonathan Leffler May 28 '18 at 14:49
  • After a quick test this does not work on static arrays indeed as you cannot assign them, unless you use type punning and a union to see them as pointers, but this hack is quite convoluted and using a dedicated copy function would be beter for clarity's sake. – Samir Labane May 29 '18 at 14:19
  • I don't think namespace pollution is a problem; the scope of `_store` is the expression statement in which it is defined, so it isn't visible outside that statement. – Jonathan Leffler Nov 04 '18 at 20:34