G27
Self-checkedTier 1 17 min readpds.array-2d-address-calculation

Multidimensional arrays and address calculation

Memory is one long line, so a 2-D or 3-D array has to be flattened. Learn row-major and column-major address formulas (with arbitrary bounds), how C declares and passes 2-D arrays, and the index formulas for triangular and tridiagonal matrices that GATE keeps asking for.

Why it exists

A table of numbers, a matrix, an image: data with rows and columns is everywhere, and C offers int m[3][4] for it. But memory has no rows and no columns. It is a single line of numbered bytes. The compiler therefore has to flatten the table into that line, and every access m[i][j] becomes an arithmetic formula on the starting address. GATE asks for exactly this formula, in many dressings: different bounds, row-major or column-major, three dimensions, or only half of a matrix stored.

Prerequisites

Precise definitions

  • Two-dimensional array. In C, int m[3][4] is an array of 3 elements, each of which is an array of 4 ints. There are 3×4=123 \times 4 = 12 ints in one contiguous block. There is no separate "row structure": m[i] is simply the ii-th inner array.
  • Row-major order. The elements are stored row after row: the whole of row 0, then row 1, then row 2. C always uses row-major order. Consecutive elements of one row are adjacent; consecutive elements of one column are a whole row apart.
  • Column-major order. Column after column. Fortran and MATLAB use it. GATE questions state which one applies when they leave C.
  • Address formulas. Let the base address be BB and the element size ww.

For an array with RR rows and CC columns, 0-based indices, row-major:

address(A[i][j])=B+(i⋅C+j)⋅w.\text{address}(A[i][j]) = B + (i \cdot C + j) \cdot w.

For general bounds, rows L1…U1L_1 \ldots U_1 and columns L2…U2L_2 \ldots U_2, let n1=U1−L1+1n_1 = U_1 - L_1 + 1 be the number of rows and n2=U2−L2+1n_2 = U_2 - L_2 + 1 the number of columns:

row-major: B+((i−L1)⋅n2+(j−L2))⋅w,column-major: B+((j−L2)⋅n1+(i−L1))⋅w.\text{row-major: } B + \big((i - L_1) \cdot n_2 + (j - L_2)\big) \cdot w, \qquad \text{column-major: } B + \big((j - L_2) \cdot n_1 + (i - L_1)\big) \cdot w.

For three dimensions a[D1][D2][D3] (0-based, row-major):

address(a[i][j][k])=B+((i⋅D2+j)⋅D3+k)⋅w.\text{address}(a[i][j][k]) = B + \big((i \cdot D_2 + j) \cdot D_3 + k\big) \cdot w.

The last index varies fastest. The pattern: the number you multiply by is always the product of the sizes of the dimensions to the right.

  • What the types are. For int m[3][4]:
ExpressionTypeSizeMeaning
mint [3][4] (decays to int (*)[4])48the whole table
m[1]int [4] (decays to int *)16row 1
m[1][2]int4one element
m + 1int (*)[4]8 (64-bit)moves one whole row (16 bytes)
*(m + 1)int [4]16row 1 again, so m[1] is *(m + 1)
*(*(m + i) + j)int4the same as m[i][j]

Rows: sizeof m / sizeof m[0] =3= 3. Columns: sizeof m[0] / sizeof m[0][0] =4= 4.

  • Pointer to an array versus array of pointers. int (*p)[4] is one pointer to an array of 4 ints (this is what m decays to). int *q[4] is an array of 4 pointers to int. The parentheses decide. with 4-byte int and 8-byte pointers, sizeof *p is 16 and sizeof q is 32.
  • Declaring and passing. Every dimension except the first must be given, because the compiler needs the row length for the formula above. void f(int a[][4], int rows) is the same as void f(int (*a)[4], int rows). A parameter int a[][] is an error.
  • Initialisation. Braces can be nested to give each row. Missing values are zero. If the braces are flattened, values fill the array in row-major order, and only the first dimension may be left out: int b[][3] = {1, 2, 3, 4, 5}; makes 2 rows, {1, 2, 3} and {4, 5, 0}.

How it works

The run below is real. It declares m[3][4] but does not initialise it, so the cells show ?. Look at the addresses: they climb by 4 along a row and then keep climbing into the next row with no gap. The grid you see is only a way of drawing 12 consecutive ints. The last two steps take a pointer to row 2 (m[2], which begins 2⋅4=82 \cdot 4 = 8 cells after the start) and move one cell along it: m[2][1] sits 2⋅4+1=92 \cdot 4 + 1 = 9 cells after the start.

A 3 by 4 array is 12 ints in row-major order

Real run, real addresses

C source

1#include <stdio.h>
2
3int main(void) {
4 int m[3][4];
5 int r, c, k = 0;
6 for (r = 0; r < 3; r++) {
7 for (c = 0; c < 4; c++) {
8 m[r][c] = k++;
9 }
10 }
11 int *row = m[2];
12 int *p = row + 1;
13 return 0;
14}

Stack

mint

?…d5d0
[0][0]
?…d5d4
[0][1]
?…d5d8
[0][2]
?…d5dc
[0][3]
?…d5e0
[1][0]
?…d5e4
[1][1]
?…d5e8
[1][2]
?…d5ec
[1][3]
?…d5f0
[2][0]
?…d5f4
[2][1]
?…d5f8
[2][2]
?…d5fc
[2][3]

Amber = changed in this step. Hover a cell for its full address. Arrows show where a pointer points; the small tag under a cell names the pointers aimed at it.

Step 1

m has 3 rows of 4 ints: 12 cells in one block. It is uninitialised, so the cells show ?. Look at the addresses: they increase by 4 along a row and then continue straight into the next row.

1 / 6
m[r][c] lives at start + (r * 4 + c) cells. The grid is only how we draw it; memory is one line. Produced by: real execution: every address and value is read from memory by the running program at each step. gcc (Debian 14.2.0-19) 14.2.0. gcc -O0 -o prog prog.c && setarch x86_64 -R ./prog (ASLR off so the numbers are stable)

Triangular and tridiagonal matrices

A symmetric, triangular or tridiagonal n×nn \times n matrix has many entries that are known to be 0 (or repeated). Instead of n2n^2 cells, only the useful entries are stored, row by row, in a 1-D array, and an index formula converts (i,j)(i, j) to a position.

Lower triangular (entries with j≤ij \le i), stored row-wise, 0-based. Rows 0, 1, ..., i−1i-1 hold 1+2+⋯+i=i(i+1)/21 + 2 + \cdots + i = i(i+1)/2 entries before row ii, and (i,j)(i, j) is the (j+1)(j+1)-th entry in its row:

index(i,j)=i(i+1)2+j,total=n(n+1)2.\text{index}(i, j) = \frac{i(i+1)}{2} + j, \qquad \text{total} = \frac{n(n+1)}{2}.

With 1-based indices and 1-based positions, the formula is i(i−1)2+j\frac{i(i-1)}{2} + j, so the address is B+(i(i−1)2+j−1)⋅wB + \big(\frac{i(i-1)}{2} + j - 1\big) \cdot w.

Upper triangular (entries with j≥ij \ge i), stored row-wise, 0-based. Row rr holds n−rn - r entries, so before row ii there are ∑r<i(n−r)=i⋅n−i(i−1)2\sum_{r<i}(n - r) = i \cdot n - \frac{i(i-1)}{2} entries:

index(i,j)=i⋅n−i(i−1)2+(j−i).\text{index}(i, j) = i \cdot n - \frac{i(i-1)}{2} + (j - i).

Tridiagonal (entries with ∣i−j∣≤1|i - j| \le 1), stored row-wise, 0-based. Row 0 has 2 entries, every other row 3 (except the last, which has 2). The formula collapses to

index(i,j)=2i+j,total=3n−2.\text{index}(i, j) = 2i + j, \qquad \text{total} = 3n - 2.

The lower-triangular and tridiagonal formulas are checked by programs in the worked examples (the program lists the entries in storage order and compares the position it counts with the formula). The upper-triangular, general-bounds and column-major formulas were checked by a one-off script during the build.

Worked examples

EX 1Sizes, rows, columns and one offset

Easy
#include <stdio.h>
 
int main(void) {
    int m[3][4];
    printf("%zu %zu %zu %zu %zu %td\n", sizeof m, sizeof m[0], sizeof m[0][0],
           sizeof m / sizeof m[0], sizeof m[0] / sizeof m[0][0],
           (char *)&m[1][2] - (char *)&m[0][0]);
    return 0;
}

Output (4-byte int):

48 16 4 3 4 24

Reading it. The whole table is 48 bytes, a row is 16, an element is 4. Dividing gives 3 rows and 4 columns. m[1][2] is (1⋅4+2)⋅4=24(1 \cdot 4 + 2) \cdot 4 = 24 bytes after the first element, the same formula as in the animation.

EX 2Pointer forms of m[i][j], and pointer to a row

Medium
#include <stdio.h>
 
int main(void) {
    int m[3][4];
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 4; j++) m[i][j] = i * 4 + j;
    int (*pa)[4] = m;
    printf("%d %td %d %zu %zu\n", *(*(m + 2) + 1), (char *)(pa + 1) - (char *)pa,
           pa[1][3], sizeof *pa, sizeof pa);
    return 0;
}

Output (64-bit):

9 16 7 16 8

Reading it. *(*(m + 2) + 1): m + 2 is row 2, *(m + 2) is that row decayed to a pointer to its first int, + 1 moves one int, and the outer * reads it: m[2][1]=2⋅4+1=9m[2][1] = 2 \cdot 4 + 1 = 9. pa is a pointer to a row of 4 ints, so pa + 1 moves 16 bytes. pa[1][3] is m[1][3]=7m[1][3] = 7. sizeof *pa is the size of the row (16), sizeof pa the size of one pointer (8).

EX 3Row-major and column-major with arbitrary bounds

GATE level

An array A[1..5][1..4] has 2-byte elements and base address 1000. Find the address of A[3][2] in row-major and in column-major order.

Dimensions. n1=5n_1 = 5 rows, n2=4n_2 = 4 columns.

Row-major. 1000+((3−1)⋅4+(2−1))⋅2=1000+9⋅2=1018.1000 + \big((3 - 1) \cdot 4 + (2 - 1)\big) \cdot 2 = 1000 + 9 \cdot 2 = 1018.

Column-major. 1000+((2−1)⋅5+(3−1))⋅2=1000+7⋅2=1014.1000 + \big((2 - 1) \cdot 5 + (3 - 1)\big) \cdot 2 = 1000 + 7 \cdot 2 = 1014.

Check by counting. Row-major: rows 1 and 2 hold 2⋅4=82 \cdot 4 = 8 elements, then A[3][1] is the 9th, and A[3][2] is the 10th, so 9 elements come before it. Column-major: column 1 holds 5 elements, then A[1][2], A[2][2] come before A[3][2], so 7 elements come before it. Both match.

EX 4Initialisers, partial rows and the omitted first dimension

Medium
#include <stdio.h>
 
int main(void) {
    int a[2][3] = {{1}, {4, 5}};
    int b[][3] = {1, 2, 3, 4, 5};
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) printf("%d ", a[i][j]);
        printf("| ");
    }
    printf("\n%zu:", sizeof b / sizeof b[0]);
    for (int i = 0; i < 2; i++)
        for (int j = 0; j < 3; j++) printf(" %d", b[i][j]);
    printf("\n");
    return 0;
}

Output:

1 0 0 | 4 5 0 |
2: 1 2 3 4 5 0

Reading it. Each inner brace initialises one row, and anything not given is zero. In b the values are not grouped, so they fill row 0 completely and then row 1, and the compiler counts that 5 values need 2 rows of 3 cells. The unused last cell is zero. This is legal C, but gcc's -Wall warns about the missing braces, because grouping each row in braces is clearer.

EX 5Three dimensions

GATE level
#include <stdio.h>
 
int main(void) {
    int a[2][3][4];
    printf("%td %zu\n", (char *)&a[1][2][3] - (char *)&a[0][0][0], sizeof a);
    return 0;
}

Output (4-byte int):

92 96

Check with the formula. ((1⋅3+2)⋅4+3)⋅4=(5⋅4+3)⋅4=23⋅4=92((1 \cdot 3 + 2) \cdot 4 + 3) \cdot 4 = (5 \cdot 4 + 3) \cdot 4 = 23 \cdot 4 = 92. If the base address is 5000, the address is 5092. The full array has 2⋅3⋅4=242 \cdot 3 \cdot 4 = 24 ints, 96 bytes, so a[1][2][3] is the very last element, at offset 96−4=9296 - 4 = 92.

EX 6Lower triangular matrix stored row by row

GATE level
#include <stdio.h>
 
int main(void) {
    int n = 5, idx = 0, found = -1;
    for (int i = 0; i < n; i++)
        for (int j = 0; j <= i; j++) {
            if (i == 3 && j == 2) found = idx;
            idx++;
        }
    printf("%d %d %d\n", found, 3 * 4 / 2 + 2, idx);
    return 0;
}

Output:

8 8 15

Reading it. The loops list the stored entries in storage order (row 0 has 1, row 1 has 2, ...). Entry (3,2)(3, 2) turns out to be at position 8, and the formula i(i+1)/2+j=6+2=8i(i+1)/2 + j = 6 + 2 = 8 agrees. In total 5⋅6/2=155 \cdot 6 / 2 = 15 entries are stored instead of 25.

With 1-based indices. The same physical entry is called (4,3)(4, 3) when rows and columns are numbered from 1. The 1-based formula gives position i(i−1)2+j=4⋅32+3=9\frac{i(i-1)}{2} + j = \frac{4 \cdot 3}{2} + 3 = 9 (counting from 1), which is position 8 when counting from 0, and the address is B+8wB + 8w. The two conventions describe the same cell.

EX 7Tridiagonal matrix: index 2i + j

GATE level
#include <stdio.h>
 
int main(void) {
    int n = 6, idx = 0, ok = 1;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (j >= i - 1 && j <= i + 1) {
                if (idx != 2 * i + j) ok = 0;
                idx++;
            }
    printf("%d %d %d\n", ok, idx, 3 * n - 2);
    return 0;
}

Output:

1 16 16

Reading it. The loop visits the entries with ∣i−j∣≤1|i - j| \le 1 in row-major order and counts them. For every one of them, the running count equals 2i+j2i + j (ok stays 1), and the total is 3n−2=163n - 2 = 16. So to find the address of (i,j)(i, j) in a tridiagonal matrix stored row-wise, use B+(2i+j)⋅wB + (2i + j) \cdot w (0-based).

EX 8Passing a 2-D array to a function

Medium
#include <stdio.h>
 
int sum(int a[][4], int rows) {
    int s = 0;
    printf("%zu %zu ", sizeof a, sizeof a[0]);
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < 4; j++) s += a[i][j];
    return s;
}
 
int main(void) {
    int m[3][4];
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 4; j++) m[i][j] = i * 4 + j;
    printf("%d\n", sum(m, 3));
    return 0;
}

Output (64-bit):

8 16 66

Reading it. int a[][4] is really int (*a)[4], a pointer to a row of 4 ints, so sizeof a is 8 (one pointer) and sizeof a[0] is 16 (one row). The function cannot know the number of rows: that is why rows is passed. The column count 4 is part of the type and is what lets a[i][j] compute i⋅4+ji \cdot 4 + j. The sum is 0+1+⋯+11=660 + 1 + \cdots + 11 = 66. (gcc warns about sizeof a, which is the point of this trap.)

Cheat box

Cheat box

  • int m[R][C] == RR rows of CC ints. R⋅CR \cdot C cells, contiguous, row-major in C.
  • Row-major, 0-based: B+(i⋅C+j)⋅wB + (i \cdot C + j) \cdot w. Bounds L1..U1L_1..U_1, L2..U2L_2..U_2: B+((i−L1)⋅n2+(j−L2))⋅wB + ((i - L_1) \cdot n_2 + (j - L_2)) \cdot w.
  • Column-major: B+((j−L2)⋅n1+(i−L1))⋅wB + ((j - L_2) \cdot n_1 + (i - L_1)) \cdot w.
  • 3-D: B+((i⋅D2+j)⋅D3+k)⋅wB + ((i \cdot D_2 + j) \cdot D_3 + k) \cdot w. Multiply by the sizes to the right.
  • m + 1 moves a row. m[i] is a row. *(*(m + i) + j) is m[i][j]. sizeof m[0] is a row.
  • Parameter int a[][C] is int (*a)[C]. Only the first dimension may be omitted.
  • Lower triangular, row-wise, 0-based: i(i+1)/2+ji(i+1)/2 + j (n(n+1)/2n(n+1)/2 cells). Upper: in−i(i−1)/2+(j−i)i n - i(i-1)/2 + (j - i). Tridiagonal: 2i+j2i + j (3n−23n - 2 cells).
  • int (*p)[4]: pointer to a row. int *q[4]: array of 4 pointers.

Traps and GATE patterns

Connections

The row-major formula is the one-dimensional array formula applied twice. The type rules rely on pointer arithmetic and on the way an array name decays. The reason loops that run along rows are faster than loops that run down columns is the cache, which is the subject of cache mapping: in row-major order the elements of a row are neighbours in memory. Passing a 2-D array to a function is a special case of parameter passing.

PYQs

5 previous year questions from your book. I re-solved each one myself (5 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs

year not read yetQ7 · page 40 of the book
Consider the following ANSI C program.
#include <stdio.h>
int main() {
    int arr[4][5];
    int i, j;
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 5; j++) {
            arr[i][j] = 10 * i + j;
        }
    }
    printf("%d", *(arr[1] + 9));
    return 0;
}

What is the output of the above program?
  • (a)14
  • (b)24
  • (c)30
  • (d)20
GATE 2020 · NAT · 1 markQ8 · page 40 of the book
Consider the following C program.
#include
int main () {
int a [4] [5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10},{11, 12,
13, 14, 15}, {16, 17, 18, 19, 20}};
printf ("%d\n", *(* (a+**a+2) +3));
return (0);
}

The output of the program is _______.
GATE 2015 · Set 1 · MCQ · 1 markQ19 · page 43 of the book
What is the output of the following C code? Assume
that the address of x is 2000 (in decimal) and an integer
requires four bytes of memory.
#include <stdio.h>
int main()
{
unsigned int x[4][3] = {{1, 2, 3}, {4, 5, 6},
{7, 8, 9}, {10, 11, 12}};
printf("%u, %u, %u", x+3, *(x+3), *(x+2)+3);
}
  • (a)2036, 2036, 2036
  • (b)2012, 4, 2204
  • (c)2036, 10, 10
  • (d)2012, 4, 6
GATE 2014 · MCQQ19 · page 282 of the book
For a C program accessing X[i][j][k], the following
intermediate code is generated by a compiler. Assume
that the size of an integer is 32 bits and the size of a
character is 8 bits.

t0 = i * 1024

t1 = j * 32

t2 = k * 4

t3 = t1 + t0

COMPILER DESIGN

t4 = t3 + t2

t5 = x[t4]

Which one of the following statements about the
source code for the C program is CORRECT?
  • (a)X is declared as "int X[32][32][8]".
  • (b)X is declared as "int X[4][1024][32]".
  • (c)X is declared as "char X[4][32][8]".
  • (d)X is declared as "char X[32][16][2]".
GATE 2015 · Set 3 · MCQQ2 · page 53 of the book
Consider the following two C code segments. Y
and X are one and two dimensional arrays of size n
and n×n respectively, where 2 ≤n≤10. Assume that
in both code segments, elements of Y are initialized
to 0 and each element X[i][j] of array X is
initialized to i+j. Further assume that when stored
in main memory all elements of X are in same main
memory page frame.

Code segment 1:
// initialize elements of Y to 0
// initialize elements of X[i][j] of X to i+j
for (i=0; i<n; i++)
Y[i] += X[0][i];
Code segment 2:
// initialize elements of Y to 0
// initialize elements of X[i][j] of X to i+j
for (i=0; i<n; i++)
Y[i] += X[i][0];
Which of the following statements is/are correct?

S1: Final contents of array Y will be same in both
code segments
S2: Elements of array X accessed inside the for loop
shown in code segment 1 are contiguous in main
memory
S3: Elements of array X accessed inside the for loop
shown in code segment 2 are contiguous in main
memory
  • (a)Only S2 is correct
  • (b)Only S3 is correct
  • (c)Only S1 and S2 are correct
  • (d)Only S1 and S3 are correct

Self-check

Question 1

int a[10][20] is stored row-major, 4-byte int, base address 2000. What is the address of a[3][5]?

Show answer

2000+(3⋅20+5)⋅4=2000+65⋅4=2000+260=22602000 + (3 \cdot 20 + 5) \cdot 4 = 2000 + 65 \cdot 4 = 2000 + 260 = 2260.

Question 2

For double m[4][6] (8-byte double), what are sizeof m, sizeof m[0] and sizeof m[0][0]?

Show answer

4⋅6⋅8=1924 \cdot 6 \cdot 8 = 192 bytes, a row is 6⋅8=486 \cdot 8 = 48 bytes, an element is 8 bytes.

Question 3

What does this print?

#include <stdio.h>
 
int main(void) {
    int m[2][3] = {{1, 2, 3}, {4, 5, 6}};
    printf("%d %d\n", *(*(m + 1) + 1), **m + *(*m + 2));
    return 0;
}
Show answer

*(*(m + 1) + 1) is m[1][1]=5m[1][1] = 5. **m is m[0][0]=1m[0][0] = 1, and *(*m + 2) is m[0][2]=3m[0][2] = 3, so the sum is 4.

5 4

Question 4

A lower triangular n×nn \times n matrix is stored row by row in a 1-D array using 1-based indexing. At what 1-based position is element (4,2)(4, 2), and what is its address if the base is BB and each cell is ww bytes?

Show answer

Position =i(i−1)2+j=4⋅32+2=8= \frac{i(i-1)}{2} + j = \frac{4 \cdot 3}{2} + 2 = 8. The address is B+(8−1)⋅w=B+7wB + (8 - 1) \cdot w = B + 7w.

Question 5

A[0..9][0..4] is stored column-major with 2-byte elements and base 100. What is the address of A[6][3]?

Show answer

There are 10 rows. 100+(3⋅10+6)⋅2=100+72=172100 + (3 \cdot 10 + 6) \cdot 2 = 100 + 72 = 172.

Revision card

One-screen revision

  • C 2-D arrays are arrays of rows, contiguous, row-major. m + 1 moves a row.
  • Row-major: B+((i−L1)⋅n2+(j−L2))⋅wB + ((i - L_1) \cdot n_2 + (j - L_2)) \cdot w. Column-major swaps the roles.
  • 3-D: multiply each index by the product of the sizes of the dimensions to its right.
  • sizeof m / sizeof m[0] rows; sizeof m[0] / sizeof m[0][0] columns.
  • Parameters: int a[][C] == int (*a)[C]. Give all but the first dimension.
  • Lower triangular row-wise: i(i+1)/2+ji(i+1)/2 + j. Tridiagonal row-wise: 2i+j2i + j. (0-based.)
  • Number of rows from bounds is U−L+1U - L + 1.

References

  • ·Kernighan and Ritchie, The C Programming Language, 2nd ed., Sections 5.7 Multi-dimensional Arrays, 5.9 Pointers vs. Multi-dimensional Arrays, 4.9 Initialization
  • ·ISO/IEC 9899:2011 (C11), Sections 6.2.5 Types (array types), 6.5.2.1 Array subscripting, 6.7.6.2 Array declarators, 6.7.6.3 Function declarators (parameter adjustment), 6.7.9 Initialization

Verification

The animation replays a real run of an instrumented C program: every address and value is read from memory at each step, and the test suite checks that the 12 cells have consecutive 4-byte addresses and that m[2][1] is exactly (2 * 4 + 1) * 4 bytes after the start. All C programs on this page are compiled with -Wall -Wextra -Werror (the one marked warns is a deliberate trap) and run by npm run verify:c; they include enumeration programs that check the lower-triangular, tridiagonal and 3-D formulas. The general-bounds formulas (row-major and column-major) and the upper-triangular formula were additionally checked by a one-off enumeration script during the build, but that script is not part of the repository. The textbook section numbers were written from memory and have NOT been checked against the books. The prose and the GATE-pattern claims have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.

last verified: never