Dangling Pointers and References

Learn about an important consideration when returning pointers and references from functions
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

Now that we’re working with pointers and references, we need to talk briefly about memory management.

This is a large topic - we have a full chapter dedicated to it in the next course. For now, we just need to understand an important consideration when we’re using references or pointers to objects created in our function.

Let's demonstrate the problem with a simple program:

#include <iostream>
using namespace std;

int* GetNumber(){
  int Number{1};
  return &Number;
}

int main(){
  int* Result{GetNumber()};
  cout << "Result: " << *Result;
}

We might expect this to log out 1, but that is not the case:

Result: -858993460

The compiler should give us a warning that we might have been doing something wrong on our return statement:

Address of stack memory associated with local variable Number returned

Let's delve a little deeper into this warning, as we should understand what’s going on here.

Stack Memory

In the introductory lessons, we covered the idea of the stack, and how our functions each create stack frames.

Stack frames are a form of memory management.

The parameters and local variables that our functions create are stored within these stack frames.

Within our call to GetNumber(), we are allocated memory to store the int we created. We then returned that memory address to our main function:

Diagram showing the return of a pointer from a function

However, stack frames are an automated form of memory management. When the function ends, its stack frame, and all the objects stored within it are destroyed.

We can see this by using a custom type with a destructor:

#include <iostream>
using namespace std;

struct SomeType {
  int Value{1};
  ~SomeType(){ cout << "Destroying\n"; }
};

SomeType* GetObject(){
  SomeType SomeObject;
  return &SomeObject;
}

int main(){
  SomeType* Result{GetObject()};
  cout << "Result: " << Result->Value;
}
Destroying
Result: -858993460

Dangling Pointers and References

When we have a pointer or reference to an object, and that object is then deleted, the pointer is no longer useful. It is sometimes referred to as a dangling pointer or dangling reference.

If we attempt to use a dangling pointer or reference, our program's behavior becomes undefined. This often leads to unpredictable results, and may cause crashes or other serious issues.

This is because the memory our object was using is freed up for other purposes, meaning we no longer know what will be stored there by the time we use our pointer or reference.

Diagram showing a dangling pointer

Why this only applies to references and pointers

Previously, we've been creating and returning local variables by value without issues. When we return a value from a function, that value is typically copied or moved to the calling function's stack frame or another appropriate location.

When the thing we returned was the value, everything worked as expected.

However, when a pointer (or reference) is returned, that pointer can refer to a memory location that is about to be freed. When that pointer is then used, unpredictable things can happen, as the first example demonstrated.

Storage Durations

In C++, objects have different lifetimes depending on how they're created. This concept is known as storage duration. The local variables we've been using in functions have what's called automatic storage duration - they're automatically created when the function is called and destroyed when it ends.

However, there are other storage durations in C++:

  1. Static storage duration: Objects exist for the entire run of the program.
  2. Thread storage duration: Each thread has its own copy of the object.
  3. Dynamic storage duration: The programmer controls when objects are created and destroyed.

For now, we're primarily concerned with automatic storage duration and its implications. When we need an object to last longer than a single function call, we have several options:

  • Create it in the global scope (though this should be used sparingly)
  • Create it inside the main function
  • Make it part of a longer-lived object

In the next course, we'll delve deeper into memory management and explore how to control object lifetimes more precisely. This will include techniques for dynamic allocation and smart pointers, which give us more flexibility in managing object lifetimes.

Next Lesson: Memory Ownership and Smart Pointers

In the next lesson, we introduce the main design pattern used to manage memory in complex applications. We also introduce smart pointers, the main mechanism used to implement this pattern. We cover:

  1. Memory Management Simplified: We explore how smart pointers automate memory management, thereby reducing the chances of memory-related errors.
  2. Understanding Memory Ownership: What smart pointers are and how they implement an ownership model for objects in dynamically allocated memory
  3. Creating Unique Pointers using std::make_unique(): How to create unique pointers using std::make_unique(),
  4. Access and Ownership Transfer: How to give other functions access to our resources, and whether we want to maintain or transfer ownership at the same time.

Was this lesson useful?

Next Lesson

Memory Ownership and Smart Pointers

Learn how to manage dynamic memory using unique pointers and the concept of memory ownership
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Dangling Pointers and References

Learn about an important consideration when returning pointers and references from functions

3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Memory, References and Pointers
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

  • 57 Lessons
  • Over 200 Quiz Questions
  • 95% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Memory Ownership and Smart Pointers

Learn how to manage dynamic memory using unique pointers and the concept of memory ownership
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved