Solutions: Lecture 6 In-Class Exercises¶
Solutions to the Lecture 6 in-class exercises. Try each exercise yourself before reading these.
Part A - Stacks and lifetimes (pen and paper)¶
Exercise A1 - Trace the frames¶
- At the deepest point, four frames are on the stack:
top -> sum_to(n = 0) returns 0
sum_to(n = 1) returns 1 + 0 = 1
sum_to(n = 2) returns 2 + 1 = 3
bottom sum_to(n = 3) returns 3 + 3 = 6
- They return top to bottom (innermost first):
sum_to(0)returns0, thensum_to(1)returns1, thensum_to(2)returns3, thensum_to(3)returns6. The frame that was pushed last is popped first. - Each call gets its own frame with its own
n. The fournvalues (3, 2, 1, 0) live in four separate frames on the stack at once, so they never overwrite one another - that is exactly what lets recursion work.
Exercise A2 - Spot the bug¶
- (a) Returning the address of a local.
xlives infirst's stack frame, which is popped whenfirstreturns, so&xdangles. Fix: return the value (int first(void) { return 10; }), or allocatexon the heap and return that pointer (caller frees). - (b) Use-after-free (dangling pointer). After
free(p), the block is no longer yours;*preads freed memory. Fix: print beforefree(p), or do not free until you are truly done. Settingp = NULL;after the free turns a later stray use into a loud crash. - (c) Memory leak.
ais allocated but never freed; whenloadreturns, the pointer is gone and the block can never be reclaimed. Fix:free(a);before the function returns.
Part B - The heap at the keyboard¶
Exercise B1 - A dynamic array¶
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("How many numbers? ");
scanf("%d", &n);
int *a = malloc(n * sizeof(int));
if (a == NULL) {
return 1;
}
printf("Enter %d numbers: ", n);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
int largest = a[0];
for (int i = 1; i < n; i++) {
if (a[i] > largest) {
largest = a[i];
}
}
printf("largest: %d\n", largest);
free(a);
return 0;
}
malloc(n * sizeof(int))reserves exactlynints - a size known only at run time, which a stack array could not do. TheNULLcheck guards against a failed allocation before anya[i]runs.a[i]works on the heap block exactly as on a stack array, becausea[i]is*(a + i)for any pointer. Seedlargestfroma[0], then scan fromi = 1.- One
malloc, onefree. Nothing reclaims the block for you. - Stretch (average):
double avg = (double) sum / n;after summing in the same loop; cast before dividing to avoid integer division.
Exercise B2 - Return a heap array¶
#include <stdio.h>
#include <stdlib.h>
int *make_squares(int n) {
int *a = malloc(n * sizeof(int));
if (a == NULL) {
return NULL;
}
for (int i = 0; i < n; i++) {
a[i] = i * i;
}
return a;
}
int main(void) {
int *sq = make_squares(5);
if (sq == NULL) {
return 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", sq[i]);
}
printf("\n");
free(sq); /* main owns the block now */
return 0;
}
make_squaresallocates on the heap, so the block survives after the function returns and its frame is popped. The local pointeradisappears on return, but the block it named does not.- Ownership passes to the caller.
mainreceives the pointer and is responsible for the singlefree. The function does not free what it returns - that would hand back a dangling pointer. - Stretch (the broken sibling):
int *make_squares_bad(int n) {
int a[100];
for (int i = 0; i < n; i++) {
a[i] = i * i;
}
return a; /* WARNING: address of a local; dangles on return */
}
a lives in the function's frame, popped on return, so the returned address is
dead - the compiler warns about exactly this. B2's heap block has no such problem
because its lifetime is manual, not tied to the frame.
Stretch¶
Concatenate two arrays onto the heap¶
#include <stdio.h>
#include <stdlib.h>
int *concat(const int *a, int na, const int *b, int nb) {
int *out = malloc((na + nb) * sizeof(int));
if (out == NULL) {
return NULL;
}
for (int i = 0; i < na; i++) {
out[i] = a[i];
}
for (int i = 0; i < nb; i++) {
out[na + i] = b[i]; /* b's elements start after a's na slots */
}
return out;
}
int main(void) {
int a[] = {1, 2, 3};
int b[] = {4, 5};
int *joined = concat(a, 3, b, 2);
if (joined == NULL) {
return 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", joined[i]);
}
printf("\n");
free(joined);
return 0;
}
- The size is computed at run time from
na + nb, then allocated in one block. The second copy starts at offsetna, soblands right aftera. const int *aandconst int *bsay the inputs are read-only; only the new blockoutis written. The caller ownsoutand frees it.- This "compute the size, allocate, fill, return, caller frees" pattern is how C code builds data whose length is not known until the program runs - the same shape behind growing buffers and string builders.