Home > Back-end >  confused with call-by-reference in C
confused with call-by-reference in C

Time:12-16

This function does not return the intended result (swapping a and b).

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

void swap_one(int *x, int *y) {
    int *tmp;
    tmp = x;
    x = y;
    y = tmp;
    printf("x = %d y = %d\n", *x, *y);
}

void swap_two(int *x, int *y) {

}

int main() {
    int a = 5;
    int b = 100;
    printf("Before a = %d b = %d\n\n", a, b);
    int *p = (int *) malloc(sizeof(int));
    int *q = (int *) malloc(sizeof(int));
    p = &a;
    q = &b;

    swap_one(p, q);
    printf("After a = %d b = %d\n", a, b);
    return 0;
}

But the below code works as expected.

#include <stdlib.h>
#include <stdio.h>
typedef struct ListElmt_ {
    void *data;
    struct ListElmt_ *next;
} ListElmt;

typedef struct List_ {
    int size;
    int (*match) (const void *key1, const void *key2);
    void (*destroy) (void *data);
    ListElmt *head;
    ListElmt *tail;
} List;

void list_init (List *list) {
    list->size = 0;
    list->match = NULL;
    list->destroy = NULL;
    list->head = NULL;
    list->tail = NULL;
}

int list_ins_next(List *list, ListElmt *element, void *data) {
    ListElmt *new_element;
    /* Alocate storage for the element. */
    if ((new_element = (ListElmt *) malloc(sizeof(ListElmt))) == NULL) return -1;
    /* new_element->data is of type void *. So we use (void *) data */
    new_element->data = (void *)data;
    if (element == NULL) {
        /* Handle insertion at the head of the list */
        if (list->size == 0) list->tail = new_element;
        new_element->next = list->head;
        list->head = new_element;
    } else {
        if (element->next == NULL) list->tail = new_element;
        new_element->next = element->next;
        element->next = new_element;
    }
    list->size  ;
    return 0;
}

/* Print the list */
static void print_list(const List *list) {
    ListElmt *element;
    int *data;
    int i;
    /* Display the linked list */
    fprintf(stdout, "List size is %d\n", list->size);
    i = 0;
    element = list->head;
    while (1) {
        data = element->data;
        fprintf(stdout, "list[d] = d\n", i, *data);
        i  ;
        if (element->next == NULL) {
            break;
        } else {
            element  = element->next;
        }
    }
}

int main(int argc, char **argv) {
    List list;
    ListElmt *element;
    int *data;
    int i;
    /* list = (List *) malloc(sizeof(List)); */
    /* Initialize the linked list */
    List *listPtr;
    listPtr = &list;
    list_init(listPtr);
    /* Perform some linked list operations */
    element = listPtr->head;
    for (i = 10; i > 0; i--) {
        if ( (data = (int *) malloc(sizeof(int))) == NULL) return 1;
        *data = i;
        if (list_ins_next(listPtr, NULL, data) != 0) return 1;
    }
    print_list(listPtr);
    fprintf(stdout, "Value in *data is:%d\n", *data);
    return 0;
}

Question is: In the swap_one function, x=y is similar to new_element->next = element->next or element->next = new_element. Why do new_element->next = element->next and element->next = new_element work but x =y in the swap_one function does not swap a and b?

Sorry for lot of the code but I am genuinely confused about this one.

Thanks.

CodePudding user response:

The pointers are passed by value. Swapping the pointer values held in those variables does not achieve a swap of the data they point to.

  1. Imagine that &p is the address 0x1000 and &q is the address 0x1004.

  2. Now, you call swap_one(&p, &q); -- the value of the pointer x is 0x1000 and the value of the pointer y is 0x1004.

  3. You now swap the pointer values. x is now 0x1004 which points at q and y is now 0x1000 which points at p. However, p and q never moved in memory. You only changed the values stored in your pointers.

  4. When the function returns, those pointers go out of scope. p and q still hold the same contents as before, because you never modified them in the first place.

So, to swap the values they point at, you must dereference the pointer to get at the actual data.

int tmp = *x;
*x = *y;
*y = tmp;

Contrast this to your linked list example. In that example, you had a pointer to a structure, and you were modifying its next member. This actually modifies the pointer in memory, because that's where your linked list resides and how it stores values.

However, the same issue would occur if you gave these pointers to some hypothetical function: swap_next(node **x, node **y); -- x and y are just local variables pointing at a pointer value. If you swap them, they do not modify anything other than themselves. So the same fix applies: to change data being pointed at, you must follow the pointer.

CodePudding user response:

Your swap_one function is only swapping the pointer values passed to the function, not what the pointers are pointing to. So any change you make won't be reflected outside of the function.

You need to dereference those pointers to read/write what they point to.

void swap_one(int *x, int *y) {
    int tmp;
    tmp = *x;
    *x = *y;
    *y = tmp;
    printf("x = %d y = %d\n", *x, *y);
}

In the list_ins_next function, you are changing what the pointers point to, so the changes are seen outside of the function.

CodePudding user response:

The variable x and y are local variable of swap_one(). This is what happening when you calling swap_one() function:

p = &a;
q = &b;

p (pointer)        q (pointer)
---                ---
| |---             | |--- 
---   |            ---   |
      |                  |
   a ----             b ----
     |  |               |  |
     ----               ----

Calling swap_one(p, q) function from main() function:

p (pointer)        q (pointer)
---                ---
| |---             | |--- 
---   |            ---   |
      |                  |
   a ----             b ----
     |  |               |  |
     ----               ----
      |                  |
      |                  |
---   |            ---   |
| |---             | |--- 
---                ---   
x (pointer)        y (pointer)
pointing to a      pointing to b

(x and y are parameters of swap_one()) 

After execution of these statements of swap_one() function:

tmp = x;
x = y;
y = tmp;

the pointer x will point to address that pointer y was pointing at and pointer y will point to address that pointer x was pointing at when swap_one() was called.

 p (pointer)        q (pointer)
---                ---
| |---             | |--- 
---   |            ---   |
      |                  |
   a ----             b ----
     |  | -----         |  |
     ----      |        ----
               |         | 
       --------|--------- 
---   |        |   ---   
| |---          ---| |
---                ---   
x (pointer)        y (pointer)
pointing to b      pointing to a 

Note that pointer p and pointer q are still pointing to variable a and b respectively. That's why when swap_one() function returned, the value of variable a and b is not swapped.

If you want to swap the value of the variables, whose address passed as arguments to swap_one() function than dereference the pointer parameters and replace the value at the location, i.e. you should do following in swap_one() function:

    tmp = *x;
    *x = *y; 
    *y = tmp;

With these changes the values of variable whose address passed to swap_one() function will be swapped because, now, you are dereferencing the pointer x (i.e. *x) and pointer y (i.e. *y) and assigning the values at that location.

Now, coming to this part of second code. Taking the case when element is not NULL

    } else {
    if (element->next == NULL) list->tail = new_element;
    new_element->next = element->next;
    element->next = new_element;

Note that element pointer is of type ListElmt and yes it is also a local pointer variable of list_ins_next() function. But, here we are making use of this local pointer variable to modify the value of structure member which it is pointing to.

    new_element (pointer of type ListElmt)
      ----
      |  |----- 
      ----     |
               |
           -----------
           |    |    |
           -----------
             ^      ^
             |      |
          data     next
          pointer  pointer


    element (pointer of type ListElmt, which is pointing to an existing node of list
      ----   passed to list_ins_next() function)
      |  |----- 
      ----     |
               |
           -----------
           |    |    |
           -----------
             ^      ^
             |      |
          data     next
          pointer  pointer

Note that this

new_element->next 

is same as

(*new_element).next

which means, dereferencing the new_element pointer (go to the location where it is pointing to) and access the next pointer at that location.

So, this

new_element->next = element->next;

will make the new_element->next pointer pointing to what element->next pointer is pointing and this

element->next = new_element; // same as (*element).next = new_element;

will make the element->next pointer pointing to what new_element pointer is pointing at. After execution of these statements,

        new_element (pointer of type ListElmt)
      ----
      |  |----- 
      ----     |
               |
           -----------
       --- |    |    |-------> (pointing to what element->next was pointing at)
      |    -----------
      |      ^      ^
      |      |      |
      |   data     next
      |   pointer  pointer
      -------------------- 
                          |
    element               |
      |  |-----           |
      ----     |          |
               |          |
           -----------    |
           |    |    |---- 
           -----------
             ^      ^
             |      |
          data     next
          pointer  pointer

So, if you want to make changes in the pointer that you are passing to a function, you have to dereference it in the calling function.


If you are still confused then only check this:

In your second code if you do same as what you are doing in first code:

example = <some pointer of type ListElmt>;

i.e. assign some other pointer of same type to example, this is what will happen:

     element 
      ----                 -----------
      |  |---------------> |    |    |
      ----                 -----------

         (The original ListElmt type element
          whose address passed to list_ins_next() function)
           -----------
           |    |    |
           -----------
             ^      ^
             |      |
          data     next
          pointer  pointer

Now, when list_ins_next() function will returned, the original element whose address was passed to this function will remain unchanged.


Additional:

Here, your program is leaking memory:

    int *p = (int *) malloc(sizeof(int));
    int *q = (int *) malloc(sizeof(int));
    p = &a;
    q = &b;

because the allocated memory references, to pointer p and q, (using malloc()) will be lost when you are assigning &a and &b to p and q respectively.

  • Related