Project 3: Dynamic Array
Build a
vector-style dynamic array with resizing, push/pop, and bounds checks.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Intermediate |
| Time Estimate | 1 week |
| Language | C |
| Prerequisites | Project 2, malloc/free |
| Key Topics | Amortized growth, ownership, APIs |
1. Learning Objectives
By completing this project, you will:
- Implement a resizable array with capacity management.
- Understand amortized time complexity.
- Design a C API for generic containers.
- Handle memory growth and shrink safely.
2. Theoretical Foundation
2.1 Core Concepts
- Capacity vs size: Capacity is allocated space; size is used elements.
- Amortized analysis: Doubling capacity makes average push O(1).
- Ownership: The array owns its buffer; elements may be owned by user.
2.2 Why This Matters
Dynamic arrays are the most common building block in modern systems. This project teaches how efficient containers work under the hood.
2.3 Historical Context / Background
C has no built-in containers. Systems libraries implement dynamic arrays with manual resizing and careful memory management.
2.4 Common Misconceptions
- “Realloc always moves memory”: It can extend in place.
- “Capacity equals size”: They are separate.
3. Project Specification
3.1 What You Will Build
A Vec-style container that supports:
vec_init,vec_freevec_push,vec_popvec_get,vec_setvec_reserve,vec_shrink
3.2 Functional Requirements
- Start with a small capacity and grow as needed.
- Provide bounds-checked accessors.
- Support element sizes via
void *andsize_t elem_size. - Return errors on allocation failures.
3.3 Non-Functional Requirements
- Performance: Doubling strategy for growth.
- Reliability: No memory leaks or invalid writes.
- Usability: Clear documentation for ownership.
3.4 Example Usage / Output
Vec v;
vec_init(&v, sizeof(int));
int x = 10;
vec_push(&v, &x);
int *p = vec_get(&v, 0);
// *p == 10
vec_free(&v);
3.5 Real World Outcome
You can reuse this container for arrays of any type and reason about its resizing behavior. You understand why push is “usually” constant time.
4. Solution Architecture
4.1 High-Level Design
Vec { data, size, capacity, elem_size } -> API functions -> tests
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
Vec struct |
Store metadata | Size/capacity fields |
| Resizer | Grow/shrink buffer | realloc strategy |
| Accessors | Read/write elements | Bounds checks |
4.3 Data Structures
typedef struct {
void *data;
size_t size;
size_t capacity;
size_t elem_size;
} Vec;
4.4 Algorithm Overview
Key Algorithm: Push
- If size == capacity, grow to
max(1, capacity * 2). - Copy element into buffer at
data + size * elem_size. - Increment size.
Complexity Analysis:
- Push: Amortized O(1)
- Get/Set: O(1)
5. Implementation Guide
5.1 Development Environment Setup
cc -Wall -Wextra -O2 -g -o test_vec test_vec.c vec.c
5.2 Project Structure
vec/
├── src/
│ ├── vec.c
│ └── vec.h
├── tests/
│ └── test_vec.c
└── README.md
5.3 The Core Question You’re Answering
“How do you grow a contiguous buffer without breaking existing data?”
5.4 Concepts You Must Understand First
Stop and research these before coding:
reallocsemantics- What happens when
reallocfails? - When does it move memory?
- What happens when
- Element size
- How do you compute the byte offset for an index?
- Ownership
- Who frees the elements inside the vector?
5.5 Questions to Guide Your Design
Before implementing, think through these:
- Will
vec_getreturn a pointer or copy into user buffer? - Should
vec_popreturn the element or just remove it? - When should you shrink capacity?
5.6 Thinking Exercise
Amortized Growth
If you start with capacity 1 and double each time, how many total copies occur after 1,000 pushes?
5.7 The Interview Questions They’ll Ask
Prepare to answer these:
- “Why is doubling capacity efficient?”
- “What does
reallocdo if it cannot grow in place?” - “How do you store elements of any type in C?”
5.8 Hints in Layers
Hint 1: Start with fixed capacity Implement push/pop without resizing.
Hint 2: Add resizing
Use realloc when size hits capacity.
Hint 3: Add bounds checks Return an error if index is out of range.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Arrays and pointers | “The C Programming Language” | Ch. 5 |
| Data structures | “Algorithms in C” | Arrays chapter |
5.10 Implementation Phases
Phase 1: Foundation (2-3 days)
Goals:
- Core struct and init/free
Tasks:
- Implement
vec_initandvec_free.
Checkpoint: No leaks in basic tests.
Phase 2: Core Functionality (2-3 days)
Goals:
- Push and get
Tasks:
- Implement
vec_pushwith resize. - Implement
vec_get.
Checkpoint: Push 1,000 items without failure.
Phase 3: Polish & Edge Cases (1-2 days)
Goals:
- Pop and shrink
Tasks:
- Add
vec_popand optional shrink.
Checkpoint: Size and capacity behave predictably.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Growth factor | 1.5x vs 2x | 2x | Simple and common |
| Shrinking | Aggressive vs conservative | Conservative | Avoid thrashing |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | API correctness | Push/get/set |
| Stress Tests | Large pushes | 100k items |
| Edge Cases | Empty pop | Return error |
6.2 Critical Test Cases
- Push past capacity: Buffer grows correctly.
- Out-of-bounds get: Returns error.
- Pop from empty: Safe failure.
6.3 Test Data
Integers, structs, small strings
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Forgetting elem_size | Corrupt data | Store element size |
Using realloc incorrectly |
Leaks | Assign to temp first |
| Off-by-one access | Crashes | Check bounds |
7.2 Debugging Strategies
- Use
valgrindto catch invalid writes. - Add asserts for size/capacity invariants.
7.3 Performance Traps
Shrinking too often makes push/pop expensive. Shrink only when size is far below capacity.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add
vec_clear. - Add
vec_insertat index.
8.2 Intermediate Extensions
- Add iterator-style traversal.
- Add
vec_reserve.
8.3 Advanced Extensions
- Implement small-buffer optimization.
- Add thread-safe variant.
9. Real-World Connections
9.1 Industry Applications
- Runtime systems: Dynamic arrays are everywhere.
- Embedded: Often reimplemented due to constraints.
9.2 Related Open Source Projects
- klib: Minimal C containers.
9.3 Interview Relevance
Dynamic array resizing and amortized analysis are common interview topics.
10. Resources
10.1 Essential Reading
- “Algorithms in C” - Arrays chapter
- “The C Programming Language” - Ch. 5
10.2 Video Resources
- Data structures lectures (arrays and amortized analysis)
10.3 Tools & Documentation
man 3 realloc: Reallocation behavior
10.4 Related Projects in This Series
- Linked List: Alternative dynamic container.
- Hash Table: Uses dynamic arrays internally.
11. Self-Assessment Checklist
11.1 Understanding
- I can explain amortized O(1).
- I know how
reallocbehaves. - I can define ownership of elements.
11.2 Implementation
- Vector resizes correctly.
- No memory leaks or overflows.
- API is documented.
11.3 Growth
- I can add insertion and deletion features.
- I can explain this project in an interview.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Supports init, push, get, free.
Full Completion:
- Adds pop, reserve, and bounds checks.
Excellence (Going Above & Beyond):
- Implements iterators and small-buffer optimization.
This guide was generated from C_PROGRAMMING_COMPLETE_MASTERY.md. For the complete learning path, see the parent directory.