Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/chapter-structure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Chapter Markdown Structure
on:
pull_request:
paths:
- '01_low_level/**/README.md'
- 'scripts/check_chapter_structure.py'
- '.github/workflows/chapter-structure.yml'
push:
branches: [master]
paths:
- '01_low_level/**/README.md'
- 'scripts/check_chapter_structure.py'
- '.github/workflows/chapter-structure.yml'
workflow_dispatch:
jobs:
chapter-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Validate Book 01 chapter structure
shell: bash
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
base="${{ github.event.pull_request.base.sha }}"
elif [[ "${{ github.event_name }}" == "push" ]]; then
base="${{ github.event.before }}"
else
python scripts/check_chapter_structure.py
exit
fi
mapfile -t files < <(git diff --name-only --diff-filter=ACMR "$base" HEAD -- '01_low_level/*/README.md')
if (( ${#files[@]} )); then
python scripts/check_chapter_structure.py "${files[@]}"
else
echo "No changed chapter READMEs to validate"
fi
103 changes: 86 additions & 17 deletions 01_low_level/10_linking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Most subtle C++ bugs at scale are **linking bugs**, not syntax or type-system bu

---

## Translation Units
## 1. Translation Units

A **translation unit (TU)** is the result of preprocessing a source file:

Expand All @@ -23,7 +23,7 @@ Each TU is compiled **independently**, then linked together.

---

## Linkage Types
## 2. Linkage Types

### 1. Internal linkage

Expand Down Expand Up @@ -86,7 +86,7 @@ They are **purely compile-time or local constructs**.

---

## Scope vs Linkage (do not confuse)
## 3. Scope vs Linkage (do not confuse)

| Concept | Meaning |
| ----------- | --------------------------------------------------- |
Expand All @@ -109,7 +109,7 @@ C++ adds:

---

## The One Definition Rule (ODR)
## 4. The One Definition Rule (ODR)

The **ODR** governs what may be defined where:

Expand All @@ -129,7 +129,7 @@ Violations lead to:

---

## `static` keyword: historical overload
## 5. `static` keyword: historical overload

`static` has **two unrelated meanings**:

Expand All @@ -147,7 +147,7 @@ static int x; // internal linkage (global)

---

## `const` and linkage
## 6. `const` and linkage

In **C++ (unlike C)**:

Expand All @@ -173,7 +173,7 @@ inline constexpr int ci = 42;

---

## Headers and Inclusion Rules
## 7. Headers and Inclusion Rules

Best practices:

Expand All @@ -185,7 +185,7 @@ Best practices:

---

## Global Initialization Order
## 8. Global Initialization Order

There is **no guaranteed order** of initialization of global objects across TUs.

Expand Down Expand Up @@ -216,7 +216,7 @@ constinit int g = 42;

---

## `extern "C"` and ABI Boundaries
## 9. `extern "C"` and ABI Boundaries

```cpp
extern "C" void c_function();
Expand Down Expand Up @@ -254,7 +254,7 @@ extern "C" {

---

## Name Mangling
## 10. Name Mangling

C++ encodes:

Expand All @@ -278,7 +278,7 @@ Use tools to inspect:

---

## Inline Functions and Variables
## 11. Inline Functions and Variables

### Inline functions

Expand All @@ -297,7 +297,7 @@ inline int counter = 0;

---

## Templates and Linking
## 12. Templates and Linking

Templates are **instantiated at use sites**.

Expand All @@ -315,7 +315,7 @@ template int foo<int>(int);

---

## Symbol Visibility (Shared Libraries)
## 13. Symbol Visibility (Shared Libraries)

On ELF / Mach-O systems (GCC/Clang):

Expand All @@ -336,7 +336,7 @@ Benefits:

---

## Windows DLL Import / Export
## 14. Windows DLL Import / Export

```cpp
__declspec(dllexport)
Expand All @@ -360,7 +360,7 @@ Different from ELF:

---

## Dynamic Linking at Runtime
## 15. Dynamic Linking at Runtime

POSIX example:

Expand All @@ -376,7 +376,7 @@ Requires:

---

## Facts about Linking
## 16. Facts about Linking

```
extern "C" {
Expand All @@ -401,6 +401,75 @@ extern "C" {...}

---

## 17. Modern linking and ABI engineering: details beyond the linker command

### C++17 inline variables preserve one entity across translation units

C++98's common header pattern for global mutable variables required an `extern` declaration in a header and exactly one definition elsewhere. C++17 introduces inline variables, allowing an identical definition to appear in multiple translation units while designating one program-level entity, subject to the one-definition rule. An inline variable should be declared inline on every relevant declaration that defines it; it is not a substitute for managing a shared library's symbol visibility and ABI.

```cpp
// configuration.hpp (C++17)
inline std::atomic<unsigned> active_sessions{0};
// Unlike a namespace-scope const in a header, this is one shared entity
// within a correctly linked program.
```

Keep dynamic initialization costs and initialization dependencies visible even for inline variables. An ODR violation may be ill-formed with no diagnostic required, rather than an ordinary linker error.

### C++20 modules introduce a third linkage category: module linkage

In named-module contexts, some namespace-scope entities have *module linkage*: they can be referenced from translation units attached to the same named module, but are not exposed as external-linkage declarations to unrelated translation units. This is not equivalent to an anonymous namespace's internal linkage or to a namespace nested under a module name.

```cpp
// module-unit.cpp (C++20 modules)
module;
#include <cstdint>
export module telemetry;
int detail_counter = 0; // module linkage in the appropriate context
export int read_counter() { return detail_counter; }
```

This requires an actual module build graph and compiled module interface artifacts; simply renaming a header to a module does not solve dependency or ODR problems. The global module fragment and textual headers preserve relevant traditional linkage rules.

### Dynamic initialization ordering has stronger and weaker guarantees

Within a translation unit, ordered dynamic initialization follows definition order for the applicable variables. Across translation units, initialization order is much more constrained by language rules and may remain indeterminate for many dependencies. The *construct-on-first-use* idiom relies on function-local statics and C++11's thread-safe initialization guarantee.

```cpp
Registry& registry() {
static Registry instance;
return instance;
}
```

Initialization is safe under the specified concurrency rules; destruction-order dependencies can still be problematic at process termination. C++20 `constinit` forces static initialization or rejects the declaration, but does not make an object immutable and does not impose arbitrary cross-module teardown ordering.

### LTO can expose ODR and ABI mistakes that separate compilation concealed

Link-time optimization allows the optimizer to see definitions across translation units and reason more aggressively about inline functions, class layouts, and aliasing. Code that accidentally violates the one-definition rule might appear stable when compiled independently and break under LTO.

```text
TU A: header sees PACKED=1, defines inline access()
TU B: header sees PACKED=0, defines inline access()
Result: potentially different definitions of the same inline entity
```

The problem is not LTO itself: compiling the same entity inconsistently already breaks the C++ program contract. Enable reproducible macro settings and ABI-oriented integration tests, and consider compiler ODR diagnostics where available.

### Weak symbols, COMDAT, and visibility are ABI mechanisms, not ISO C++ contracts

ELF symbol binding, PE/COFF COMDAT folding, Mach-O symbol visibility, and linker-specific dead-stripping are implementation technologies. They help implement inline definitions, templates, and dynamic libraries, but their exact behavior is not a source-language guarantee.

For ELF use `readelf -Ws`, `nm -C`, `objdump -T` and linker map files; for Windows use `dumpbin /symbols` and `dumpbin /exports`. Record symbol exports as part of ABI tests rather than expecting visibility to follow from namespaces or C++ linkage alone.

### C++23 and C++26 preserve explicit module and ABI design obligations

C++23's language and library additions do not replace the one-definition rule, and C++26 reflection or package-oriented tooling does not promise ABI compatibility between independently built C++ libraries. Template instantiations, exception models, RTTI, standard library configurations, and calling conventions still require deliberate compatibility decisions.

Prefer narrow C interfaces for long-lived cross-toolchain plugin boundaries, and document compiler, target, standard library, and exported symbol versions for C++-to-C++ binary interfaces.

---

## Rules worth keeping in working memory

The controlling distinction for linkage and translation units is between the portable language contract and the behavior of a particular compilation environment. C++ gives programmers the ability to build close to system boundaries, but also requires those boundaries to be specified rather than guessed.
Expand Down Expand Up @@ -538,7 +607,7 @@ Modules provide explicit compiled interfaces but retain ABI/linkage concerns.

Module integration improves without eliminating platform linkers.

### C++26: Forthcoming support
### C++26: Evolving tools without fundamental linkage or lookup changes

Expect evolving module-toolchain interoperability; check build-system support.

Expand Down
75 changes: 75 additions & 0 deletions scripts/check_chapter_structure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Validate Book 01 technical-literature chapter contracts (stdlib only)."""
from __future__ import annotations

import argparse
from pathlib import Path
import re
import sys

ENDING = (
"Rules worth keeping in working memory",
"Diagnostics, useful compiler settings and extensions",
"Standards timeline",
"Migration note for Java / Python / C# developers",
"Further reading",
)
YEARS = ("C++98/03", "C++11", "C++14", "C++17", "C++20", "C++23", "C++26")
STUBS = re.compile(r"\b(?:TODO|TBD|FIXME|PLACEHOLDER|fill this in)\b|<placeholder>", re.I)
HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.M)
FENCE = re.compile(r"^\s*(`{3,}|~{3,})", re.M)
CHAPTER = re.compile(r"^(\d{2})_")

def validate(path: Path) -> list[str]:
body = path.read_text(encoding="utf-8")
errors: list[str] = []
chapter = CHAPTER.match(path.parent.name)
if not chapter:
return [f"{path}: not a numbered chapter directory"]
headings = [(len(m.group(1)), m.group(2)) for m in HEADING.finditer(body)]
if not headings or headings[0][0] != 1 or not headings[0][1].startswith(chapter.group(1)):
errors.append("H1 must start with chapter number")
h2 = [h for level, h in headings if level == 2]
if h2[-len(ENDING):] != list(ENDING):
errors.append("final five H2 sections must match the chapter template in order")
for h in ENDING:
if h2.count(h) != 1:
errors.append(f"expected exactly one H2: {h}")
numeric = [int(m.group(1)) for h in h2 if (m := re.match(r"^(\d+)\.\s", h))]
if not numeric or numeric != list(range(1, len(numeric) + 1)):
errors.append(f"numbered H2 sections not consecutive: {numeric}")
found_stubs = STUBS.findall(body)
if found_stubs:
errors.append(f"contains stub markers: {found_stubs[:3]}")
fences = FENCE.findall(body)
if len(fences) % 2:
errors.append("unbalanced fenced code blocks")
timeline_start = body.find("\n## Standards timeline")
migration_start = body.find("\n## Migration note")
if timeline_start >= 0 and migration_start > timeline_start:
timeline = body[timeline_start:migration_start]
missing = [std for std in YEARS if not re.search(r"^### " + re.escape(std) + r"\b", timeline, re.M)]
if missing:
errors.append(f"standards timeline missing: {', '.join(missing)}")
if len(body) < 7000:
errors.append("chapter is unexpectedly short for advanced technical literature")
for item in errors:
print(f"{path}: {item}", file=sys.stderr)
if not errors:
print(f"PASS {path} ({len(numeric)} numbered sections)")
return errors

def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("paths", nargs="*", type=Path,
help="README.md paths; defaults to chapters 01–13")
args = ap.parse_args()
paths = args.paths or sorted(Path("01_low_level").glob("[0-9][0-9]_*/README.md"))
if not paths:
print("No chapter READMEs found", file=sys.stderr)
return 2
failures = sum(bool(validate(p)) for p in paths)
return 1 if failures else 0

if __name__ == "__main__":
raise SystemExit(main())
Loading