Skip to Content
CSE332SObject-Oriented Programming Lab (Lecture 13)

CSE332S Object-Oriented Programming in C++ (Lecture 13)

Memory layout of a C++ program, variables and their lifetimes

C++ Memory Overview

4 major memory segments

  • Global: variables outside stack, heap
  • Code (a.k.a. text): the compiled program
  • Heap: dynamically allocated variables
  • Stack: parameters, automatic and temporary variables (all the variables that are declared inside a function, managed by the compiler, so must be fixed size)
  • For the dynamically allocated variables, they will be allocated in the heap segment, but the pointer (fixed size) to them will be stored in the stack segment.

Key differences from Java

  • Destructors of automatic variables called when stack frame where declared pops
  • No garbage collection: program must explicitly free dynamic memory

Heap and stack use varies dynamically

Code and global use is fixed

Code segment is “read-only”

int g_default_value = 1; int main (int argc, char **argv) { Foo *f = new Foo; f->setValue(g_default_value); delete f; // programmer must explicitly free dynamic memory return 0; } void Foo::setValue(int v) { this->m_value = v; }

Image of memory layout

Memory, Lifetimes, and Scopes

Temporary variables

  • Are scoped to an expression, e.g., a = b + 3 * c;

Automatic (stack) variables

  • Are scoped to the duration of the function in which they are declared

Dynamically allocated variables

  • Are scoped from explicit creation (new) to explicit destruction (delete)

Global variables

  • Are scoped to the entire lifetime of the program
  • Includes static class and namespace members
  • May still have initialization ordering issues

Member variables

  • Are scoped to the lifetime of the object within which they reside
  • Depends on whether object is temporary, automatic, dynamic, or global

Lifetime of a pointer/reference can differ from the lifetime of the location to which it points/refers

Direct Dynamic Memory Allocation and Deallocation

#include <iostream> using namespace std; int main (int, char *[]) { int * i = new int; // any of these can throw bad_alloc int * j = new int(3); int * k = new int[*j]; int * l = new int[*j]; for (int m = 0; m < *j; ++m) { // fill the array with loop l[m] = m; } delete i; // call int destructor delete j; // single destructor call delete [] k; // call int destructor for each element delete [] l; return 0; }

Issues with direct memory management

A Basic Issue: Multiple Aliasing

int main (int argc, char **argv) { Foo f; Foo *p = &f; Foo &r = f; delete p; return 0; }

Multiple aliases for same object

  • f is a simple alias, the object itself
  • p is a variable holding a pointer
  • r is a variable holding a reference

What happens when we call delete on p?

  • Destroy a stack variable (may get a bus error there if we’re lucky)
  • If not, we may crash in destructor of f at function exit
  • Or worse, a local stack corruption that may lead to problems later

Problem: object destroyed but another alias to it was then used (dangling pointer issue)

Memory Lifetime Errors

Foo *bad() { Foo f; return &f; // return address of local variable, f is destroyed after function returns } Foo &alsoBad() { Foo f; return f; // return reference to local variable, f is destroyed after function returns } Foo mediocre() { Foo f; return f; // return copy of local variable, f is destroyed after function returns, danger when f is a large object } Foo * good() { Foo *f = new Foo; return f; // return pointer to local variable, with new we can return a pointer to a dynamically allocated object, but we must remember to delete it later } int main() { Foo *f = &mediocre(); // f is a pointer to a temporary object, which is destroyed after function returns, f is invalid after function returns cout << good()->value() << endl; // good() returns a pointer to a dynamically allocated object, but we did not store the pointer, so it will be lost after function returns, making it impossible to delete it later. return 0; }

Automatic variables

  • Are destroyed on function return
  • But in bad, we return a pointer to a variable that no longer exists
  • Reference from also_bad similar
  • Like an un-initialized pointer

What if we returned a copy?

  • Ok, we avoid the bad pointer, and end up with an actual object
  • But we do twice the work (why?)
  • And, it’s a temporary variable (more on this next)

We really want dynamic allocation here

Dynamically allocated variables

  • Are not garbage collected
  • But are lost if no one refers to them: called a “memory leak

Temporary variables

  • Are destroyed at end of statement
  • Similar to problems w/ automatics

Can you spot 2 problems?

  • One with a temporary variable
  • One with dynamic allocation

Double Deletion Errors

int main (int argc, char **argv) { Foo *f = new Foo; delete f; // ... do other stuff delete f; // will throw an error because f is already deleted return 0; }

What could be at this location?

  • Another heap variable
  • Could corrupt heap

Shared pointers and the RAII idiom

A safer approach using smart pointers

C++11 provides two key dynamic allocation features

  • shared_ptr : a reference counted pointer template to alias and manage objects allocated in dynamic memory (we’ll mostly use the shared_ptr smart pointer in this course)
  • make_shared : a function template that dynamically allocates and value initializes an object and then returns a shared pointer to it (hiding the object’s address, for safety)

C++11 provides 2 other smart pointers as well

  • unique_ptr : a more complex but potentially very efficient way to transfer ownership of dynamic memory safely (implements C++11 “move semantics”)
  • weak_ptr : gives access to a resource that is guarded by a shared_ptr without increasing reference count (can be used to prevent memory leaks due to circular references)

Resource Acquisition Is Initialization (RAII)

Also referred to as the “Guard Idiom”

  • However, the term “RAII” is more widely used for C++

Relies on the fact that in C++ a stack object’s destructor is called when stack frame pops

Idea: we can use a stack object (usually a smart pointer) to hold the ownership of a heap object, or any other resource that requires explicit clean up

  • Immediately initialize stack object with the allocated resource
  • De-allocate resource in the stack object’s destructor

Example: Resource Acquisition Is Initialization (RAII)

shared_ptr<Foo> createAndInit() { shared_ptr<Foo> p = make_shared<Foo> (); init(p);// may throw exception return p; } int run () { try { shared_ptr<Foo> spf = createAndInit(); cout <<*spf is ” << *spf; } catch (...) { return -1 } return 0; }

RAII idiom example using shared_ptr

#include <memory> using namespace std;
  • shared_ptr<X> assumes and maintains ownership of aliased X
  • Can access the aliased X through it (*spf)
  • shared_ptr<X> destructor calls delete on address of owned X when it’s safe to do so (per reference counting idiom discussed next)
  • Combines well with other memory idioms

Reference Counting

Basic Problem

  • Resource sharing is often more efficient than copying
  • But it’s hard to tell when all are done using a resource
  • Must avoid early deletion
  • Must avoid leaks (non-deletion)

Solution Approach

  • Share both the resource and a counter for references to it
  • Each new reference increments the counter
  • When a reference is done, it decrements the counter
  • If count drops to zero, also deletes resource and counter
  • “last one out shuts off the lights”

Reference Counting Example

shared_ptr<Foo> createAndInit() { shared_ptr<Foo> p = make_shared<Foo> (); init(p);// may throw exception return p; } int run () { try { shared_ptr<Foo> spf = createAndInit(); shared_ptr<Foo> spf2 = spf; // object destroyed after // both spf and spf2 go away } catch (...) { return -1 } return 0; }

Again starts with RAII idiom via shared_ptr

  • spf initially has sole ownership of aliased X
  • spf.unique() would return true
  • spf.use_count would return 1

shared_ptr<X> copy constructor increases count, and its destructor decreases count

shared_ptr<X> destructor calls delete on the pointer to the owned X when count drops to 0

Last updated on