Skip to content

CMSC 14300: Practice Set 4 - Pointers and Dynamic Memory

Practice for the material in Week 3: pointers, pointer arithmetic, passing pointers to functions, the stack-vs-heap distinction, and heap allocation with malloc/free. None of these repeats a lecture or homework exercise; together they drill the new moves - handing a function an address, walking a pointer, and owning heap memory. This is good preparation for the next quiz.

Work in a fresh directory and compile everything with warnings on. Link <stdlib.h> for malloc/free:

mkdir -p ~/cmsc14300/lec05-pset && cd ~/cmsc14300/lec05-pset
clang -Wall -Wextra -std=c17 problem1.c -o problem1

If your compiler prints a warning, treat it as something to fix - warnings are the compiler trying to help you, and the warning about "returning the address of a local" in particular is one you want to listen to.


Problem 1 - Rotate three values through pointers

Write a function that rotates three ints in the caller: a takes b's value, b takes c's, and c takes a's old value. The whole point is that the change must be visible back in main, so the function takes addresses.

void rotate3(int *a, int *b, int *c);
before: x = 1, y = 2, z = 3
after:  x = 2, y = 3, z = 1
  • Save one value in a temporary first, then shuffle through the pointers - the same idea as a two-way swap, with one more step.
  • Call it as rotate3(&x, &y, &z);. If x, y, z do not change, you swapped the pointers instead of the values they point at - put your stars on the values (*a, *b, *c).

Problem 2 - Split a duration with out-parameters

A function returns one value, but here you need three. Use out-parameters: pass pointers for the function to fill with hours, minutes, and seconds.

void split_time(int total_seconds, int *h, int *m, int *s);
3661 seconds = 1 h 1 m 1 s
  • *h = total_seconds / 3600; then reduce the remainder for minutes and seconds. The / and % operators do all the work.
  • In main, declare int h, m, s;, call split_time(3661, &h, &m, &s);, then print the three. Check a few values by hand: 3661 = 3600 + 60 + 1.

Problem 3 - Sum a sentinel-terminated array by walking a pointer

Some arrays carry an end marker instead of a separate length - the same idea as a string's '\0'. Here the marker is a -1. Write a function that sums everything before the -1, using only pointer arithmetic - no [] and no length passed in.

int sum_until_sentinel(const int *a);   /* sum the values before the -1 marker */
int data[] = {10, 20, 30, 40, -1};
sum = 100
  • Walk a pointer: const int *p = a; then loop while (*p != -1), doing total += *p; and p++. Stop when the dereferenced value is the sentinel.
  • This is exactly the "walk to the terminator" loop from strings, with -1 playing the role of '\0' and a pointer instead of an index.
  • Check your understanding: why does this function not need an n parameter, when Lecture 4's sum(int a[], int n) did? What plays the role that n played?

Problem 4 - Read an unknown count into a heap buffer, then print it reversed

You do not know how many numbers are coming until the user tells you. Read the count n first, allocate a heap array of exactly that size, fill it, print the values in reverse order, and free it. This is the array whose size is decided at run time - a fixed int a[100] could not adapt to n.

How many numbers? 5
Enter 5 numbers: 64 8 23 91 19
reversed: 19 91 23 8 64
  • Read n, then int *a = malloc(n * sizeof(int));. Check a against NULL before using it.
  • Fill with a scanf("%d", &a[i]) loop over 0 .. n-1, then print with a second loop running i from n - 1 down to 0.
  • Finish with exactly one free(a);. One malloc, one free.
  • The new thing here is that a is a heap block sized by the user at run time, not a constant baked into the program. Indexing it (a[i]) works exactly as on a stack array.

Problem 5 - Build and return a heap array

Write a function that allocates an array of the first n even numbers on the heap and returns it. The caller uses it and frees it. This is the safe way to hand back an array from a function - heap memory outlives the call, a local array does not.

int *evens(int n);   /* returns a heap array: 0, 2, 4, 6, ... (n of them) */
0 2 4 6 8
  • Inside evens: malloc(n * sizeof(int)), check for NULL (return NULL on failure), fill a[i] = 2 * i;, and return a;.
  • In main: call evens(5), check the result, print the values, then free it - main owns the block now. The function allocates; the caller frees.
  • Check your understanding: what goes wrong if evens instead declares a local int a[100]; and returns a? Which lifetime rule does that break, and what does the compiler say about it?

Self-check

You're ready for the upcoming quiz material if you can, without looking it up:

  • Explain what & and * do, and why writing through a pointer changes the original variable.
  • State why C functions cannot change a caller's variable unless they are given its address, and how out-parameters return more than one value.
  • Say what a[i] means in terms of pointer arithmetic, and why p + 1 advances by one element rather than one byte.
  • Describe the difference between the stack and the heap: who controls each one's lifetime, and when you need the heap.
  • Write the malloc/check/use/free sequence from memory, and name the two bugs that come from getting free wrong (a leak and a dangling pointer).
  • Explain why a function may return heap memory but never the address of a local.