<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged pointers</title>
<link>https://coderlegion.com/tag/pointers</link>
<description>Powered by Question2Answer</description>
<item>
<title>Build a Dynamic Array in C from Scratch</title>
<link>https://coderlegion.com/23851/build-a-dynamic-array-in-c-from-scratch</link>
<description>&lt;h2 id=&quot;build-a-dynamic-array-in-c-from-scratch&quot;&gt;Build a Dynamic Array in C from Scratch&lt;/h2&gt;
&lt;p&gt;You've used &lt;code&gt;push()&lt;/code&gt; in JavaScript, &lt;code&gt;append()&lt;/code&gt; in Python, &lt;code&gt;add()&lt;/code&gt; in Java. They feel instant. They never run out of space. You never think about them.&lt;/p&gt;
&lt;p&gt;That's a problem — not because those abstractions are bad, but because you're using a tool without knowing what it costs. Every one of those methods is built on ideas you can understand completely. This article will show you how, in C, where the machine hides nothing from you.&lt;/p&gt;
&lt;p&gt;By the end you'll have built a working dynamic array and, more importantly, a mental model that makes &lt;code&gt;std::vector&lt;/code&gt;, Go slices, and Rust's &lt;code&gt;Vec&lt;/code&gt; all obvious.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-core-idea-two-layers-of-memory&quot;&gt;The Core Idea: Two Layers of Memory&lt;/h3&gt;
&lt;p&gt;Before writing a single line, you need to understand the central insight that makes a dynamic array possible.&lt;/p&gt;
&lt;p&gt;A normal array is a fixed block of memory. You declare &lt;code&gt;int numbers[8]&lt;/code&gt; and the compiler carves out exactly 32 bytes on the stack. That's it. If you need a 9th element, you're stuck.&lt;/p&gt;
&lt;p&gt;A dynamic array gets around this by living on the &lt;strong&gt;heap&lt;/strong&gt; — a region of memory that your program can request and release at runtime, in any size, at any time. The heap doesn't get freed automatically when a function returns. You control it. That control is what makes resizing possible, and what makes memory bugs possible too.&lt;/p&gt;
&lt;p&gt;The dynamic array is two things at once: a block of integers sitting somewhere on the heap, and a small control structure that remembers where that block is, how big it is, and how many elements are currently in it. Everything we build is just bookkeeping those two things correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-you-need-to-know-first&quot;&gt;What You Need to Know First&lt;/h3&gt;
&lt;h4 id=&quot;memory-stack-vs-heap&quot;&gt;Memory: Stack vs Heap&lt;/h4&gt;
&lt;p&gt;When you declare a local variable, it goes on the &lt;strong&gt;stack&lt;/strong&gt;. The stack is fast and automatic — variables appear when a function is called and vanish when it returns.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void foo() {
    int x = 10; // lives on the stack
}               // x is gone here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;strong&gt;heap&lt;/strong&gt; is different. Memory there persists until you explicitly free it. You ask the OS for a chunk, use it, and free it when you're done. This is what enables data structures that outlive the function that created them.&lt;/p&gt;
&lt;h4 id=&quot;pointers&quot;&gt;Pointers&lt;/h4&gt;
&lt;p&gt;A pointer is a variable that holds a memory address rather than a value.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int score = 99;
int *ptr = &amp;amp;score; // ptr holds the address of score, not 99
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;amp;score&lt;/code&gt; means &quot;give me the address of score.&quot; &lt;code&gt;*ptr&lt;/code&gt; means &quot;go to that address and read what's there.&quot; They're inverse operations.&lt;/p&gt;
&lt;p&gt;Why does this matter? Because when you allocate memory on the heap, you get back an address — a pointer — to where that memory starts. There's no other way to refer to heap memory. Pointers aren't a language quirk; they're the only handle you have on dynamic allocation.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int *numbers = malloc(sizeof(int) * 8);
// numbers holds the address of a 32-byte block on the heap
// numbers[0], numbers[1], ... numbers[7] are all valid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;malloc&lt;/code&gt; returns a pointer to the allocated block. &lt;code&gt;sizeof(int)&lt;/code&gt; gives the size in bytes of one integer (typically 4). Multiply by 8 and you get 32 bytes — room for 8 integers.&lt;/p&gt;
&lt;h4 id=&quot;structs&quot;&gt;Structs&lt;/h4&gt;
&lt;p&gt;C has no classes. To group related data, you use a &lt;code&gt;struct&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct IntArray {
    int *data;     // pointer to the integers on the heap
    int length;    // how many elements are currently stored
    int capacity;  // how much space is allocated
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;typedef&lt;/code&gt; lets you drop the &lt;code&gt;struct&lt;/code&gt; keyword every time you use it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;typedef struct {
    int *data;
    int length;
    int capacity;
} IntArray;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now &lt;code&gt;IntArray arr;&lt;/code&gt; is valid instead of &lt;code&gt;struct IntArray arr;&lt;/code&gt;. Small thing, but it adds up.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-&amp;gt;&lt;/code&gt; operator accesses a struct's field through a pointer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;array-&amp;gt;length  // same as (*array).length
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll use &lt;code&gt;-&amp;gt;&lt;/code&gt; constantly because our functions always receive a pointer to the struct, not the struct itself. Passing the struct by value would copy all three fields every time — wasteful, and it means modifications wouldn't be visible to the caller.&lt;/p&gt;
&lt;h4 id=&quot;the-header-files&quot;&gt;The Header Files&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdio.h&amp;gt;   // printf, scanf
#include &amp;lt;stdlib.h&amp;gt;  // malloc, realloc, free, exit
#include &amp;lt;stdbool.h&amp;gt; // bool, true, false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;stdlib.h&lt;/code&gt; is the critical one. Every memory function we use lives there. Without it, &lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; are undeclared.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;stdbool.h&lt;/code&gt; exists because C didn't originally have a boolean type. It gives you &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;true&lt;/code&gt;, and &lt;code&gt;false&lt;/code&gt; as proper names instead of &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, and &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-structure&quot;&gt;The Structure&lt;/h3&gt;
&lt;p&gt;Now that the building blocks are clear, the struct makes obvious sense:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#define DEFAULT_CAPACITY 8

typedef struct {
    int *data;     // address of our integer block on the heap
    int length;    // elements currently stored (logical size)
    int capacity;  // elements we have room for (physical size)
} IntArray;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;length&lt;/code&gt; and &lt;code&gt;capacity&lt;/code&gt; are not the same thing. This distinction is the heart of how a dynamic array works.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;capacity = 8  (8 slots exist in memory)
length   = 3  (only 3 are in use)

[ 10 | 20 | 30 | __ | __ | __ | __ | __ ]
  [0]  [1]  [2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;length == capacity&lt;/code&gt;, the array is full and must grow before the next &lt;code&gt;add&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;#define DEFAULT_CAPACITY 8&lt;/code&gt; is a compile-time constant. The preprocessor replaces every occurrence of &lt;code&gt;DEFAULT_CAPACITY&lt;/code&gt; with &lt;code&gt;8&lt;/code&gt; before the compiler sees the code. It's not a variable — it has no address and uses no runtime memory.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;creating-the-array&quot;&gt;Creating the Array&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;IntArray *createArray(int capacity) {

    // Guard against nonsense input
    if (capacity &amp;lt;= 0)
        capacity = DEFAULT_CAPACITY;

    // Allocate the control struct on the heap
    // This gives us room for: one pointer, two ints
    IntArray *array = malloc(sizeof(IntArray));

    if (array == NULL) {
        printf(&quot;Memory allocation failed\n&quot;);
        exit(EXIT_FAILURE); // no safe way to recover here
    }

    // Allocate the integer block separately
    // The struct just holds a pointer to this block
    array-&amp;gt;data = malloc(sizeof(int) * capacity);

    if (array-&amp;gt;data == NULL) {
        printf(&quot;Memory allocation failed\n&quot;);
        free(array); // don't leak the struct we just allocated
        exit(EXIT_FAILURE);
    }

    array-&amp;gt;length = 0;        // no elements yet
    array-&amp;gt;capacity = capacity; // but we have space reserved

    return array; // return the address of the struct
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice we allocate &lt;strong&gt;twice&lt;/strong&gt;. Once for the struct, and once for the integer data. These are two separate heap allocations, and that's why we must free both when we're done. The struct contains the address of the data block, but it doesn't own it in any automatic sense — we have to track and free it ourselves.&lt;/p&gt;
&lt;p&gt;The return type is &lt;code&gt;IntArray *&lt;/code&gt; — a pointer. We're not returning the struct itself; we're returning its address. This is the handle the caller will use for every subsequent operation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;destroying-the-array&quot;&gt;Destroying the Array&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void destroyArray(IntArray *array) {
    free(array-&amp;gt;data); // free the integer block first
    free(array);       // then free the struct
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Order matters. Free the data block first, then the struct. If you freed the struct first, &lt;code&gt;array-&amp;gt;data&lt;/code&gt; would be a dangling read — you'd be accessing memory through a pointer to something that's already freed.&lt;/p&gt;
&lt;p&gt;Every &lt;code&gt;malloc&lt;/code&gt; must have exactly one matching &lt;code&gt;free&lt;/code&gt;. This is the discipline C demands. Modern languages have garbage collectors to enforce this automatically. In C, it's on you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getting-and-setting-elements&quot;&gt;Getting and Setting Elements&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int get(IntArray *array, int index) {
    if (index &amp;lt; 0 || index &amp;gt;= array-&amp;gt;length) {
        printf(&quot;Index out of bounds\n&quot;);
        exit(EXIT_FAILURE);
    }
    return array-&amp;gt;data[index];
}

void set(IntArray *array, int index, int value) {
    if (index &amp;lt; 0 || index &amp;gt;= array-&amp;gt;length) {
        printf(&quot;Index out of bounds\n&quot;);
        exit(EXIT_FAILURE);
    }
    array-&amp;gt;data[index] = value;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The bounds check is not optional. Without it, &lt;code&gt;array-&amp;gt;data[index]&lt;/code&gt; when &lt;code&gt;index &amp;gt;= length&lt;/code&gt; reads memory that belongs to something else — possibly another variable, possibly the OS. C won't stop you. The behavior is undefined, which in practice means silent data corruption or a crash that's hard to trace.&lt;/p&gt;
&lt;p&gt;Both operations are &lt;strong&gt;O(1)&lt;/strong&gt; because &lt;code&gt;array-&amp;gt;data[index]&lt;/code&gt; computes directly to a memory address:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;address = base_address + (index × sizeof(int))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No searching. One arithmetic operation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;adding-elements-the-core-of-a-dynamic-array&quot;&gt;Adding Elements: The Core of a Dynamic Array&lt;/h3&gt;
&lt;p&gt;This is the function everything else builds toward.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void add(IntArray *array, int value) {

    // Is the array full?
    if (array-&amp;gt;length == array-&amp;gt;capacity) {

        // Double the capacity — we'll explain why below
        array-&amp;gt;capacity *= 2;

        // Ask the OS to resize our integer block
        int *newData = realloc(
            array-&amp;gt;data,
            sizeof(int) * array-&amp;gt;capacity
        );

        // CRITICAL: store in a temp pointer, not array-&amp;gt;data
        // If realloc fails, it returns NULL — if we assigned
        // directly to array-&amp;gt;data we'd lose the original pointer
        // and have a memory leak with no way to recover
        if (newData == NULL) {
            printf(&quot;Reallocation failed\n&quot;);
            exit(EXIT_FAILURE);
        }

        // Only update once we know it succeeded
        array-&amp;gt;data = newData;
    }

    // Store the value and advance the length in one step
    array-&amp;gt;data[array-&amp;gt;length++] = value;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;realloc&lt;/code&gt; is &lt;code&gt;malloc&lt;/code&gt;'s smarter sibling. It takes an existing pointer and a new size. If it can extend the block in place, it does. If not, it allocates a new block, copies the old data, frees the old block, and returns the new address. The existing contents are preserved either way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why double the capacity instead of growing by 1?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you grew by 1 every time the array filled up, every single &lt;code&gt;add&lt;/code&gt; would trigger a &lt;code&gt;realloc&lt;/code&gt; and a copy of all existing elements. Adding &lt;code&gt;n&lt;/code&gt; elements would cost &lt;code&gt;O(n²)&lt;/code&gt; total.&lt;/p&gt;
&lt;p&gt;Doubling means resizes happen at sizes 4, 8, 16, 32, 64... Each resize copies everything, but the number of copies per element approaches 2 as &lt;code&gt;n&lt;/code&gt; grows. Over many insertions, the average cost per &lt;code&gt;add&lt;/code&gt; is &lt;strong&gt;O(1)&lt;/strong&gt;. This is called &lt;strong&gt;amortized constant time&lt;/strong&gt; — expensive occasionally, cheap on average.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;removing-elements&quot;&gt;Removing Elements&lt;/h3&gt;
&lt;h4 id=&quot;remove-by-index&quot;&gt;Remove by Index&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void removeAt(IntArray *array, int index) {

    if (index &amp;lt; 0 || index &amp;gt;= array-&amp;gt;length) {
        printf(&quot;Index out of bounds\n&quot;);
        exit(EXIT_FAILURE);
    }

    // Shift every element after the removed one left by one position
    for (int i = index; i &amp;lt; array-&amp;gt;length - 1; i++) {
        array-&amp;gt;data[i] = array-&amp;gt;data[i + 1];
    }

    // The last position still holds the old value, but we don't
    // erase it — we just stop counting it as part of the array
    array-&amp;gt;length--;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The loop goes to &lt;code&gt;array-&amp;gt;length - 1&lt;/code&gt;, not &lt;code&gt;array-&amp;gt;length&lt;/code&gt;. At the final iteration, &lt;code&gt;i&lt;/code&gt; is &lt;code&gt;length - 2&lt;/code&gt; and we read &lt;code&gt;array-&amp;gt;data[length - 1]&lt;/code&gt;. If the loop ran to &lt;code&gt;length&lt;/code&gt;, the last iteration would read &lt;code&gt;array-&amp;gt;data[length]&lt;/code&gt;, which is outside the allocated block.&lt;/p&gt;
&lt;p&gt;This is &lt;strong&gt;O(n)&lt;/strong&gt; in the worst case — removing from index 0 shifts every element.&lt;/p&gt;
&lt;h4 id=&quot;remove-by-value&quot;&gt;Remove by Value&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;bool removeValue(IntArray *array, int value) {
    for (int i = 0; i &amp;lt; array-&amp;gt;length; i++) {
        if (array-&amp;gt;data[i] == value) {
            removeAt(array, i);
            return true;  // remove first match only
        }
    }
    return false; // value wasn't in the array
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Search linearly, delegate the actual removal to &lt;code&gt;removeAt&lt;/code&gt;. Returns &lt;code&gt;false&lt;/code&gt; when the value isn't found so the caller can detect the miss.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reversing-the-array&quot;&gt;Reversing the Array&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void reverse(IntArray *array) {
    int left = 0;
    int right = array-&amp;gt;length - 1;

    while (left &amp;lt; right) {
        // Swap elements at left and right
        int temp = array-&amp;gt;data[left];
        array-&amp;gt;data[left] = array-&amp;gt;data[right];
        array-&amp;gt;data[right] = temp;

        // Move both pointers inward
        left++;
        right--;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two-pointer technique. Start from opposite ends, swap, move inward, stop when they meet. &lt;strong&gt;O(n)&lt;/strong&gt; time, &lt;strong&gt;O(1)&lt;/strong&gt; extra space — no second array needed.&lt;/p&gt;
&lt;p&gt;The condition is &lt;code&gt;left &amp;lt; right&lt;/code&gt;, not &lt;code&gt;left != right&lt;/code&gt;. For an odd-length array, they'll converge on the middle element. That element doesn't need swapping with itself, so the loop correctly stops before touching it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;printing&quot;&gt;Printing&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;void printArray(IntArray *array) {
    printf(&quot;[&quot;);
    for (int i = 0; i &amp;lt; array-&amp;gt;length; i++) {
        printf(&quot;%d&quot;, array-&amp;gt;data[i]);
        if (i != array-&amp;gt;length - 1)
            printf(&quot;, &quot;);
    }
    printf(&quot;]\n&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The comma logic — print a comma after every element except the last — is a common pattern. The alternative (printing a comma before every element except the first) is equivalent; pick one and be consistent.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;putting-it-together&quot;&gt;Putting It Together&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int main() {
    IntArray *arr = createArray(4); // capacity 4, length 0

    add(arr, 3);   // length 1
    add(arr, 7);   // length 2
    add(arr, 6);   // length 3
    add(arr, -2);  // length 4, capacity 4 — full

    printArray(arr); // [3, 7, 6, -2]

    removeValue(arr, 7); // shift left, length becomes 3

    printArray(arr); // [3, 6, -2]

    reverse(arr); // two-pointer swap

    printArray(arr); // [-2, 6, 3]

    destroyArray(arr); // free data block, then free struct

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: the first &lt;code&gt;add&lt;/code&gt; after &lt;code&gt;createArray(4)&lt;/code&gt; that would require a 5th slot would trigger a resize — capacity doubles to 8. In this &lt;code&gt;main&lt;/code&gt; we add exactly 4 elements, so no resize occurs. To see it in action, add a 5th element.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-gotchas&quot;&gt;The Gotchas&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Assigning &lt;code&gt;realloc&lt;/code&gt; directly to the original pointer.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Wrong — if realloc returns NULL, you've lost your data
array-&amp;gt;data = realloc(array-&amp;gt;data, newSize);

// Right — check before assigning
int *newData = realloc(array-&amp;gt;data, newSize);
if (newData != NULL) array-&amp;gt;data = newData;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Freeing in the wrong order.&lt;/strong&gt;&lt;br&gt;
Free &lt;code&gt;array-&amp;gt;data&lt;/code&gt; before &lt;code&gt;array&lt;/code&gt;. If you free the struct first, you're reading &lt;code&gt;array-&amp;gt;data&lt;/code&gt; through a dangling pointer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;length&lt;/code&gt; instead of &lt;code&gt;capacity&lt;/code&gt; in the resize check.&lt;/strong&gt;&lt;br&gt;
The array is full when &lt;code&gt;length == capacity&lt;/code&gt;. If you compare against the wrong field, you either resize too early (wasted allocations) or never resize at all (out-of-bounds writes).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Off-by-one in &lt;code&gt;removeAt&lt;/code&gt;'s loop.&lt;/strong&gt;&lt;br&gt;
The loop runs to &lt;code&gt;array-&amp;gt;length - 1&lt;/code&gt;, not &lt;code&gt;array-&amp;gt;length&lt;/code&gt;. The last iteration reads &lt;code&gt;data[length - 1]&lt;/code&gt;. Going one further reads past the end of the allocated block.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not checking &lt;code&gt;malloc&lt;/code&gt; for &lt;code&gt;NULL&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;malloc&lt;/code&gt; returns &lt;code&gt;NULL&lt;/code&gt; when the OS can't satisfy the request. Dereferencing a &lt;code&gt;NULL&lt;/code&gt; pointer is undefined behavior. Always check.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using the array after &lt;code&gt;destroyArray&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
After you free memory, the pointer still holds the old address. Reading through it is undefined behavior. Set pointers to &lt;code&gt;NULL&lt;/code&gt; after freeing if you need to guard against accidental reuse.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;junior-pattern-vs-senior-pattern&quot;&gt;Junior Pattern vs Senior Pattern&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Resizing:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Junior — assigns directly, leaks memory on failure
array-&amp;gt;data = realloc(array-&amp;gt;data, sizeof(int) * array-&amp;gt;capacity);
if (array-&amp;gt;data == NULL) exit(EXIT_FAILURE); // original pointer is gone

// Senior — uses temp, original is recoverable on failure
int *newData = realloc(array-&amp;gt;data, sizeof(int) * array-&amp;gt;capacity);
if (newData == NULL) {
    // array-&amp;gt;data is still valid here, nothing was lost
    exit(EXIT_FAILURE);
}
array-&amp;gt;data = newData;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Cleanup:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Junior — frees struct first, then reads dangling pointer
free(array);
free(array-&amp;gt;data); // undefined behavior: array is already freed

// Senior — free data first, then the container
free(array-&amp;gt;data);
free(array);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Bounds checking:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Junior — trusts the caller
return array-&amp;gt;data[index];

// Senior — validates before accessing
if (index &amp;lt; 0 || index &amp;gt;= array-&amp;gt;length) {
    // handle error
}
return array-&amp;gt;data[index];
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;where-this-lives-in-the-real-world&quot;&gt;Where This Lives in the Real World&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;std::vector&lt;/code&gt; in C++ is this exact structure. The underlying implementation uses &lt;code&gt;new[]&lt;/code&gt; instead of &lt;code&gt;malloc&lt;/code&gt;, but the capacity/length distinction, the doubling strategy, and the contiguous memory layout are identical.&lt;/p&gt;
&lt;p&gt;Go slices have a header with &lt;code&gt;ptr&lt;/code&gt;, &lt;code&gt;len&lt;/code&gt;, and &lt;code&gt;cap&lt;/code&gt; — those are our &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;length&lt;/code&gt;, and &lt;code&gt;capacity&lt;/code&gt;. When you &lt;code&gt;append&lt;/code&gt; past &lt;code&gt;cap&lt;/code&gt;, Go allocates a new backing array, copies, and returns a new slice header.&lt;/p&gt;
&lt;p&gt;Rust's &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; is the same: a heap-allocated contiguous buffer, a length, and a capacity. The ownership system handles the freeing automatically, but the memory layout underneath is what you just built.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;time-complexity&quot;&gt;Time Complexity&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Operation &lt;/th&gt;&lt;th&gt;Complexity &lt;/th&gt;&lt;th&gt;Why&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;get&lt;/code&gt; &lt;/td&gt;&lt;td&gt;O(1) &lt;/td&gt;&lt;td&gt;Direct address calculation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;set&lt;/code&gt; &lt;/td&gt;&lt;td&gt;O(1) &lt;/td&gt;&lt;td&gt;Direct address calculation&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;add&lt;/code&gt; (no resize) &lt;/td&gt;&lt;td&gt;O(1) &lt;/td&gt;&lt;td&gt;Write to &lt;code&gt;data[length]&lt;/code&gt;, increment&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;add&lt;/code&gt; (resize) &lt;/td&gt;&lt;td&gt;O(n) &lt;/td&gt;&lt;td&gt;&lt;code&gt;realloc&lt;/code&gt; copies all elements&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;add&lt;/code&gt; (amortized) &lt;/td&gt;&lt;td&gt;O(1) &lt;/td&gt;&lt;td&gt;Resizes are infrequent enough&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;removeAt&lt;/code&gt; &lt;/td&gt;&lt;td&gt;O(n) &lt;/td&gt;&lt;td&gt;Shift all elements after index&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;removeValue&lt;/code&gt; &lt;/td&gt;&lt;td&gt;O(n) &lt;/td&gt;&lt;td&gt;Linear scan + shift&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;reverse&lt;/code&gt; &lt;/td&gt;&lt;td&gt;O(n) &lt;/td&gt;&lt;td&gt;Visit each element once&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;your-turn&quot;&gt;Your Turn&lt;/h3&gt;
&lt;p&gt;Extend the implementation with these two functions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// Insert a value at a specific index, shifting everything right
void insertAt(IntArray *array, int index, int value);

// Return the index of the first occurrence of value, or -1 if not found
int indexOf(IntArray *array, int value);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Constraints: &lt;code&gt;insertAt&lt;/code&gt; must handle growth the same way &lt;code&gt;add&lt;/code&gt; does. &lt;code&gt;indexOf&lt;/code&gt; must return &lt;code&gt;-1&lt;/code&gt;, not exit, when the value is absent.&lt;/p&gt;
&lt;p&gt;You'll know you're done when this &lt;code&gt;main&lt;/code&gt; produces the expected output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;IntArray *arr = createArray(4);
add(arr, 10);
add(arr, 30);
insertAt(arr, 1, 20); // insert 20 between 10 and 30
printArray(arr);      // [10, 20, 30]
printf(&quot;%d\n&quot;, indexOf(arr, 20)); // 1
printf(&quot;%d\n&quot;, indexOf(arr, 99)); // -1
destroyArray(arr);
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23851/build-a-dynamic-array-in-c-from-scratch</guid>
<pubDate>Fri, 31 Jul 2026 10:38:49 +0000</pubDate>
</item>
</channel>
</rss>