upgrade structures and migrate to nextra v4
This commit is contained in:
309
content/CSE332S/CSE332S_L13.md
Normal file
309
content/CSE332S/CSE332S_L13.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# CSE332S 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"
|
||||
|
||||
```cpp
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
|
||||
```cpp
|
||||
#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
|
||||
|
||||
```cpp
|
||||
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
|
||||
|
||||
```cpp
|
||||
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
|
||||
|
||||
```cpp
|
||||
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)
|
||||
|
||||
```cpp
|
||||
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
|
||||
|
||||
```cpp
|
||||
#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
|
||||
|
||||
```cpp
|
||||
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
|
||||
Reference in New Issue
Block a user