5.1.11 · D2C Programming

Visual walkthrough — Multi-dimensional arrays

1,686 words8 min readBack to topic

This is the flagship derivation for the parent topic. If any word below feels unfamiliar (pointer, decay, sizeof), the Connections at the bottom link the pieces.


Step 1 — What "memory" even is: a numbered street of boxes

WHAT. Before any array, picture computer memory as one endless straight street. Every house on it has a number (its address) and holds exactly one byte. A byte is the smallest labelled box the computer can point at.

WHY start here. The whole mystery of 2D arrays dissolves once you accept that memory is one straight line — there is no real "grid" anywhere. The grid lives only in your head.

PICTURE. Look at the figure: a single row of boxes, each with an increasing address written under it. Nothing is 2-dimensional yet.

Figure — Multi-dimensional arrays

Step 2 — One element is not one byte: enter sizeof

WHAT. An int does not fit in one byte-box; it usually needs 4 boxes side by side. We call that count .

WHY this tool and not "just count boxes". If we counted raw bytes, moving "one element forward" would be an ugly jump of 4. Instead we count in elements and multiply by only at the very end. That keeps the arithmetic clean and works for any type (char where , double where , ...). This is exactly why the sizeof operator exists — see sizeof Operator.

PICTURE. The red bracket groups 4 grey byte-boxes into a single int cell. The address of an int is the address of its first byte.

Figure — Multi-dimensional arrays

Step 3 — Lay a 2D array onto the street: row-major order

WHAT. Declare int a[3][4]: we want 3 rows of 4. C places them on the street row by row — all of row 0 first, then all of row 1, then row 2. This ordering is called row-major order (see Row-major vs Column-major).

WHY row-major and not column-major. C had to pick some order. It picked "finish a whole row before starting the next" because int a[3][4] literally means array of 3 things, each thing an int[4] — so the natural block is one full row. (Fortran picked the other order; that is the only difference.)

PICTURE. The 12 cells sit in one line. Coloured underneath: the first 4 belong to row 0, the next 4 to row 1, the last 4 to row 2. The mental grid above is drawn faded — it is a fiction.

Figure — Multi-dimensional arrays
Recall Why "array of arrays" forces row-major

int a[3][4] reads as int (a[3])[4] ::: an array of 3 elements, each element being a full int[4] row — so C stores it one whole row at a time.


Step 4 — To reach row , skip the rows before it

WHAT. We want to land on the start of row . Every complete earlier row holds elements (here ). We pass over of them, so we skip elements.

WHY multiply. Each row is the same length , and they are back-to-back on the street. "Same-sized chunks, repeated times" is exactly what multiplication counts. This is the only place the column count is used — remember that.

PICTURE. The red arrow leaps over full rows, spanning cells, landing at the head of row 2.

Figure — Multi-dimensional arrays

Step 5 — Inside the target row, step across columns

WHAT. We are now at the start of row . To reach column , we step more elements forward.

WHY just add . Inside one row, the columns are stored consecutively (that is what "row-major" gave us). Moving along consecutive cells is plain addition — no multiplication, because we are not skipping repeated blocks, just single steps.

PICTURE. From the head of row 2 (where Step 4 left us) the red arrow takes single step to land on a[2][1].

Figure — Multi-dimensional arrays

Step 6 — Convert elements to bytes and add the base

WHAT. We now know how many elements to skip. Multiply by (Step 2) to get bytes, then add the base address (Step 1) to land on the real house number.

WHY only now multiply by . We deferred it deliberately: counting in elements kept every earlier step type-independent. One clean multiplication converts the whole count at the end.

PICTURE. A number line: start at , jump right by bytes, arrow tip labelled .

Figure — Multi-dimensional arrays

Step 7 — Why vanishes (the edge that matters most)

WHAT. Look back at the formula: the only counts used were , , , . The total number of rows played no part.

WHY this is not a coincidence. To find one address you only ever move forward from the base. Nothing forward-facing needs to know how many rows exist behind or ahead — only how long each row is (). This is exactly why C makes you write int a[][4] (column given, row blank): the compiler can build addresses without , but it cannot without . See Array Decay and Variable Length Arrays (C99).

PICTURE. Two arrays, a[3][4] and a[99][4], drawn stacked. The path to a[2][1] is the identical red arrow in both — extra rows below change nothing.

Figure — Multi-dimensional arrays

Step 8 — The degenerate cases: first cell, and empty steps

WHAT. Check the formula does the sane thing at the boundaries.

  • a[0][0]: skip elements . The very first cell is the base. ✅
  • Last cell a[R-1][C-1] of a[3][4]: elements the 12th (final) cell. ✅ No overshoot.

WHY test these. A formula that fails at zero or at the last element is silently broken everywhere. Confirming both endpoints proves the counting has no off-by-one error.

PICTURE. The street with a[0][0] sitting exactly on (red) and a[2][3] sitting on the final cell (red), the count printed under each.

Figure — Multi-dimensional arrays

The one-picture summary

Everything above in a single diagram: the faded mental grid on top, the real flat street below, the red path skip rows → step → × from base, and the boxed formula.

Figure — Multi-dimensional arrays
Recall Feynman: tell the whole walkthrough in plain words

Memory is one long shelf of tiny numbered boxes. An int takes 4 boxes, so I count in elements and only turn that into boxes at the very end. When I write int a[3][4], the computer does not build a grid — it lays my 12 numbers on the shelf, row after row. To find a[i][j] I start at the array's first box (the base), leap over the complete rows in front of me (each row is boxes, so that leap is ), then walk single boxes into my row. That total, , is how many elements I skipped; multiply by 4 to get boxes, add the base, and I'm standing on the exact byte. I never once needed to know how many rows exist in total — only how long one row is. That single fact is why C lets you leave the row count blank but never the column count.


Connections

  • Multi-dimensional arrays — the parent this derivation belongs to.
  • C Arrays (1D) — the single row is just a 1D array.
  • Pointers in C — the address arithmetic is pointer arithmetic.
  • Array Decay — why a becomes int(*)[4], carrying but not .
  • Pointer to Array vs Array of Pointersint (*p)[4] vs int *p[4].
  • sizeof Operator — the in every step.
  • Row-major vs Column-major — why rows are laid down first.
  • Variable Length Arrays (C99) — passing int a[][cols].