Lecture 7
Lecture 7
This document is copyright (C) Stanford Computer Science and Nick Troccoli, licensed under Creative Commons Attribution 2.5 License. All rights reserved.
Based on slides created by Marty Stepp, Cynthia Lee, Chris Gregg, Lisa Yan, Jerry Cain and others.
1
Lecture Plan
• The Stack
• The Heap and Dynamic Memory
• Practice: Pig Latin
• realloc
• Practice: Pig Latin Part 2
cp -r /afs/ir/class/cs107/lecture-code/lect7 .
2
Lecture Plan
• The Stack
• The Heap and Dynamic Memory
• Practice: Pig Latin
• realloc
• Practice: Pig Latin Part 2
3
Memory Layout
• We are going to dive deeper into different areas of
memory used by our programs.
• The stack is the place where all local variables and
parameters live for each function. A function’s stack
“frame” goes away when the function returns.
• The stack grows downwards when a new function is
called and shrinks upwards when the function is
finished.
4
The Stack
Memory
void func2() {
int d = 0;
} main
5
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
6
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
7
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
8
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
9
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
10
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
11
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
12
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
13
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
14
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
15
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
16
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
17
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
18
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
19
0x0
The Stack
Memory
void func2() {
int d = 0;
} main
20
0x0
The Stack
Memory
void func2() {
int d = 0;
}
void func1() {
int c = 99;
func2();
}
21
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else {
return n * factorial(n – 1);
}
}
22
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else {
return n * factorial(n – 1);
}
}
23
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
}
24
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
}
25
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4));
return 0;
}
26
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4));
return 0;
}
27
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4)); factorial
return 0; n: 2
}
28
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4)); factorial
return 0; n: 2
}
29
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4)); factorial
return 0; n: 2
}
factorial
n: 1
30
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4)); factorial
return 0; n: 2
}
Returns 1 factorial
n: 1
31
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4)); Returns 2 factorial
return 0; n: 2
}
32
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); n: 4
}
} Returns 6 factorial
n: 3
int main(int argc, char *argv[]) {
printf("%d", factorial(4));
return 0;
}
33
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else { factorial
return n * factorial(n – 1); Returns 24
n: 4
}
}
34
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else {
return n * factorial(n – 1);
}
}
35
0x0
The Stack
Memory
Each function call has its own stack frame for its own copy of
variables.
main
int factorial(int n) { argc:
1
if (n == 1) { Stack
return 1; argv: 0xfff0
} else {
return n * factorial(n – 1);
}
}
36
0x0
The Stack
• The stack behaves like a…well…stack! A new function call pushes on a new
frame. A completed function call pops off the most recent frame.
• Interesting fact: C does not clear out memory when a function’s frame is
removed. Instead, it just marks that memory as usable for the next function
call. This is more efficient!
• A stack overflow is when you use up all stack memory. E.g. a recursive call
with too many function calls.
• What are the limitations of the stack?
37
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str;
}
38
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str;
}
39
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str; create_string
ch: 'a' num: 4
}
40
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str; create_string
ch: 'a' num: 4
}
41
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str; create_string
ch: 'a' num: 4
}
42
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str; create_string
ch: 'a' num: 4
}
43
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str: 0xff50
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str; create_string
ch: 'a' num: 4
}
44
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str: 0xff50
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str;
}
45
0x0
The Stack
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str: 0xff50
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str;
}
47
0x0
Stacked Against Us
48
Lecture Plan
• The Stack
• The Heap and Dynamic Memory
• Practice: Pig Latin
• realloc
• Practice: Pig Latin Part 2
49
The Heap
Memory
char *create_string(char ch, int num) {
char new_str[num + 1];
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str; create_string
ch: 'a' num: 4
}
• This function returns a pointer to the starting address of the new memory. It
doesn’t know or care whether it will be used as an array, a single block of
memory, etc.
• void *means a pointer to generic memory. You can set another pointer
equal to it without any casting.
• The memory is not cleared out before being allocated to you!
• If malloc returns NULL, then there wasn’t enough memory for this request.
53
The Heap
Memory
char *create_string(char ch, int num) {
char *new_str = malloc(sizeof(char) * (num + 1));
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str; create_string
ch: 'a' num: 4
}
new_str: 0xed0
int main(int argc, char *argv[]) {
char *str = create_string('a', 4);
printf("%s", str); // want "aaaa"
'\0'
return 0;
Heap 'a'
}
'a'
'a'
'a'
54
0x0
The Heap
Memory
char *create_string(char ch, int num) {
char *new_str = malloc(sizeof(char) * (num + 1));
for (int i = 0; i < num; i++) { main
argc: str:
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0'; Returns e.g. 0xed0
return new_str; create_string
ch: 'a' num: 4
}
new_str: 0xed0
int main(int argc, char *argv[]) {
char *str = create_string('a', 4);
printf("%s", str); // want "aaaa"
'\0'
return 0;
Heap 'a'
}
'a'
'a'
'a'
55
0x0
The Heap
Memory
char *create_string(char ch, int num) {
char *new_str = malloc(sizeof(char) * (num + 1));
for (int i = 0; i < num; i++) { main
argc: str: 0xed0
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0'; Returns e.g. 0xed0
return new_str; create_string
ch: 'a' num: 4
}
new_str: 0xed0
int main(int argc, char *argv[]) {
char *str = create_string('a', 4);
printf("%s", str); // want "aaaa"
'\0'
return 0;
Heap 'a'
}
'a'
'a'
'a'
56
0x0
The Heap
Memory
char *create_string(char ch, int num) {
char *new_str = malloc(sizeof(char) * (num + 1));
for (int i = 0; i < num; i++) { main
argc: str: 0xed0
new_str[i] = ch; 1
Stack
} argv: 0xfff0
new_str[num] = '\0';
return new_str;
}
• If an allocation error occurs (e.g. out of heap memory!), malloc will return
NULL. This is an important case to check for robustness.
• assert will crash the program if the provided condition is false. A memory
allocation error is significant, and we should terminate the program.
62
Other heap allocations: calloc
void *calloc(size_t nmemb, size_t size);
calloc is like malloc that zeros out the memory for you—thanks, calloc!
• You might notice its interface is also a little different—it takes two parameters,
which are multiplied to calculate the number of bytes (nmemb * size).
// allocate and zero 20 ints
int *scores = calloc(20, sizeof(int));
// alternate (but slower)
int *scores = malloc(20 * sizeof(int));
for (int i = 0; i < 20; i++) scores[i] = 0;
• calloc is more expensive than malloc because it zeros out memory. Use only
when necessary!
63
Other heap allocations: strdup
char *strdup(char *s);
strdup is a convenience function that returns a null-terminated, heap-
allocated string with the provided text, instead of you having to malloc and
copy in the string yourself.
64
Cleaning Up with free
void free(void *ptr);
• If we allocated memory on the heap and no longer need it, it is our
responsibility to delete it.
• To do this, use the free command and pass in the starting address on the
heap for the memory you no longer need.
• Example:
char *bytes = malloc(4);
…
free(bytes);
65
free details
Even if you have multiple pointers to the You must free the address you
same block of memory, each memory received in the previous allocation
block should only be freed once. call; you cannot free just part of a
previous allocation.
67
Memory Leaks
• A memory leak is when you allocate memory on the heap, but do not free it.
• Your program should be responsible for cleaning up any memory it allocates
but no longer needs.
• If you never free any memory and allocate an extremely large amount, you
may run out of memory in the heap!
However, memory leaks rarely (if ever) cause crashes.
• We recommend not to worry about freeing memory until your program is
written. Then, go back and free memory as appropriate.
• Valgrind is a very helpful tool for finding memory leaks!
68
Lecture Plan
• The Stack
• The Heap and Dynamic Memory
• Practice: Pig Latin
• realloc
• Practice: Pig Latin Part 2
69
Demo: Pig Latin
pig_latin.c 70
Lecture Plan
• The Stack
• The Heap and Dynamic Memory
• Practice: Pig Latin
• realloc
• Practice: Pig Latin Part 2
71
realloc
void *realloc(void *ptr, size_t size);
• The realloc function takes an existing allocation pointer and enlarges to a new
requested size. It returns the new pointer.
• If there is enough space after the existing memory block on the heap for the
new size, realloc simply adds that space to the allocation.
• If there is not enough space, realloc moves the memory to a larger location,
frees the old memory for you, and returns a pointer to the new location.
72
realloc
char *str = strdup("Hello");
assert(str != NULL);
…
strcat(str, addition);
printf("%s", str);
free(str);
73
realloc
• realloc only accepts pointers that were previously returned my malloc/etc.
• Make sure to not pass pointers to the middle of heap-allocated memory.
• Make sure to not pass pointers to stack memory.
74
Cleaning Up with free and realloc
You only need to free the new memory coming out of realloc—the previous
(smaller) one was already reclaimed by realloc.
char *str = strdup("Hello");
assert(str != NULL);
…
// want to make str longer to hold "Hello world!"
char *addition = " world!";
str = realloc(str, strlen(str) + strlen(addition) + 1);
assert(str != NULL);
strcat(str, addition);
printf("%s", str);
free(str);
75
Heap allocator analogy: A hotel
Request memory by size (malloc)
• Receive room key to first of connecting rooms
Need more room? (realloc)
• Extend into connecting room if available
• If not, trade for new digs, employee moves your
stuff for you
Check out when done (free)
• You remember your room number though
Errors! What happens if you…
• Forget to check out?
• Bust through connecting door to neighbor? What
if the room is in use? Yikes…
• Return to room after checkout?
76
Lecture Plan
• The Stack
• The Heap and Dynamic Memory
• Practice: Pig Latin
• realloc
• Practice: Pig Latin Part 2
77
Demo: Pig Latin Part 2
pig_latin.c 78
Heap allocation interface: A summary
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
char *strdup(char *s);
void free(void *ptr);
Compare and contrast the heap memory functions we’ve learned about.
🤔
79
Heap allocation interface: A summary
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
char *strdup(char *s);
void free(void *ptr);
Heap memory allocation guarantee: Undefined behavior occurs:
• NULL on failure, so check with assert • If you overflow (i.e., you access
• Memory is contiguous; it is not recycled beyond bytes allocated)
unless you call free • If you use after free, or if free
• realloc preserves existing data is called twice on a location.
• calloc zero-initializes bytes, malloc • If you realloc/free non-heap
and realloc do not address
80
Engineering principles: stack vs heap
Stack (“local variables”) Heap (dynamic memory)
• Fast
Fast to allocate/deallocate; okay to oversize
• Convenient.
Automatic allocation/ deallocation;
declare/initialize in one step
• Reasonable type safety
Thanks to the compiler
⚠ Not especially plentiful
Total stack size fixed, default 8MB
⚠ Somewhat inflexible
Cannot add/resize at runtime, scope
dictated by control flow in/out of functions
81
Engineering principles: stack vs heap
Stack (“local variables”) Heap (dynamic memory)
• Fast • Plentiful.
Fast to allocate/deallocate; okay to oversize Can provide more memory on demand!
• Convenient. • Very flexible.
Automatic allocation/ deallocation; Runtime decisions about how much/when to
declare/initialize in one step allocate, can resize easily with realloc
• Reasonable type safety • Scope under programmer control
Thanks to the compiler Can precisely determine lifetime
⚠ Not especially plentiful ⚠ Lots of opportunity for error
Total stack size fixed, default 8MB Low type safety, forget to allocate/free
before done, allocate wrong size, etc.,
⚠ Somewhat inflexible Memory leaks (much less critical)
Cannot add/resize at runtime, scope
dictated by control flow in/out of functions
82
Stack and Heap
• Generally, unless a situation requires dynamic allocation, stack allocation is
preferred. Often both techniques are used together in a program.
• Heap allocation is a necessity when:
• you have a very large allocation that could blow out the stack
• you need to control the memory lifetime, or memory must persist outside of a function
call
• you need to resize memory after its initial allocation
83
Recap
• The Stack
• The Heap and Dynamic Memory
• Practice: Pig Latin
• realloc
• Practice: Pig Latin Part 2
84
Question Break
Post on Ed with any questions you have for today’s lecture!
85
Extra Practice
86
Implementing strdup
How can we implement strdup using functions we’ve already seen?
87
Freeing Memory
Where should we free memory below so that all memory is freed properly?