% c-undefined-behavior-and-build.tex — the UB catalogue and why the optimiser
% exploits it, warnings + sanitizers, the build pipeline, translation units,
% linkage + ODR, header guards, macro pitfalls, static vs dynamic libraries,
% linker map / sections.
% Sources: docs/memos/c-undefined-behavior.md, docs/memos/c-preprocessor-build.md,
% docs/memos/c-strings-io.md, docs/memos/c-systems.md (Q10, sections 10-11, 13),
% docs/school/qaa/c-systems.md (Q22 PARTIAL, Q23 PARTIAL, Q25, Q29, Q30, Q32, Q33).
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/c/c-undefined-behavior-and-build.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,build,debugging
% @tags: undefined-behavior, strict-aliasing, signed-overflow, sanitizers, translation-unit, linker, linkage, odr, linker-script, gc-sections, macros, static-library
\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{
  file/.style={draw=sheetGrey, fill=white, font=\ttfamily\scriptsize,
               minimum height=4.5mm, inner sep=1.5pt},
  tool/.style={font=\ttfamily\tiny, text=sheetOrange, inner sep=1pt},
  lbl/.style={font=\scriptsize, text=black!75, inner sep=1pt},
}

\begin{document}

\sheettitle{Undefined behaviour · the build: preprocess, compile, link}{c · memo}

\oneliner{\textbf{UB} = the standard places \emph{no requirements} on the
result — and the optimiser \textbf{assumes it never happens}, so it deletes
checks and rewrites code around it; the bug shows up far away, often only at
\texttt{-O2}. \textbf{Building} = per \emph{translation unit} preprocess
$\to$ compile $\to$ assemble to a \texttt{.o}, then \textbf{link} every
\texttt{.o} + library into one image with real addresses.}

\begin{multicols}{2}

\section{The UB catalogue}
\begin{tabular}{@{}ll@{}}
\toprule
\textbf{UB} & \textbf{example · the defined alternative}\\ \midrule
signed overflow & \texttt{INT\_MAX + 1} · unsigned wraps (defined)\\
shift $\geq$ width & \texttt{x << 32}, \texttt{1 << 31}, count $< 0$ · \texttt{1u << 31}\\
out of bounds & \texttt{a[5]} of \texttt{int a[5]}, reads too\\
null deref & \texttt{*p} with \texttt{p == NULL} · check \emph{before} use\\
uninitialised read & \texttt{int x; return x;} · initialise it\\
use-after-free & \texttt{free(p); *p} · \texttt{p = NULL} after free\\
strict aliasing & \texttt{*(uint32\_t *)\&f} · \texttt{memcpy}, \texttt{union}\\
unsequenced & \texttt{i = i++;} \texttt{a[i] = i++;} · split them\\
data race & 2 threads, 1 writes, no sync · \texttt{\_Atomic}, mutex\\
literal write & \texttt{char *s = "hi"; s[0]='H';} · \texttt{char s[]}\\
div by zero & \texttt{x / 0}, \texttt{INT\_MIN / -1} · (float: inf/NaN)\\
misaligned & cast to \texttt{uint32\_t *} of \texttt{buf+1} · \texttt{memcpy}\\
\bottomrule
\end{tabular}

\section{Why the optimiser ``breaks'' it}
\begin{lstlisting}[style=CSheet]
int read_dev(struct dev *d) {
  int v = d->val;     // deref => compiler infers d != NULL
  if (!d) return -1;  // ...so this check is DELETED
  return v;
}
bool will_wrap(int x) { return x + 1 < x; } // folded: false
\end{lstlisting}
\begin{itemize}
  \item Not ``a garbage value'': a promise \emph{you} made. Debug
        (\texttt{-O0}) works, \texttt{-O2} breaks $\to$ latent UB. Remove the
        UB; never ``fix'' it by lowering \texttt{-O}.
  \item Tests can't prove absence: a path may look right today.
\end{itemize}

\section{Catch it}
\begin{itemize}
  \item \texttt{-Wall -Wextra -Werror} (+ \texttt{-Wshadow -Wconversion}).
  \item \texttt{-fsanitize=address}: OOB, use-after-free/return, double free,
        leaks. \texttt{-fsanitize=undefined}: overflow, shifts, null,
        misaligned. \texttt{-fsanitize=thread}: races (not with ASan).
        Uninitialised reads: clang MSan / Valgrind. Run on host unit tests.
  \item On the ESP32: heap poisoning, stack-smashing protection, FreeRTOS
        stack canaries (menuconfig); \texttt{cppcheck}, \texttt{clang -{}-analyze}.
\end{itemize}

\section{Static vs dynamic libraries}
\texttt{.a} = archive of \texttt{.o}; only needed objects are copied in at
link; order matters (\texttt{gcc main.o -lfoo}). \texttt{.so}/\texttt{.dylib}
= loaded at run time, shared, updatable, needs PIC. MCU firmware is all static
(each ESP-IDF component is a \texttt{.a}).

\columnbreak

\section{Picture — the pipeline}
\begin{tikzpicture}[sheet]
  \node[file] (c1) at (0,0) {main.c};
  \node[file] (i1) at (1.55,0) {main.i};
  \node[file] (s1) at (3.05,0) {main.s};
  \node[file] (o1) at (4.5,0) {main.o};
  \draw[flow] (c1) -- node[tool, above]{cpp \texttt{-E}} (i1);
  \draw[flow] (i1) -- node[tool, above]{cc1 \texttt{-S}} (s1);
  \draw[flow] (s1) -- node[tool, above]{as \texttt{-c}} (o1);
  \node[file] (c2) at (0,-0.65) {uart.c};
  \node[file] (o2) at (4.5,-0.65) {uart.o};
  \draw[flow, dashed] (c2) -- node[lbl, above]{same, own TU} (o2);
  \node[file, fill=sheetBrown!10] (a) at (4.5,-1.3) {libfoo.a};
  \node[file, draw=sheetBlue, very thick, fill=sheetBlue!8] (e) at (6.35,-0.65) {app.elf};
  \foreach \n in {o1,o2,a} \draw[hot] (\n.east) -- (e.west);
  \node[tool, above] at (6.35,-0.35) {ld + script};
  \node[file] (b) at (6.35,-1.45) {app.bin};
  \draw[flow] (e) -- node[tool, right]{objcopy} (b);
  \draw[decorate, decoration={brace, mirror, amplitude=3pt}] (-0.4,-1.05) -- (3.6,-1.05)
    node[midway, below=2pt, lbl, align=center]{per translation unit (a \texttt{.c} after \texttt{\#include})\\
    macros gone, \texttt{extern} calls = placeholders};
\end{tikzpicture}

\section{Linkage and the one-definition rule}
\begin{itemize}
  \item \textbf{External} (default for functions/globals): one name across
        TUs. \textbf{Internal}: \texttt{static} at file scope = private to
        the \texttt{.c}. \textbf{None}: locals. (\texttt{static} in a
        function = storage, not linkage.)
  \item Header \emph{declares} (\texttt{extern int count;}), exactly one
        \texttt{.c} \emph{defines} (\texttt{int count = 0;}). \texttt{int
        count;} in a header $\to$ \textbf{multiple definition} (GCC $\geq$ 10:
        \texttt{-fno-common}). Declared, never defined $\to$
        \textbf{undefined reference}.
  \item Linker: resolves symbols, relocates to final addresses, places
        sections by the \textbf{linker script} (\texttt{.text}/\texttt{.data}/
        \texttt{.bss}/IRAM $\to$ flash/RAM), drops dead code
        (\texttt{-ffunction-sections -fdata-sections -Wl,-{}-gc-sections}).
  \item \texttt{\_\_attribute\_\_((weak))}: default, overridden by any strong
        definition. \texttt{-Wl,-Map=app.map}, \texttt{size}, \texttt{nm},
        \texttt{idf.py size-components} show who uses the flash.
\end{itemize}

\section{Headers and macros}
\begin{lstlisting}[style=CSheet]
#ifndef UART_H          /* or: #pragma once */
#define UART_H
#define SQ(x)   ((x) * (x))       // parens: arg AND body
#define SWAP(a,b) do{ int t_=(a); (a)=(b); (b)=t_; }while(0)
static inline int sq(int x) { return x * x; }  // prefer
#endif
\end{lstlisting}
\begin{itemize}
  \item \texttt{SQ(i++)} evaluates \texttt{i++} twice (UB); \texttt{do\{\}while(0)}
        makes \texttt{if (c) SWAP(x,y); else} parse; \texttt{\#if FOO} with
        \texttt{FOO} undefined is silently 0. \texttt{\#x} stringifies, \texttt{a\#\#b} pastes.
\end{itemize}

\section{Interview traps}
\begin{itemize}
  \trap{\textbf{Your Q22:} UB is not ``may be 0 or -1'' — no requirements; name 3 kinds.}
  \trap{\textbf{Your Q23:} the linker's jobs: symbols, addresses, linker
        script, \texttt{-{}-gc-sections} — not vague ``optimisations''.}
  \trap{\texttt{-O0} debug · \texttt{-Og} ESP-IDF default · \texttt{-Os} size · \texttt{-O2} speed.}
\end{itemize}

\section{Remember}
\textbf{UB is a promise you made to the optimiser. cpp $\to$ cc $\to$ as per TU,
then ld once. Declare in the \texttt{.h}, define once in a \texttt{.c}.}

\section{Likely questions}
\begin{enumerate}
  \item Undefined reference vs multiple definition? — never defined vs defined twice.
  \item \texttt{static} on a global? — internal linkage: invisible to other \texttt{.c} files.
  \item Why \texttt{memcpy} for type punning? — aliasing-safe; compiles to one load.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} c-volatile-atomics-isr (data races) · c-pointers-const-restrict · c-memory-layout (sections) · Swift: \texttt{-Ounchecked}, overflow traps}

\end{document}
