arrayscpointersswapdouble-pointer

Generic swap function to swap strings and arrays in C


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.


Solution

  • 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;
    }