How do I modify a pointer that has been passed into a function in C?
Asked Answered
P

7

78

So, I have some code, kind of like the following, to add a struct to a list of structs:

void barPush(BarList * list,Bar * bar)
{
    // if there is no move to add, then we are done
    if (bar == NULL) return;//EMPTY_LIST;

    // allocate space for the new node
    BarList * newNode = malloc(sizeof(BarList));

    // assign the right values
    newNode->val = bar;
    newNode->nextBar = list;

    // and set list to be equal to the new head of the list
    list = newNode; // This line works, but list only changes inside of this function
}

These structures are defined as follows:

typedef struct Bar
{
    // this isn't too important
} Bar;

#define EMPTY_LIST NULL

typedef struct BarList
{
    Bar * val;
    struct  BarList * nextBar;
} BarList;

and then in another file I do something like the following:

BarList * l;

l = EMPTY_LIST;
barPush(l,&b1); // b1 and b2 are just Bar's
barPush(l,&b2);

However, after this, l still points to EMPTY_LIST, not the modified version created inside of barPush. Do I have to pass list in as a pointer to a pointer if I want to modify it, or is there some other dark incantation required?

Publicspirited answered 20/4, 2009 at 4:31 Comment(0)
L
77

You need to pass in a pointer to a pointer if you want to do this.

void barPush(BarList ** list,Bar * bar)
{
    if (list == NULL) return; // need to pass in the pointer to your pointer to your list.

    // if there is no move to add, then we are done
    if (bar == NULL) return;

    // allocate space for the new node
    BarList * newNode = malloc(sizeof(BarList));

    // assign the right values
    newNode->val = bar;
    newNode->nextBar = *list;

    // and set the contents of the pointer to the pointer to the head of the list 
    // (ie: the pointer the the head of the list) to the new node.
    *list = newNode; 
}

Then use it like this:

BarList * l;

l = EMPTY_LIST;
barPush(&l,&b1); // b1 and b2 are just Bar's
barPush(&l,&b2);

Jonathan Leffler suggested returning the new head of the list in the comments:

BarList *barPush(BarList *list,Bar *bar)
{
    // if there is no move to add, then we are done - return unmodified list.
    if (bar == NULL) return list;  

    // allocate space for the new node
    BarList * newNode = malloc(sizeof(BarList));

    // assign the right values
    newNode->val = bar;
    newNode->nextBar = list;

    // return the new head of the list.
    return newNode; 
}

Usage becomes:

BarList * l;

l = EMPTY_LIST;
l = barPush(l,&b1); // b1 and b2 are just Bar's
l = barPush(l,&b2);
Leandra answered 20/4, 2009 at 4:35 Comment(2)
Thanks, I'd figured this was the problem, but hoped that it wasn't ;)Publicspirited
Alternatively, have the function return the pointer to the new head of the list. BarList *barPush(BarList *list, Bar *bar)Pendleton
K
28

Generic answer: Pass a pointer to the thing you want to change.

In this case, it would be a pointer to the pointer you want to change.

Kendrakendrah answered 21/4, 2009 at 0:43 Comment(0)
G
19

Remember, in C, EVERYTHING is passed by value.

You pass in a pointer to a pointer, like this

int myFunction(int** param1, int** param2) {

// now I can change the ACTUAL pointer - kind of like passing a pointer by reference 

}
Goings answered 20/4, 2009 at 4:33 Comment(0)
M
7

This is a classic problem. Either return the allocated node or use a pointer of pointer. In C, you should pass a pointer to a X to a function where you want your X to be modified. In this case, since you want a pointer to be modified, you ought to pass a pointer to a pointer.

Miru answered 20/4, 2009 at 4:35 Comment(0)
S
4

Yes, you have to pass in a pointer to the pointer. C passes arguments by value, not by reference.

Stockstill answered 20/4, 2009 at 4:33 Comment(0)
C
3

Modifying a pointer in another function requires a concept called multiple indirection, I will explain it in later, spoiler solution given @geofftnz uses multiple indirection. What I am trying to do is try my best to explain multiple indirection in C.

Consider the following two programs, I will walk through the code.

The following program does not use multiple indirection, so it fails.

program with error:

// filename: noIndirection.c
#include <stdio.h>
#include <stdlib.h>

void allocater(int *ptrTempAllctr)
{
    ptrTempAllctr = malloc(sizeof(int));
    if (ptrTempAllctr == NULL) {
        perror("in allocater() memory allocation error");
        exit(EXIT_FAILURE);
    }
}

int main() 
{
    int *ptrMain = NULL;
    allocater(ptrMain);
    if (ptrMain == NULL) {
        printf("ptrMain is points to NULL\n");
        return 1;
    }
    //free(ptrMain);  // we don't have to free because it will be invalid free.
    return 0;
}

consider the above program(noIndirection.c), which has a variable ptrMain it is a pointer points to an int. If it was passed to a function, in the function scope(body) a temporary pointer variable is created because arguments of the function are temporary variables, they get deleted when they go out of scope.

The temporary pointer variable ptrTempAllctr (which is a argument) will point to what ever the caller(main) function's variable ptrMain(which is pointing to NULL) pointed when it was passed as argument to the function.

If we use malloc() or assign another pointer to the temporary variable ptrTempAllctr then it will point to it but the pointer variable in caller(main) function that was passed as argument to the to function allocater() still points to the same data(which is NULL) which it was pointed before the function call.

When the called (allocater()) function goes out of scope the temporary pointer variable is popped from stack and the memory left unallocated we end up memory leak. To bypass this limitation we need use multiple indirection.

MULTIPLE INDIRECTION:

Multiple indirection when we use of pointer/s to pointer/s in varying level(with multiple `*`) eg: `int **pp, int ***ppp`, etc.

and we assign them using address-of(&) operator.

what multiple indirection pointer type variables does is, allows us to make is a pointer to a pointer variable itself for fixing the above program. This allows us to pass the address of the ptrMain to allocater() using this call

allocater(&ptrMain);

thus the above program noIndirection.c doesn't allow us do this, see the program withIndirection.c to implement this multiple indirection.

We need pointer to int pointer(int **ptrMain) as function argument for allocater() function in this case to solve the above buggy program(noIndirection.c).

This was used in the following program.

The following program uses multiple indirection to solve the bug in previous program.

// filename: withIndirection.c
#include <stdio.h>
#include <stdlib.h>

void trueAllocater(int **ptrTrueAllocater)
{
    *ptrTrueAllocater = (int *) malloc(sizeof(int));
    if (ptrTrueAllocater == NULL) {
        perror("in trueAllocater() memory allocation error");
        exit(EXIT_FAILURE);
    }
}

int main(void) 
{
    int *ptrMain = NULL;
    trueAllocater(&ptrMain);
    if (ptrMain == NULL) {
        printf("memory not allocated\n");
        return EXIT_FAILURE;
    }

    printf("memory allocated and assigned to ptrMain");
    printf(" from trueAllocater\n");

    free(ptrMain);
    return EXIT_SUCCESS;
}

see the program withIndirection.c for reference from now.

To solve our problem we need pass the address of the pointer variable ptrMain (trueAllocater(&ptrMain);) to the trueAllocater, in-order to change ptrMain where it needs to be pointing later on in trueAllocater() or another function, to do this the function needs to accept indirection pointer with correct level of indirection, which is to add another * added to the argument declaration to my current understanding for the variables that are passed.

By means we need to have the trueAllocater() function argument as int ** from int * in withIndirection.c as opposed to noIndirection.c so the indirection level will be statisfied.

When the address of the caller's argument variable ptrMain's actual address was passed to the function. the temprary ptrTrueAllocater argument variable in function is pointing to the address of pointer variable ptrMain's address in caller(main) function not what pointer variable ptrMain(which is NULL in the program) points to in function(main).

If we dereference the ptrTrueAllocater variable the address which ptrMain points to will be revealed because the ptrTrueAllocater temporary variable is pointing to the caller(main) ptrMain variable itself not it contents.

The contents of the dereferenced ptrTrueAllocater variable will be the address of the data which was pointed by the caller(main)'s variable(ptrMain), so we have to do one additional dereference to get the final data.

so we have to dereference once to get the address of the ptrMain which it points in-order to change the where ptrMain needs to be pointed and dereference twice to get the actual data pointed by the ptrMain which is NULL.

@PaulWicks you intented to change so you have to dereference once to allocate or change where its pointing.

The intent of multiple indirecting using pointers is to create multi-dimensional array and passing pointer arguments that need to be pointed to something.

We need to change the variable according to the types we have to manipulate like the following,

every addition of * in declaration will increase pointer indirection level and every dereference will decrease the pointer indirection level that is will get close to data.

We can solve this problem by returning the address to the caller function assigning to the required pointer variable.

yes, we can use this multi indirection variable syntax for creating one or multi dimensional arrays. This will confuse beginners at first, if they put time to read lots of code they'll able to find the difference between them.

Please correct me if I am wrong, please give feedback and let me know what are the other uses of multiple indirection pointers. apologies for my bad english. these are the resources helped me to understand multiple indirections. https://boredzo.org/pointers/#function_pointers https://cseweb.ucsd.edu/~ricko/rt_lt.rule.html

Congelation answered 2/6, 2021 at 16:8 Comment(0)
E
1
main() {
    node *x;
    newNode(&x);

}

void newNode(node **a)
{
    *a = (node *)malloc(sizeof(node));
    //.........
}
Error answered 18/11, 2022 at 16:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.