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
- In C++, we use keywords to allocate & free memory:
- Single items:
- Many items:
- You have to delete the memory that you allocate.
- Don’t delete it more than once!
Scalar Example
int *p = new int;
*p = 42;
cout << p << ' ' << *p << '\n';
delete p;
0x109b2b0 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,
- For each call to new, call delete.
Similarly,
new[]
& delete[]
.
- What happens if you call the wrong one? Undefined behavior.
Anything could happen.
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; // ☠☠☠
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.
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';
0x24de2b0: ⁇⁇
0x24de2d0: 42
0x24de2f0: ⁇⁇
0x24de310: 11 22
- Scalars aren’t initialized, unless you initialize them.
Objects have ctors.
- The OS might clear the memory when it’s allocated for you,
to stop you from seeing others’ data. This is not guaranteed,
and won’t happen after you delete and then re-allocate the
same chunk of memory later in your program.
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::__cxx11::string*' {aka
'std::__cxx11::basic_string<char>*'} to non-scalar type
'std::__cxx11::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'
Every call to new must be matched by exactly one delete.
Not zero, and not two. One.
Similarly, every call to new[]
must be matched
by exactly one 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.
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
- Sure, it would be possible to implement new & delete
so that they detect a double delete, and respond with a sensible
error message.
- That would cost time, space, or probably both.
- Why should my clean code be slowed down to catch your errors?
Dangling Pointers
- After a pointer is deleted, its value (address) is, theoretically,
indeterminate. It is not automatically assigned nullptr,
though it could be.
Accessing the value invokes undefined behavior. ☠️
- In nearly all implementations, the address is simply left unchanged.
- After a pointer is deleted, the pointer is “stale” or “dangling”.
Even though you still have a pointer to the data, you’re not
allowed to access the data. It’s not yours!
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
8.76372e-42
56.78
Magical Thinking
- Students will often argue the previous point.
- “The pointer won’t point to the old location;
it’ll point nowhere”, they patiently explain.
- They think that, somehow, the pointer no longer contains
an address—that it doesn’t contain bits that form an address.
- How would that work? It’s still 64 (say) bits,
each a 0 or a 1. It’ll make an address.
- Or, they think that the pointer will automatically become a nullptr.
- Do you think that the compiler will produce extra instructions
to clear out a pointer that you’re not suppose to use, anyway?
- Would you wash a pair of old socks before you throw them out?
🧦
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
- C++ has no garbage collection, and so has no built-in reference counting.
- This is bad code. Bad!
string *smith = new string("Brush your teeth twice a day!");
string *jones = smith; // a shallow copy
delete smith;
cout << *jones << '\n';