Search code examples
cpointersvoid

When receiving a void** type as a function parameter, the data in the memory area of ​the other variable is changed


I was testing the linked list examples in the "Mastering Algorithms with C (By Kyle Loudon)"

In the example, there was an error in the operation of the function using the void** parameter. The operation error is as below code.

code:

#include <stdio.h>

void myswap(void **val1, void **val2)
{
    void **val3;
    *val3 = *val1;
    *val1 = *val2;
    *val2 = *val3;
}

int main()
{
    int val1 = 10;
    int val2 = 20;

    printf("%d, %d\n", val1, val2);
    myswap((void **)&val1, (void **)&val2);
    printf("%d, %d\n", val1, val2);

    return 0;
}

result:

10, 20
0, 10 <- must be 20, 10

This seems to be an error caused by the difference between the size of the variable and the size of the pointer. This can be solved by using a long variable instead of an int variable or by creating a padding variable between the two variables in main(). (I use a 64bit system. So, the size of pointers and long types is 64 bits.)

However, I want to know how to solve it without modifying the variables, structures, and functions of the already written examples. if it is possible.

If anyone knows about this issue, please help


Solution

  • Built with gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0, your myswap function throws a segmentation fault when dereferencing val3 that is not initialized (cf. *val3 = *val1 statement).

    If myswap's goal is to make val1 points on what val2 pointed to (and the opposite), then the code should be:

    void myswap(void **val1, void **val2)
    {
        void *tmp;
        tmp = *val1;
        *val1 = *val2;
        *val2 = tmp;
    }
    

    This way, your code prints the expected 10, 20 and 20, 10 messages.
    However, it still ends with a "*** stack smashing detected ***: terminated" and abort (core dumped) error messages.
    This is due to the wrong way of passing val1 and val2 arguments to myswap function.

    The myswap function considers val1 (resp. val2) as a double pointer variable, something that point to a void * address (itself pointing to something else).
    In the main context, &val1 is something that points to an int variable, and not a void * content!
    So when myswap runs a *val1 = ... operation, it gets problematic if sizeof(int) and sizeof(void*) are different (which is the case on x86_64 architecture).

    So, there are 3 things you could do:

    • Use the intptr_t (from stddint.h) instead of int, because intptr_t has the same size as void *.
      int main()
      {
          intptr_t val1 = 10;
          intptr_t val2 = 20;
      
          printf("%ld, %ld\n", (signed long)val1, (signed long)val2);
          myswap((void **)&val1, (void **)&val2);
          printf("%ld, %ld\n", (signed long)val1, (signed long)val2);
      
          return 0;
      }
      
      But this won't work for non-integer variables (i.e. struct, float...), where you cannot guarantee that variable size is the same as void *
    • Use intermediate pointers, and switching those:
      int main()
      {
          int val1 = 10;
          int val2 = 20;
          int * ptr1 = &val1;
          int * ptr2 = &val2;
      
          printf("%d, %d\n", *ptr1, *ptr2);
          myswap((void **)&ptr1, (void **)&ptr2);
          printf("%d, %d\n", *ptr1, *ptr2);
      
          return 0;
      }
      
      But this keep val1 and val2 variables unchanged, which might not be what you want.
    • Use memcpy and sizeof, as @0___________ is suggesting.