Reflection on Scope Rules and Dangling Pointer
Reflection on pointer reassignment bug. Related with Scope Rules in C++, Dangling Pointer concept and sizeof() / size() function.
The Root Error
1 |
|
When I compiled the code and make it run, bugs emerges like below:
1 | PureCppExercise % ./ResizeArray |
There are at 3 issues
- Failure in malloc operation
*** error for object 0x6000029cc000: pointer being freed was not allocated - The
arraySizecalculation is improper. - Junction values showed in output
Related concept and mechanism questions I had
- Scope Rules in C++ from compiler’s POV
- Dangling Pointer
- The difference between destroying a pointer and destroying the memory block a pointer points to.
- The calculation method variation on Fixed Array and Dynamic Array
- Why
sizeof(arrayVariableName)/sizeof(arrayVariableName[0])works for fixed-size arrays but not for dynamic arrays - Why the size information of dynamic arrays is lost at runtime
- Why
sizeof()behaves differently for fixed-size arrays and dynamic arrays
- Why
Outside Scope Issue
1 | delete[] numbers; |
It led to a change in its scope.
- I redefined a
numbersin those 3 lines. It is impossible to free this inner one. - The redefined
numberspointer(inner one) shadows the outernumbers. - When the inner
numbersactive, the outer one still alive, but hide away. - The inner one binding with
tempwill die outside of if condition. - When
ifblock finishes execution (theifblock inside thewhileloop), there is still annumbers. (Original one but had been freed already insideifblock) - The deallocation error is due to
deleteoperator couldn’t free a memory block that never be allocated : Both original memory block outernumberspoints to AND memory block innernumberspoints to don’t exist anymore.- The memory block that original outer pointer pointed to had been freed inside the
ifblock, it was bind with a memory without allocation, it became dangling pointer. When we execute the lastdeleteoperation narrowly beforereturn 0;, the address it points to & the memory it bind with has no allocation. - Inner
numberspointer is a local pointer variable toifblock, it has already died after execution ofifcondition. When we execute the lastdeleteoperation narrowly beforereturn 0;, there is no innernumbersanymore.
- The memory block that original outer pointer pointed to had been freed inside the
Since the lifetime of a pointer variable depends on its scope, I should just avoid redefining the numbers pointer. When I delete[] numbers, only the original memory block numbers pointed to was being freed, original numbers pointer itself was still alive. I don’t need to redefine an existing pointer.
The outer numbers pointer is local variable of main() function, which means it is the so-called “global variable” to if block.
So, fix int* numbers = &(temp[0]) by numbers = temp[0] or numbers = temp.
Scope Rules in C++ (Shadowing Mechanics)
In C++, scopes are nested. When we define a variable with the same name in a nested scope, this inner variable shadows the outer variable. This means that within the nested scope, any reference to the variable name will refer to the inner variable, not the outer one.
Compiler’s Actions
- Variable Definition:
- The compiler records each variable’s definition location and scope.
- When entering the
ifblock, it records a new definition fornumberswith a scope limited to theifblock.
- Variable Reference:
- Within the
ifblock, any reference tonumbersis resolved to the inner scope’s variable. - Outside the
ifblock, references tonumbersresolve to the outer scope’s variable.
- Within the
- Scope End:
- When the
ifblock ends, the inner scope’snumbersis destroyed, but the outer scope’snumbersremains.
- When the
Size Calculation Variation
I got 2 from the calculation of a 5-size dynamic array, which is very weird.
Why sizeof() works for fixed-size arrays but not for dynamic arrays
For fixed-size arrays (e.g.,
int numbers[5];),sizeof(arrayVariableName)gives the total size of the array in bytes, andsizeof(arrayVariableName[0])gives the size of one element in the array. By dividing the total size by the size of one element, we get the number of elements in the array. This works because the size of fixed-size arrays is determined at compile time, and the compiler knows the total size of the array.For dynamic arrays (e.g.,
int* numbers = new int[5];),numbersis a pointer, andsizeof(numbers)returns the size of the pointer itself (usually 4 or 8 bytes), not the size of the array it points to. Therefore,sizeof(numbers)/sizeof(numbers[0])does not correctly calculate the size of the array.
Why the size information of dynamic arrays is lost at runtime
- Because when we allocate a dynamic array using
new, we get a pointe to the first element of the array as return. - This pointer does not store any information about the size of the array.
- Therefore, once the array is allocated, its size information is no longer tracked unless we manually save this information.
- In brief, it’s due to the feature of
newoperator that returns only the first element’s address!
Why sizeof() behaves differently for fixed-size arrays and dynamic arrays
My question: I thought both fixed-size arrays and dynamic arrays are indeed treated as pointers, but why sizeof() function returns different value when they are same size?
From the compiler’s perspective, both fixed-size arrays and dynamic arrays are indeed treated as pointers in many contexts, but their behavior and semantics differ. For fixed-size arrays, the array name typically decays to a pointer to the first element of the array in most contexts, but in the sizeof operation, the array name does not decay to a pointer. Instead, it represents the entire array. Therefore, sizeof(arrayVariableName) gives the size of the entire array.
For dynamic arrays, numbers is a pointer, and sizeof(numbers) returns the size of the pointer itself, not the size of the array it points to. This is because the size information of dynamic arrays is lost at runtime, and the compiler cannot know the actual size of the array that the pointer points to.
Pointer Persistence v.s. Memory Deallocation
Deleting the memory block a pointer refers to (using delete or delete[]) does not destroy the pointer variable itself. The pointer still exists, but the memory it points to has been deallocated. Accessing this memory after it’s been freed results in undefined behaviour.
Lifetime of the pointer variable:
- The lifetime of a pointer variable depends on its scope.
- If it is a local variable, it will be destroyed when it goes out of scope.
- If it is a global variable or a member variable of a class, its lifetime is determined by the lifetime of its enclosing context.
SOP for pointer working with dynamic allocated stuff on heap
newoperator to allocate the momory, bind the address with pointer variable- ……
delete+pointer_variable_nameto deallocate the memory pointer points to- Assign
nullptrto the pointer varaible for avoiding dangling pointer.
Dangling Pointer
A dangling pointer refers to a pointer that continues to reference a memory location that has already been deallocated or no longer valid.
- Memory Deallocation: When the memory that a pointer points to is freed using
delete(in C++) orfree(in C), the pointer still holds the address of the freed memory. MY SITUATION - Local Variable Out of Scope: When a pointer points to a local variable, and the scope of that local variable ends, the pointer becomes dangling.
- Returning Address of Local Variable: When a function returns the address of a local variable, the local variable is destroyed when the function ends, making the returned pointer invalid
The dangling pointer in my program led to deallocation failure crash.
Why Garbage Values
Although I avoid using the arraySize by substituting it with 10, the for loop still uses arraySize as edging case. (Which is incorrect calculation resulted by misunderstanding of sizeof() function). Using 2 means the rest of 10-size temp will be filled in by garbage values automatically.
I had already use the correct way to track the size of dynamic array, but I didn’t realised its function. The entries is the actual size I should use for edge case of for loop.
Final Fix
1 |
|
- Use
capbilityto give full access to infinite dynamic allocation and avoiding magic number. - Fix the incorrect logic to update
numberspointer and its deallocation process. - Avoid using
sizeof()function to track the size of dynamic allocated array.
- Title: Reflection on Scope Rules and Dangling Pointer
- Author: Ricardo Pu
- Created at : 2025-03-26 17:15:31
- Updated at : 2025-04-12 21:23:41
- Link: https://ricardopotter.github.io/RicardoBlog/2025/03/26/Reflection-on-Scope-Rules-and-Dangling-Pointer/
- License: This work is licensed under CC BY-NC-SA 4.0.