Skip to content

Pointers and Scope

What is wrong with this code?

#include<cstdio>
int *get_pointer()
{
int value = 10;
int *result = &value;
return result;
}
int main()
{
int *p = get_pointer();
printf("%d\n", *p);
}

There is something bad here, but this will compile without warnings and will run and output the value 10.

Here is a hint. The following version prints 20, not 10!

#include<cstdio>
int *get_pointer()
{
int value = 10;
int *result = &value;
return result;
}
int get_value()
{
int result = 20;
return result;
}
int main()
{
int *p = get_pointer();
int other = get_value();
printf("%d\n", *p);
}

Think about what is happening in memory here, and think back to the details on the stack and how that works.

The problem relates to variable scope and lifetime.

Dangling Pointers

Lets step through the actions of get_pointer. This creates a variable, value, and assigns it the value 10. This is allocated on the stack, which is within the current function call. We then create a result pointer and get it to point to the value variable. Then we return the result, which points to value in the get_pointer data on the stack. At this point, get_pointer has ended, and it is removed from the stack. Removing its allocation for the value variable. The pointer we returned is now pointing to memory that is not actually allocated to anything!

This is referred to as a dangling pointer, which is a pointer that does not point to a valid value. It was valid, up until the get_pointer function returned.

So why did the first version seem to work? The problem here is that the location where value was stored has not yet been used for anything else. So it happens to still have the same value (10). In the second version, the get_value function was called, and this overrode the same location in memory for its result variable. Now when you dereference ptr in main, you get the new value 20.

When working with pointers you need to be careful never to return a pointer to something you have allocated on the stack. It is probably ok to pass pointers to stack values to parameters, but returning these pointers is always going to cause problems. The challenge can be that it will work for some time, and then strangely break for no apparent reason. So this is one of those places where you need to make sure these errors do not occur.

Dangling Pointers Up Close

The program begins at main, and lines 4 and 5 declare two variables p and other which are created on the stack. Note that variables of type long and long*are capable of storing memory addresses, though only p is declared as a pointer variable here.
At step 6a, the get_pointer() function is called
When the get_pointer() function runs, line 13 declares the value variable of type long, which is created on the stack and initialized with the value 10. Line 14 then declares the result variable of type long*, which is a pointer variable. result is initialized to contain the memory address of the value variable, and hence becomes a pointer to value
Lines 15 and 16 print out the memory addresses of the value and result variables as strings to the terminal. This is indicated by the '&' prefixing the variable names in the print statements
Line 17 returns the value of result (which is an address that points to value) to the caller in main()
When the call to get_pointer() returns, the return value from the function ('result') is copied to the variable p<, which then becomes a pointer to the memory area on the stack that points to variable value that was scoped within the function (step 6b)
Step 6c shows that once the function has returned and it's return value assigned to p, the function memory space on the stack is freed and available for other code to use. It so happens that data is left in place in these memory areas however (even though this data is no longer relevant with respect to the running program). In this case the pointer variable p has been left pointing to an area of memory that is not in use, but happens to contain the value '10'. We call this a dangling pointer
Line 7 prints the value of p which contains an address that points to the unallocated memory region left behind by the get_pointer() function that was called previously. Note that this time no '&' is required as a prefix to p in the print statement, since p already contains a memory address as it's value
At line 8, step 8a calls another function get_value()
The get_value() function runs, and lines 21 and 22, declare two local variables called result and other (of type long) on the stack, initializing them with values 20 and 30 respectively. Because the stack is a physical space, and acts like it's name suggests, it has allocated space for these two variables at the next free appropriate memory addresses available in the stack space. In this case since they are both of type long, they are allocated in the same physical memory space where the variables value and result existed from our previous call to get_pointer(), since this space was no longer being used. Hence they both override the stale data that was previously stored there, with their new values
Lines 23 and 24 print out the memory addresses of the result and other variables as strings to the terminal
Line 25 returns the value of the calculation result + other to the caller in main()
At step 8b, the get_value() function has returned and it's return value assigned to other, then at step 8c the function memory space on the stack is freed and available for other code to use. Once again, it so happens that data is left in place in these unallocated memory areas however, even though this data is no longer relevant with respect to the running program. In this case the pointer variable p has been left pointing to an area of memory that is not in use, but happens to contain the value '20'
Finally at line 9, the program prints the dereferenced value of p which will contain the value '20' from the stale area of memory to which the dangling pointer p is pointing. The program counter increments to line 10, and the program ends