pointers-memory · intermediate · ~35 min

Project: Grow a dynamic buffer with realloc

Doubling growth strategy and the realloc idiom (use a temporary so OOM doesn't leak the original).

Challenge

Build a growable byte buffer that appends data and enlarges its backing store on demand using realloc with a doubling growth strategy.

The buffer type and API:

typedef struct { char *data; size_t len, cap; } buf_t;
void buf_init(buf_t *b);
int  buf_append(buf_t *b, const char *bytes, size_t n);  /* 0 on success, -1 on OOM */
void buf_free(buf_t *b);

Task

Implement the four functions (no main — the grader calls them):

  • buf_init zeroes the struct (NULL data, len 0, cap 0).
  • buf_append appends n bytes from bytes, growing capacity (double it, starting at 8) when needed.
  • buf_free releases the backing store and resets the struct to the init state.

Input

buf_append receives bytes (source) and n (count, may be 0).

Output

buf_append returns 0 on success or -1 on allocation failure. The other functions return nothing.

Example

buf_t b; buf_init(&b);
buf_append(&b, "hi", 2);          ->   b.len == 2, b.cap >= 2
buf_append(&b, ", world!", 8);    ->   b.len == 10, data == "hi, world!"
buf_free(&b);                     ->   data == NULL, len == 0, cap == 0

Edge cases

  • Empty append (n == 0): no-op, returns 0.
  • First append when cap == 0: start capacity at 8 (or larger if needed).

Rules

  • Grow with realloc into a temporary so an OOM doesn't leak the original buffer.
  • Double the capacity rather than growing by exactly n.

Why this matters

Optional project: combine ownership, bounds, and failure handling across an API.

Input format

buf_append receives bytes (source) and n (count, may be 0).

Output format

buf_append returns 0 on success or -1 on allocation failure. The other functions return nothing.

Constraints

Initialized buf_t owns data and maintains len<=cap. bytes is live and non-overlapping with data for n>0. Append returns 0 or -1 on overflow/allocation failure without changing the old buffer. n=0 is a no-op; buf_free resets all fields.

Starter code

#include <stddef.h>
#ifndef BUF_T_DEFINED
#define BUF_T_DEFINED
typedef struct { char *data; size_t len, cap; } buf_t;
#endif
void buf_init(buf_t *b);
int  buf_append(buf_t *b, const char *bytes, size_t n);
void buf_free(buf_t *b);

Common mistakes

Losing the old pointer, overflowing len+n or doubled capacity, or forgetting to reset fields.

Edge cases to handle

First append; empty append; multiple growth steps; size overflow; failed growth preserves data/len/cap.

Complexity

O(1) amortised per byte. Total O(n) for n bytes across O(log n) reallocs.

Background lessons

Up next

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.