CS253: Software Development with C++

Spring 2022

Dynamic Memory

Show Lecture.DynamicMemory as a slide show.

CS253 Dynamic Memory

The old C way

In C, we used functions to allocate & free memory:

They still work, but don’t use them. They’re not type-safe, and they don’t call ctors & dtors (constructors and destructors).

The new C++ way

Scalar Example

int *p = new int;
*p = 42;
cout << p << ' ' << *p << '\n';
delete p;
0x11f42b0 42

Array Example

int *a = new int[10];
for (int i=0; i<10; i++)
    a[i] = i*11;

for (int i=0; i<10; i++)
    cout << a[i] << ',';

delete[] a;         // Note the []
0,11,22,33,44,55,66,77,88,99,

new delete, new[] delete[]

new delete, new[] delete[]

auto *p = new double[3];
delete p;    // 🦡
auto *q = new double;
delete[] q;  // 🦡
cout << "Got away with it!\n";
Got away with it!
auto *r = new string[3];
delete r;    // 🦡
c.cc:2: warning: ‘void operator delete(void*, std::size_t)’ called on 
   pointer ‘<unknown>’ with nonzero offset 8
c.cc:1: note: returned from ‘void* operator new [](std::size_t)’
munmap_chunk(): invalid pointer
SIGABRT: Aborted
auto *s = new string;
delete[] s;  // 🦡
free(): invalid pointer
SIGABRT: Aborted

Perhaps this is because string has a destructor, to release dynamic memory, whereas double doesn’t. Don’t count on it.

How not to do it

int *p;
cout << "I don’t have much faith in this." << endl;
*p = 42;  // 🦡 Wait--were is p pointing to!?
c.cc:3: warning: ‘p’ is used uninitialized
I don’t have much faith in this.
SIGSEGV: Segmentation fault

Not initialized

auto *a = new short,
     *b = new short(42),   // or short{42}
     *c = new short[10],
     *d = new short[2]{11,22};
cout << a << ": ⁇⁇\n"
     << b << ": " << *b << "\n"
     << c << ": ⁇⁇\n"
     << d << ": " << d[0] << ' ' << d[1] << '\n';
0x116e2b0: ⁇⁇
0x116e2d0: 42
0x116e2f0: ⁇⁇
0x116e310: 11 22

Java Thinking

Java programmers, remember that objects do not have to be dynamically allocated. You can, but you don’t have to.

string s = new string;  // 🦡
c.cc:1: error: conversion from ‘std::string*’ {aka 
   ‘std::__cxx11::basic_string<char>*’} to non-scalar type 
   ‘std::string’ {aka ‘std::__cxx11::basic_string<char>’} requested

Instead, just declare the string:

string s = "Hi there\n";
cout << s;
Hi there

Sure, the string allocates dynamic memory, internally, but that’s none of your business.

Avoid all of this

In general, use standard containers such as string, vector, or list when you can. They handle the dynamic memory allocation, so you don’t have to.

Besides, code that deals with dynamic memory is easy to get wrong. Code that you write will have bugs. vector, on the other hand, had its bugs discovered & fixed long ago.

If you must use dynamic memory, then consider unique_ptr and shared_ptr.

A shortcut that will bite you

An array with a non-constant size is not allowed in C++, though some compilers with some settings (g++ without -Wpedantic) allow it as a non-portable extension.

int n = 42;
int data[n];  // 🦡
return data[0];
c.cc:2: warning: ISO C++ forbids variable length array ‘data’
c.cc:3: warning: ‘*data[0]’ is used uninitialized

Double delete

Every call to new must be matched (later) by exactly one delete.

Not zero, and not two. One.

Similarly, every call to new[] must be matched by exactly one delete[].

Zero delete

If you don’t call delete, then the memory is forgotten. We call this a memory leak.

Sure, the memory will be implicitly freed when the program ends. However, some programs run for a good long time before they end. This program allocates memory every second, and forgets to free it.

// Clock program
draw_clock_face();
while (sleep(1)) {
    GraphicsContext *gc = new GraphicsContext;
    gc->redraw();
    // forget to free GraphicsContext
}

Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip. Drip.

Multiple delete

What happens if you call delete more than once? That’s undefined behavior. Let’s see what it does on this computer:

float *p = new float[100];
delete[] p;
delete[] p;  // 🦡
free(): double free detected in tcache 2
SIGABRT: Aborted

Pick Up Your Own Trash

Dangling Pointers

auto laurel = new float(12.34);  // hey, no *!
cout << *laurel << '\n';         // should be 12.34
delete laurel;
cout << *laurel << '\n';         // 🦡 value is unknown

auto hardy = new float(56.78);   // will probably re-use space
cout << *laurel << '\n';         // 🦡 most likely 56.78 for laurel
delete hardy;
12.34
6.42215e-42
56.78

Magical Thinking

Another way to produce a dangling pointer

Don’t return a pointer to something that will soon go away.

// Return a cheery message:
const char *message() {
    char buf[] = "Hello there, folks!\n";
    const char *p = buf;
    return p;  // 🦡
}

int main() {
    cout << "I say: " << message();  // Why doesn’t this work!?
    return 0;
}

No Reference Counting

string *puppycorn = new string("Unikitty!");
string *dr_fox = puppycorn;  // a shallow copy
cout << *puppycorn << endl;
delete puppycorn;            // the one & only copy is gone
cout << *dr_fox << '\n';     // 🦡
Unikitty!
SIGSEGV: Segmentation fault