Project 6: Building a “Safe String” Library

Your own simple, bounds-checked string library. You will create a safe_string_t structure that holds a char*, a length, and a capacity. You will then implement functions like s_create, s_destroy, s_append, s_copy, etc., that are impossible to use unsafely.

Quick Reference

Attribute Value
Primary Language C
Alternative Languages N/A
Difficulty Level 2: Intermediate
Time Estimate 1-2 weeks
Knowledge Area Defensive Programming / API Design / Data Structures
Tooling GCC/Clang
Prerequisites Strong C skills with malloc/realloc/free.

What You Will Build

Your own simple, bounds-checked string library. You will create a safe_string_t structure that holds a char*, a length, and a capacity. You will then implement functions like s_create, s_destroy, s_append, s_copy, etc., that are impossible to use unsafely.

Why It Matters

This project builds core skills that appear repeatedly in real-world systems and tooling.

Core Challenges

  • Designing the safe_string_t struct → maps to explicit state management
  • Handling memory allocation and reallocation → maps to dynamically growing the string’s capacity
  • Implementing bounds-checked operations → maps to writing the core logic that prevents overflows
  • Designing an ergonomic API → maps to making your library easy and intuitive to use

Key Concepts

  • Opaque Pointers: Hiding implementation details from the user.
  • Defensive Programming: “The Practice of Programming” by Kernighan and Pike.
  • API Design: “API Design for C++” by Martin Reddy (principles are universal).

Real-World Outcome

typedef struct safe_string_t* safe_string_handle;

safe_string_handle s_create(const char* initial_str);
void s_destroy(safe_string_handle ssh);
int s_append(safe_string_handle ssh, const char* to_append);
int s_copy(safe_string_handle dest, const safe_string_handle src);
const char* s_get_cstr(safe_string_handle ssh);
size_t s_get_length(safe_string_handle ssh);

Implementation Guide

  1. Reproduce the simplest happy-path scenario.
  2. Build the smallest working version of the core feature.
  3. Add input validation and error handling.
  4. Add instrumentation/logging to confirm behavior.
  5. Refactor into clean modules with tests.

Milestones

  • Milestone 1: Minimal working program that runs end-to-end.
  • Milestone 2: Correct outputs for typical inputs.
  • Milestone 3: Robust handling of edge cases.
  • Milestone 4: Clean structure and documented usage.

Validation Checklist

  • Output matches the real-world outcome example
  • Handles invalid inputs safely
  • Provides clear errors and exit codes
  • Repeatable results across runs

References

  • Main guide: LEARN_C_SECURE_CODING_DEEP_DIVE.md
  • “C Interfaces and Implementations” by David R. Hanson