Generic swap function to swap strings and arrays in C

200 views Asked by At

I am learning C by trying to implement a generic swap function which is capable of swapping strings and arrays. I think it's possible because strings and arrays are essentially arrays in memory, and I am not swapping actual bytes but only pointers. Here is my implementation:

#include "stdio.h"

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

void print_array(char *c, int size) {
    for (int i = 0; i < size; ++i) {
        printf("%c ", c[i]);
    }
    printf("\n");
}

int main() {
    char *h = "hell";
    char *w = "world";
    printf("%s %s\n", h, w);
    swap((void **)&h, (void **)&w);
    printf("%s %s\n", h, w);

    char c[5] = { 'a', 'e', 'i', 'o', 'u' };
    char d[3] = { '1', '2', '3' };
    print_array(c, 5);
    print_array(d, 3);
    swap((void **)&c, (void **)&d);

    return 0;
}

The result is:

hell world
world hell
a e i o u 
1 2 3 
*** stack smashing detected ***: terminated
Aborted (core dumped)

As you can see, this generic swap function can swap two strings, but it triggers core dumped when the inputs are arrays. I don't understand which part of my understanding is wrong about pointers and arrays.

3

There are 3 answers

0
HolyBlackCat On BEST ANSWER
char c[5] = { 'a', 'e', 'i', 'o', 'u' };

Arrays are not pointers. &c is not char **.

Yes, you can do *c and *(c + 2) and other pointer-like things to an array, and it naturally makes you think it's a pointer, but it's not.

Those only work because an array is implicitly converted ("decays") to a pointer (to its first element) in many scenarios, including those ones. The resulting pointer is a temporary (an rvalue), it's computed on the fly when needed, and isn't stored in memory otherwise. The only thing stored in an array is its elements.

One of the cases where an array doesn't decay to a pointer is &c. This doesn't return char ** (pointer to pointer to char), and this can't possibly work, because the pointer resulting from decay is computed, not persistently stored in the memory.

You instead get a pointer of type char (*)[5] (pointer to an array of 5 chars), which has the same value as &c[0], but a different type (&c[0] is a char *).

Because there's no underlying pointer to swap, what you want is impossible.

The closest thing you can do is to go over each array element and swap it. This requires arrays to have the same length. Something like this:

#include <stddef.h>
#include <stdio.h>

void swap_memory(void *a, void *b, size_t n)
{
    char *ca = a;
    char *cb = b;
    char *end_ca = ca + n;
    while (ca != end_ca)
    {
        char tmp = *ca;
        *ca = *cb;
        *cb = tmp;
        ca++;
        cb++;
    }
}

void print_array(char *c, int size) {
    for (int i = 0; i < size; ++i) {
        printf("%c ", c[i]);
    }
    printf("\n");
}

int main() {
    char *h = "hell";
    char *w = "world";
    printf("%s %s\n", h, w);
    swap_memory(&h, &w, sizeof(char *));
    printf("%s %s\n", h, w);

    char c[5] = { 'a', 'e', 'i', 'o', 'u' };
    char d[5] = { '1', '2', '3' };
    swap_memory(c, d, 5); // or `(&c, &d, 5)`, doesn't matter
    print_array(c, 3);
    print_array(d, 5);

    return 0;
}
0
Vlad from Moscow On

You can not use the same function to swap pointers and to swap arrays.

For example to swap two arrays you need to swap their elements.

That is you can not exchange addresses of extents of memory occupeid by arrays. You can only exchange the contents of extents of memory occupied by arrays.

Here is a simple demonstration program that shows that to swap contents of two arrays you need to swap their pairs of elements

#include <stdio.h>

void print_array( const int a[], size_t n  ) 
{
    while ( n-- )
    {
        printf( "%02d ", *a++ );
    }

    putchar( '\n' );
}

int main( void )
{
    enum { N = 5 };
    int a[N] = { 1, 2, 3 ,4 ,5 };
    int b[N] = { 10, 20, 30 ,40 ,50 };

    print_array( a, N );
    print_array( b, N );

    putchar( '\n' );

    for (size_t i = 0; i < N; i++)
    {
        int tmp = a[i];
        a[i] = b[i];
        b[i] = tmp;
    }

    print_array( a, N );
    print_array( b, N );
}

The program output is

01 02 03 04 05
10 20 30 40 50

10 20 30 40 50
01 02 03 04 05

As for your function

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

then it is clear seen for example from this declaration

void *tmp;

that it swaps only two pointers of the type void *. it is unable for example to swap two characters because sizeof( char ) is less than sizeof( void * ).

As for the error then for example the value of the expression &c is the value of the address of the first element of the array c.

The operation of dereferencing of the address within the function swap

*b = *a;

reads the memory occupied by the array that is its elements {'a', 'e', 'i', 'o', 'u'}; as a value of an address that results in undefined behavior.

1
chqrlie On

You can design a generic swap function to swap objects of any type, but for the swap to be possible, the objects must have the same size. Here the string pointers do have the same size, sizeof(char *), which is the same as sizeof(void *), so your code should work as expected albeit it violates the strict aliasing rule. Conversely, sizeof(c) != sizeof(d) so swapping these arrays is not possible and furthermore, your swap function is not generic enough to swap objects whose size is not exactly that of a pointer.

The fundamental misunderstanding seems to be that arrays are not pointers. h and w are pointers to arrays of char, intialized to point to string literals, which are (non-modifiable) arrays of char, whereas c and d are actual arrays of resp. 6 and 4 bytes. Passing c to a function such as print_array does pass a pointer to their first elements, but that does not make them pointers.

Here is a modified version with a generic swap function with defined behavior:

#include <stdio.h>

void swap(void *a, void *b, size_t n) {
    unsigned char *aa = a;
    unsigned char *bb = b;
    for (size_t i = 0; i < n; i++) {
        unsigned char cc = aa[i];
        aa[i] = bb[i];
        bb[i] = cc;
    }
}

void print_array(const char *c, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        printf("%c ", c[i]);
    }
    printf("\n");
}

int main(void) {
    const char *h = "hell";
    const char *w = "world";
    printf("%s %s\n", h, w);
    swap(&h, &w, sizeof(h));
    printf("%s %s\n", h, w);

    char c[5] = { 'a', 'e', 'i', 'o', 'u' };
    char d[5] = { '1', '2', '3', '4', '5' };
    print_array(c, sizeof(c));
    print_array(d, sizeof(d));
    swap(c, d, sizeof(c));
    print_array(c, sizeof(c));
    print_array(d, sizeof(d));

    return 0;
}