% c-pointers-const-restrict.tex — reading declarations, const placement,
% pointer-to-pointer, arrays of pointers vs pointer to array, function pointers +
% callbacks with void *ctx, restrict + memcpy/memmove, array decay + sizeof,
% pointer arithmetic, dangling pointers.
% Sources: docs/memos/c-pointers-arrays-memory.md, docs/memos/c-language-fundamentals.md
% (Q20-Q22), docs/memos/c-systems.md (Q3, Q6, Q8, Q15), docs/school/qaa/c-systems.md
% (Q18, Q31), docs/school/playground/c-systems/01-const-pointers/,
% docs/school/session/2026-06-24.md (Q3 INCORRECT, Q6 PARTIAL, Q15 INCORRECT).
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/c/c-pointers-const-restrict.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=c kind=concept level=senior platform=embedded new=no round=c-esp32-2026-09-24 topic=language,memory
% @tags: pointers, const-pointer, restrict, function-pointers, void-ctx, array-decay, pointer-arithmetic, dangling-pointer, memcpy, memmove, realloc
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}

\lstdefinestyle{CSheet}{style=printup, language=C,
  morekeywords={uint8_t,uint16_t,uint32_t,size_t,bool,restrict,inline}}

\tikzset{
  mem/.style={cell, minimum width=10mm, minimum height=4.5mm},
  ptr/.style={draw=sheetBlue, thick, fill=sheetBlue!8, font=\ttfamily\scriptsize,
              minimum width=7mm, minimum height=4.5mm, inner sep=1pt},
  dat/.style={draw=sheetGrey, fill=white, font=\ttfamily\scriptsize,
              minimum width=9mm, minimum height=4.5mm, inner sep=1pt},
  ro/.style={draw=sheetRed, very thick, fill=sheetRed!12},
  lbl/.style={font=\scriptsize, text=black!75, inner sep=1pt},
}

\begin{document}

\sheettitle{Pointers · const · restrict · decay · dangling}{c · memo}

\oneliner{Read a declaration \textbf{from the name outward: right first}
(\texttt{[]}, \texttt{()}), \textbf{then left} (\texttt{*}, \texttt{const}).
\texttt{const} guards what is \textbf{immediately left} of it (or right, if it
is first). \texttt{restrict} promises ``this pointer is the only way to that
memory''. A pointer is valid only while its object lives.}

\begin{multicols}{2}

\section{Reading declarations}
\begin{tabular}{@{}lp{47mm}@{}}
\toprule
\textbf{declaration} & \textbf{read as · allowed}\\ \midrule
\texttt{const int *p} & ptr to const int · \texttt{p++} ok, \texttt{*p=1} \textcolor{sheetRed}{error}\\
\texttt{int const *p} & identical to the line above\\
\texttt{int *const p} & const ptr to int · \texttt{*p=1} ok, \texttt{p++} \textcolor{sheetRed}{error}\\
\texttt{const int *const p} & neither moves nor writes\\
\texttt{int **pp} & ptr to ptr — a callee can reseat \emph{your} ptr\\
\texttt{int *a[5]} & array of 5 pointers to int\\
\texttt{int (*a)[5]} & pointer to an array of 5 ints\\
\texttt{int *f(void)} & function returning \texttt{int *}\\
\texttt{int (*f)(void)} & pointer to function returning \texttt{int}\\
\texttt{char *(*x[3])(int)} & array of 3 ptrs to fn(\texttt{int}) $\to$ \texttt{char *}\\
\bottomrule
\end{tabular}

\textbf{Rule of thumb:} \texttt{const} \emph{left} of \texttt{*} = the data is
read-only; \emph{right} of \texttt{*} = the pointer is fixed. Registers:
\texttt{volatile uint32\_t *const REG = (volatile uint32\_t *)0x3FF4403C;}

\section{Function pointers + \texttt{void *ctx}}
\begin{lstlisting}[style=CSheet]
typedef void (*event_cb)(uint8_t evt, void *ctx);
typedef struct { event_cb cb; void *ctx; } listener;
static void on_evt(uint8_t evt, void *ctx) {
  counter *c = ctx;          // void* -> T*: no cast in C
  c->n++;                    // C's "closure": fn + state
}
static counter clicks;       // ctx must OUTLIVE the listener
listener l = { on_evt, &clicks };
l.cb(EVT_PRESS, l.ctx);      // == (*l.cb)(EVT_PRESS, l.ctx)
static const event_cb table[] = { [EVT_A] = on_a,
                                  [EVT_B] = on_b }; // dispatch
\end{lstlisting}

\section{restrict — ``I am the only door''}
\begin{lstlisting}[style=CSheet]
int f(int *a, int *b) { *a = 1; *b = 2; return *a; }
int g(int *restrict a, int *restrict b)
                      { *a = 1; *b = 2; return *a; }
// f must reload *a (b may == a); g may just return 1
\end{lstlisting}
\begin{itemize}
  \item Gain: values stay in registers, loads/stores reorder, loops
        \textbf{vectorise}. Breaking the promise = \textbf{UB}, no diagnostic.
  \item \texttt{memcpy(void *restrict, const void *restrict, size\_t)} —
        overlap is UB. \texttt{memmove} has no \texttt{restrict}: copies in
        the safe direction. \texttt{memmove(buf+2, buf, n)} to shift in place.
  \item C99 keyword; C++ has only the \texttt{\_\_restrict} extension.
\end{itemize}

\section{Arrays, decay, arithmetic}
\begin{itemize}
  \item An array name \textbf{decays} to \texttt{\&a[0]} except under
        \texttt{sizeof}, unary \texttt{\&}, and a string literal initialising
        a \texttt{char} array. \texttt{\&a} is \texttt{int (*)[4]}.
  \item Parameter \texttt{int a[10]} \emph{is} \texttt{int *a}: inside, \texttt{sizeof a}
        = pointer size, so \texttt{sizeof a / sizeof a[0]} lies. Pass a length.
  \item \texttt{p + n} moves \texttt{n * sizeof *p} bytes; \texttt{q - p} = element
        count (\texttt{ptrdiff\_t}), same array only. No arithmetic on \texttt{void *}
        in standard C (GCC treats it as bytes).
\end{itemize}

\columnbreak

\section{Picture — which part is read-only?}
\begin{tikzpicture}[sheet]
  \foreach \y/\decl/\pr/\dr in {0/{const int *p}/0/1, -0.7/{int *const p}/1/0, -1.4/{const int *const p}/1/1}{
    \node[font=\ttfamily\scriptsize, anchor=east] at (2.3,\y) {\decl};
    \ifnum\pr=1 \node[ptr, ro] (p) at (2.9,\y) {p}; \else \node[ptr] (p) at (2.9,\y) {p}; \fi
    \ifnum\dr=1 \node[dat, ro] (d) at (4.4,\y) {42}; \else \node[dat] (d) at (4.4,\y) {42}; \fi
    \draw[flow] (p.east) -- (d.west);
  }
  \node[lbl, anchor=west, text=sheetRed] at (5.1,0) {can't \texttt{*p = 1}};
  \node[lbl, anchor=west, text=sheetRed] at (5.1,-0.7) {can't \texttt{p++}};
  \node[lbl, anchor=west, text=sheetRed] at (5.1,-1.4) {can't either};
  \node[note, anchor=west] at (0,-1.95) {Red = read-only \emph{through this name}. The 42 may still sit in
    RAM and change by another path.};
\end{tikzpicture}

\section{Picture — decay, arithmetic, sizeof}
\begin{tikzpicture}[sheet]
  \foreach \i in {0,1,2,3} \node[mem] (m\i) at (1.05*\i+0.9,0) {a[\i]};
  \node[mem, draw=sheetGrey!60, dashed, text=sheetGrey] (m4) at (5.1,0) {end};
  \foreach \i/\a in {0/1000,1/1004,2/1008,3/100C,4/1010}
    \node[font=\ttfamily\tiny, text=sheetGrey] at (1.05*\i+0.9,-0.38) {0x\a};
  \node[lbl, anchor=east] at (0.3,0) {\texttt{int a[4]}};
  \draw[hot] (0.9,0.85) node[above, lbl]{\texttt{p = a} (decay)} -- (m0.north);
  \draw[hot] (1.95,0.55) node[above right=-1pt, lbl]{\texttt{p+1}: +4 B} -- (m1.north);
  \draw[flow] (5.1,0.85) node[above, lbl]{\texttt{\&a + 1}: +16 B} -- (m4.north);
  \node[lbl, align=left, anchor=north west] at (-0.4,-0.6) {\texttt{sizeof a} = 16 · \texttt{sizeof p} = 4 (ESP32) / 8 (64-bit) ·
    \texttt{a[i]} $\equiv$ \texttt{*(a+i)}\\ \texttt{end} = one-past-the-end: may be formed and compared, never dereferenced.};
\end{tikzpicture}

\section{Dangling pointers — and the fixes}
\begin{itemize}
  \item \textbf{Return of a local:} \texttt{char buf[32]; return buf;} — the frame
        is popped; it may ``work'' until the next call overwrites it. Fix:
        \textbf{caller-provided buffer + size} (embedded default), \texttt{static}
        (not re-entrant), or \texttt{malloc} (caller frees).
  \item \textbf{Use-after-free / double free:} \texttt{free(p); p = NULL;}
        (\texttt{free(NULL)} is a no-op).
  \item \texttt{realloc} may \textbf{move}: every old pointer dangles;
        \texttt{p = realloc(p, n)} leaks \texttt{p} on failure — use a temp.
  \item A \texttt{ctx} pointing at a stack variable, registered as a callback
        from a function that returns = dangling on the first event.
\end{itemize}

\section{Interview traps}
\begin{itemize}
  \trap{\textbf{Your Q3/Q18:} could not write ``pointer to const'' vs ``const
        pointer'' — say the name, go right, then left.}
  \trap{\textbf{Your Q6:} blamed the missing \texttt{\textbackslash 0}; \texttt{char
        buf[32]="hello world"} has one. The bug is \emph{lifetime}.}
  \trap{\textbf{Your Q15/Q31:} \texttt{restrict} = no-aliasing promise; overlap
        $\to$ \texttt{memmove}, never \texttt{memcpy}.}
  \trap{\texttt{const} $\neq$ ROM: \texttt{const int *p} may point at writable RAM.}
  \trap{\texttt{char **} $\to$ \texttt{const char **} is \emph{not} an implicit conversion.}
  \trap{\texttt{char *s = "hi"; s[0]='H';} is UB — use \texttt{char s[] = "hi";}.}
\end{itemize}

\section{Remember}
\textbf{Name, right, left. const guards its left neighbour. restrict = only
door. A pointer never outlives its object.}

\section{Likely questions}
\begin{enumerate}
  \item \texttt{uint32\_t *p; p+1}? — address + 4.
  \item Return a string from a function, embedded? — caller passes \texttt{buf, len}.
  \item Why is \texttt{memcpy} faster than \texttt{memmove}? — may assume no overlap.
  \item When \texttt{int **}? — out-param that allocates, \texttt{argv}, jagged arrays.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} c-memory-layout (segments) · c-undefined-behavior-and-build (UB catalogue) · Swift \texttt{UnsafePointer} / \texttt{withUnsafeBytes} · closures vs \texttt{fn + ctx}}

\end{document}
