Home > other >  Why can I reposition a pointer given as an argument to a function but not reassign a new value to it
Why can I reposition a pointer given as an argument to a function but not reassign a new value to it

Time:11-11

In this question I'm referring to another stackoverflow question because I don't have enough points to comment it.


See: Valgrind Reports Invalid Realloc

In short: Why must the instruction buffer = trim(buffer); be written outside the function? Why can't it be written inside the function as phrase = (char*)realloc(phrase, strlen(phrase) 1);?


In depth: Assuimng I pass any pointer to a function - such as const *str; - then by executing str ; I can create a side effect and change the starting position of the string.

But why can't I just reassign another value to the function through dynamic memory management functions such as malloc and realloc?

Why can't I just set str = (char*) realloc(str, strlen(str) 1 * sizeof(char));?

What makes this side effect different from the other one? Couldn't I hypothetically just move str where ever I want to?

CodePudding user response:

C passes all arguments by value. Look at the parameters of a called function like local variables of this function, initialized by copies of the arguments given by the caller.

We can "emulate" pass by reference, if we pass a pointer. By dereferencing the pointer, we can access the "referenced" object living outside the called function. But this pointer is still passed by value, meaning it is a copy of the argument, initializing the parameter.

Note: The references of C and other languages are nothing else than pointers, under the hood. There are additional semantics, though. You might want to look at generated machine code.

So you can do anything you want with that pointer in the parameter, overwrite it, increment or decrement it, even NULL it. This has no effect of the source of the pointer in the caller.

The problem of the question you link can be boiled down to:

char* called(char* pointer)
{
    return realloc(pointer, /* some irrelevant value */);
}

void caller(void)
{
  char* buffer = malloc(/* some irrelevant value */);

  /* ignore returned pointer */ called(buffer);

  free(buffer); /* here Valgrind reports an error */
}

We need to differentiate multiple cases for realloc() here.

  1. realloc() returns NULL, because there is not enough memory to satisfy the request. The former address is still valid.
  2. realloc() returns the same address, because it can satisfy the request this way. Since returned and former address are equal, both are valid.
  3. realloc() returns a new address. The former address is now invalid.

Of these, the third case is the common one, and it leads to the documented issue.

Because buffer in caller() is not changed by called(), simply because called() cannot access it, it still holds the former address. Now when free() is called with this invalid address, the error is detected.

To correct this error, caller() needs to use the returned value. The Right WayTM to do this is:

void caller(void)
{
  char* buffer = malloc(/* some irrelevant value */);

  char* new_buffer = called(buffer);
  if (new_buffer != NULL) {
    buffer = new_buffer;
  } else {
    /* handle the re-allocation error, the address in buffer is still valid */
  }

  free(buffer);
}

An alternative is to pass the pointer to buffer to called() and let it modify buffer correctly. But this type of redirection often generates worse readable code. However, for a convenience function you might decide to go this route.

  •  Tags:  
  • c
  • Related