Step 59. Pointers 2: Arrays — The Law of Adjacent Slots

Step 59. Pointers 2: Arrays — The Law of Adjacent Slots

Level 1 — Programming and the Computer’s Inner Workings | Difficulty ★★★★☆ | Estimated time: 4 hours

Prerequisites: Steps 56–58 complete. You’ve done the basic experiments with pointers (& and *).

  • What you need: a Linux terminal, gcc, a notebook for writing down addresses.
  • Caution: today’s exercises are experiments inside your own computer, so they’re safe. Addresses change on every run — numbers differing from the book is normal.

In Step 58 we looked at the slot of a single variable. Today we look at five slots at once: the array — several variables of the same kind lined up in a row. It looks similar to a Python list, but inside it’s far simpler, and for that reason it shows you far more.

Today’s topic in one sentence: "arrays and pointers are actually the same thing." This sentence is C’s most famous truth, and at first the hardest one to believe. So today we go by experiment, not persuasion. Print addresses, add to them, follow them — and you’ll believe it on your own. And when this truth meets the tiny guard hiding at the end of strings, , the front gate of a giant topic called buffer overflow starts coming into view.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Confirm with address output that an array is "slots stuck together in memory"
  • Prove by experiment that an array’s name is really the address of its first slot
  • Confirm that arr[i] and *(arr + i) mean the same thing by accessing both ways
  • Know that in pointer arithmetic, "one step" is set by the data type’s size
  • Explain the invisible guard at the end of strings,

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment C language, Linux terminal, gcc -Wall (verified: Ubuntu 13.3.0, 64-bit)
Today’s syntax int arr[5], arr[i], *(arr + i), pointer addition and subtraction, ''
Concepts needed Array = adjacent slots, array name = address of the first slot, the step size of pointer arithmetic, the null character

2-1. Arrays — Slots Stuck Together

int arr[5]; means "make five int slots stuck together." If a Python list is a bundle of boxes each floating on its own, a C array is a single five-slot locker unit. Being stuck together is the key point. Know the first slot’s number, and the remaining slots’ numbers are computed by addition.

2-2. Array Name = Address of the First Slot

Here’s today’s truth. The array name arr is actually the same value as "the address of the first slot." arr == &arr[0] holds. That’s why in Step 56’s scanf we didn’t attach & to a character array — the name itself was already the address.

2-3. Pointer Arithmetic — The Size of a Step

If you add 1 to an address, how many slots do you move? Not 1. You move "one slot of that data type." Add 1 to an int (4 bytes) pointer and the address grows by 4. The size of a step is determined by the shoe (the data type). So arr + 1 is "the address of the second slot," and *(arr + 1) is "the second slot’s value." And arr[1] is its pretty shorthand.

2-4. — The End Guard of Strings

In C, a string is "an array of characters + a marker announcing the end." "Hi" is actually three slots — H, i, and the invisible (the null character, value 0). C reads a string until it meets this guard. If the guard is missing or pushed out? The read, not knowing the end, crosses into the neighboring slot. This incident is the key scene of the buffer overflow story.


3. Follow Along

Today, too, addresses change on every run. Don’t look at the numbers themselves — look at the relationships: "by how much do they jump, are the two the same."

3-1. The Addresses of an Array — Confirming They’re Adjacent

Input (arr1.c)

#include <stdio.h>

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d, address: %pn", i, arr[i], &arr[i]);
    }
    return 0;
}

Compile and run

gcc -Wall arr1.c -o arr1
./arr1
arr[0] = 10, address: 0x7fffd1d9bd50
arr[1] = 20, address: 0x7fffd1d9bd54
arr[2] = 30, address: 0x7fffd1d9bd58
arr[3] = 40, address: 0x7fffd1d9bd5c
arr[4] = 50, address: 0x7fffd1d9bd60

(Verified 2026-09-09. Addresses differ on every run.)

How to read it: look at the endings of the addresses. 50, 54, 58, 5c, 60 — jumping by 4 in hexadecimal. Since one int slot is 4 bytes, this is proof the five slots are stuck exactly together. Your addresses will differ, but the "jumping by 4" pattern will be the same.

Predict: if you change int to char, what happens to the address spacing? A char is 1 byte. Predict, then check. (Verified: char arr[5] produced addresses …c3, c4, c5, c6, c7 — jumping by 1.)

Why: hearing "arrays are adjacent" is different from seeing slot numbers jump by 4 with your own eyes. This screen is the root of every formula today.

3-2. The True Identity of an Array Name

Input (arr2.c)

#include <stdio.h>

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};
    printf("arr      = %pn", arr);
    printf("&arr[0]  = %pn", &arr[0]);
    return 0;
}

Compile and run

gcc -Wall arr2.c -o arr2
./arr2
arr      = 0x7ffde7cc5bc0
&arr[0]  = 0x7ffde7cc5bc0

(Verified 2026-09-09. Addresses differ on every run.)

How to read it: both lines printed the same slot. The array name arr was the address of the first slot itself. Rather than the array being "a box containing an address," the name itself is the address, so to speak. Here resolves the mystery of why scanf on a character array in Step 56 needed no &.

3-3. Pointer Arithmetic — Walking to the Next Slot

Input (arr3.c)

#include <stdio.h>

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;
    for (int i = 0; i < 5; i++) {
        printf("*(p + %d) = %d, address %pn", i, *(p + i), p + i);
    }
    return 0;
}

Compile and run

gcc -Wall arr3.c -o arr3
./arr3
*(p + 0) = 10, address 0x7ffffd11b1e0
*(p + 1) = 20, address 0x7ffffd11b1e4
*(p + 2) = 30, address 0x7ffffd11b1e8
*(p + 3) = 40, address 0x7ffffd11b1ec
*(p + 4) = 50, address 0x7ffffd11b1f0

(Verified 2026-09-09. Addresses differ on every run.)

How to read it: we added 0, 1, 2 to p, but the address grows by 0, 4, 8. Moving by "the number you add × the data type’s size" is pointer arithmetic. *(p + i) is "go to the address i slots back and give me the value." The output matches 3-1’s arr[i] exactly.

Why: arr[i] was the polite notation for *(arr + i). Now you can speak arrays in two languages: "index" and "address steps."

3-4. Same Value, Two Syntaxes — A Contrast Experiment

Input (arr4.c)

#include <stdio.h>

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};
    int i = 3;
    printf("arr[i]     = %dn", arr[i]);
    printf("*(arr + i) = %dn", *(arr + i));
    return 0;
}

Compile and run

gcc -Wall arr4.c -o arr4
./arr4
arr[i]     = 40
*(arr + i) = 40

(Verified 2026-09-09.)

How to read it: the same 40. When you write arr[i], the compiler internally rewrites it as *(arr + i) before translating. The square brackets were wrapping paper for humans.

Predict: then would i[arr], written backward, also work? Since it becomes the same expression as *(i + arr)… try compiling it. (Verified: it compiled, it ran, and it printed i[arr] = 40.) Working and being something you should use are different, but if you can explain why it works, you’ve understood today.

3-5. Strings and — Finding the Guard

Input (str0.c)

#include <stdio.h>

int main(void) {
    char s[] = "Hi";
    for (int i = 0; i < 3; i++) {
        printf("s[%d] = %c (value %d)n", i, s[i], s[i]);
    }
    return 0;
}

Compile and run

gcc -Wall str0.c -o str0
./str0
s[0] = H (value 72)
s[1] = i (value 105)
s[2] =   (value 0)

(Verified 2026-09-09.)

How to read it: "Hi" is two letters but three slots. The last slot’s value is 0 — this is the null character , the end guard of the string. It’s invisible with %c (value 0 draws nothing on screen), but printed with %d it revealed itself. H being 72 follows Step 50’s ASCII table exactly.

Why: all of C’s string functions (including printf’s %s) work on "read until you meet this guard." The guard’s existence is the source of every C string rule.

3-6. When There’s No Guard — An Honest Experiment

Input (str1.c)

#include <stdio.h>

int main(void) {
    char s[4] = {'H', 'e', 'l', 'p'};
    printf("%sn", s);
    return 0;
}

Compile and run (run three times)

gcc -Wall str1.c -o str1
./str1
(Run 1) Help
(Run 2) Help
(Run 3) Help

(Verified 2026-09-09.)

How to read it — honestly speaking: the four slots are packed full, so there’s no room for the guard . So %s, having no marker to stop at after reading "Help," keeps reading the neighboring slots until it happens to meet a slot with value 0. In testing, all three runs printed only a clean "Help" — because the slot right next door happened to be 0. This is luck. This behavior, never knowing when it will end, is called "undefined behavior." On another day, with different code, garbage characters may trail after "Help."

Predict: what if you add one slot with char s[5] = {'H','e','l','p'}? What goes into the fifth slot? (Verified: the uninitialized fifth slot becomes 0, and a clean Help printed — the guard stood up on its own, so to speak.)

Why: this "read that crosses over without knowing the end" becomes one scene of information-leak incidents. Today’s experiment is harmless, but the principle is the same as the principle behind incidents big and small.

3-7. Steps Through a Character Array — char Pointer Arithmetic

Let’s apply the steps we learned with int to char.

Input (charwalk.c)

#include <stdio.h>

int main(void) {
    char s[] = "SECURITY";
    char *p = s;
    while (*p != '') {
        printf("%c", *p);
        p++;
    }
    printf("n(total %ld letters)n", p - s);
    return 0;
}

Compile and run

gcc -Wall charwalk.c -o charwalk
./charwalk
SECURITY
(total 8 letters)

(Verified 2026-09-09.)

How to read it: move p one step at a time (p++), print *p, and stop when you meet the guard ''. Being a char pointer, one step is 1 byte. The final p - s is "end address minus start address," which becomes the number of steps — that is, the number of letters. Subtracting one pointer from another gives you "how many slots apart."

Why: C’s string functions (measuring length, copying, comparing) are all built on this kind of walking inside. What strlen does, you just did by hand.


4. Missions & Exercises

Mission — An Array Program That Speaks Two Languages

  1. Create an int score array (five subjects, values up to you).
  2. First output: print every subject and the average using the index style (arr[i]).
  3. Second output: print the same thing again using pointer arithmetic (*(arr + i)). The two results must match.
  4. Print each subject’s address too, and see with your eyes the adjacency (jumping by 4).
  5. Add a mini experiment switched to char (five alphabet letters) and confirm the address spacing changes to 1.
  6. When done, draw a picture in your notebook: a five-slot locker, each slot’s address, an arr arrow pointing at the first slot.

Exercises

Problem 1. In a five-slot array int arr[5], what happens if you read arr[5], and why doesn’t C stop you?

Problem 2. State the relationship between arr and &arr[0], and use it to explain why Step 56’s scanf("%19s", name) needed no &.

Problem 3. Adding 1 to an int pointer grew the address by 4. Why 4 and not 1? And by how much does a char pointer grow?

Problem 4. In C, why must a string array’s size be "letter count + 1"? And based on the 3-6 experiment, explain what incident results from code that forgets this "+1."


5. Model Answers & Completion Criteria

Mission Model Answer

#include <stdio.h>

int main(void) {
    int scores[5] = {80, 92, 75, 88, 95};
    int sum = 0;

    printf("[Index style]n");
    for (int i = 0; i < 5; i++) {
        printf("Subject %d: %d points (address %p)n", i + 1, scores[i], &scores[i]);
        sum += scores[i];
    }
    printf("Average: %dn", sum / 5);

    sum = 0;
    printf("[Pointer style]n");
    for (int i = 0; i < 5; i++) {
        printf("Subject %d: %d pointsn", i + 1, *(scores + i));
        sum += *(scores + i);
    }
    printf("Average: %dn", sum / 5);
    return 0;
}

Run (verified 2026-09-09. Addresses differ on every run)

[Index style]
Subject 1: 80 points (address 0x7ffe83646940)
Subject 2: 92 points (address 0x7ffe83646944)
Subject 3: 75 points (address 0x7ffe83646948)
Subject 4: 88 points (address 0x7ffe8364694c)
Subject 5: 95 points (address 0x7ffe83646950)
Average: 86
[Pointer style]
Subject 1: 80 points
Subject 2: 92 points
Subject 3: 75 points
Subject 4: 88 points
Subject 5: 95 points
Average: 86

How to verify: ① the scores and averages from both styles must match exactly, line by line. ② Check that addresses jump by 4 in hexadecimal (40, 44, 48, 4c, 50). ③ The char mini experiment should jump by 1. ④ The picture must have five slots, addresses, and an arrow pointing at the first slot.

Exercise Answers

Problem 1 answer. The five slots are arr[0]arr[4]; arr[5] is a sixth slot — someone else’s land. Because C doesn’t check boundaries, it reads out whatever garbage value sits there without any warning. This comes from C’s design of "trusting the programmer," which is why keeping to boundaries is always the author’s job.

Problem 2 answer. arr == &arr[0] — an array name is the same value as the address of the first slot. Since the name of a character array like name is already an address, handing it to scanf as-is makes it exactly "the slot to store into." That’s why we attached no &.

Problem 3 answer. Because the unit of pointer arithmetic is not the byte but "one slot of the data type." An int slot is 4 bytes, so addresses grow by 4; a char slot is 1 byte, so they grow by 1. The step size is set by the shoe (the data type).

Problem 4 answer. Because a C string absolutely requires one slot for the null character that announces the end. Forget the "+1" and there’s no room for the guard, so — as in 3-6 — the read crosses into the neighboring slot without knowing the end. An overflowing read leaks garbage values or other people’s data; an overflowing write overwrites the adjacent variable — the starting point of buffer overflow.

Completion Criteria Checklist

  • [ ] I confirmed with address output that array slots are adjacent
  • [ ] I can show by experiment that an array name is the address of the first slot
  • [ ] I confirmed arr[i] equals *(arr + i) by printing both ways
  • [ ] I know adding 1 to a pointer moves it by the data type’s size
  • [ ] I can explain the role of at the end of strings and the incident when it’s missing
  • [ ] I completed the array program that speaks two languages

6. Common Pitfalls & Fixes

Wall 1. Array Indices Start at 0

Symptom: thinking there’s an arr[5], you read arr[5] of a five-slot array.
Cause: the five slots are arr[0]–arr[4]. arr[5] is a sixth slot — someone else’s land.
Fix: memorize "N slots means indices 0 to N-1." Even if you read someone else’s land, C won’t scold you — it hands you a garbage value. That silence is the scary part. Engrave why the condition in for (i = 0; i < 5; i++) is <.

Wall 2. Mistaking p + 1 for 1 Byte

Symptom: you added 1 to an address and are bewildered that it grew by 4.
Cause: the unit of pointer arithmetic is not the byte but one slot of the data type.
Fix: remember "the step size is set by the shoe (the data type)." An int pointer walks 4 bytes at a time; a char pointer walks 1 byte. The address outputs of 3-1 and 3-3 are the evidence.

Wall 3. Sizing a String by Its Letter Count

Symptom: trying to put "Hello" into char s[5] misbehaves.
Cause: "Hello" is five letters, but you need six slots including the guard .
Fix: always size string arrays as "letter count + 1." That’s exactly the calculation behind Step 56’s char name[20] with %19s.

Wall 4. A Guard-less String Prints "Fine"

Symptom: 3-6’s str1.c prints a clean "Help" (in the 2026-09-09 verification, all three runs were clean too).
Cause: the neighboring slot happened to be 0, so the read stopped right away. That’s not the rule being honored — it’s luck.
Fix: don’t use "it works right now" as evidence. A string with no end marker is undefined behavior, with no telling when it ends, and the result changes if the compiler or runtime environment changes. Always leave room for in string arrays.

Wall 5. Handing an Array to a Function Loses Its Length

Symptom: you’re flustered because there’s no way to know the array’s length inside the function.
Cause: handing an array to a function passes only the address of the first slot. The information "it’s five slots" doesn’t travel along.
Fix: passing the length as a separate argument is C’s convention — void print_scores(int arr[], int n). This constraint explains in reverse why C strings needed the guard. Since length couldn’t be passed, an end marker served instead.


7. Summary

Today’s Concepts

Concept One-line explanation
Array Slots of the same data type stuck together in memory
Array name The address of the first slot — arr == &arr[0]
Pointer arithmetic address + i moves by "i × data type size"
Null character () The end guard of strings — one slot with value 0
Undefined behavior Behavior whose result can’t be guaranteed, like a read with no end marker

Today’s Syntax

Syntax What it does
int arr[5] = {10, 20, 30, 40, 50}; Create and initialize a five-slot array
arr[i] The i-th slot — polite notation for *(arr + i)
*(arr + i) Go to the address i slots back and read the value
int *p = arr; A pointer to the array’s first slot
p - s The distance (slot count) between two addresses
'' The null character constant — the end marker of strings

A Sense More Important Than Syntax

Today you saw two things: the incident of reading outside the slots (3-6), and the fact that slots are adjacent (3-1). Combine the two and a scary thought becomes possible — "what if you write outside the slots?" If another variable lives in the slot right next to the array, that variable gets overwritten. This is the starting point of buffer overflow, and as long as C doesn’t check slot boundaries, boundaries are always a human responsibility.

And I hope you’ve grown eyes that read addresses. From today, when you look at address output, look at the spacing of the endings (how many bytes apart they sit — the spacing reveals the data type) and at patterns that persist across repeated runs. An address is not just a long number; it’s a coordinate on the map called memory. If the picture is starting to form in your head — slots stuck together, with arrows walking between them — then you are already the owner of eyes that see memory.


Once every box is checked, Step 59 is complete.