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.
realloc()
returnsNULL
, because there is not enough memory to satisfy the request. The former address is still valid.realloc()
returns the same address, because it can satisfy the request this way. Since returned and former address are equal, both are valid.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.