From 4e3e205b72ea7f21ed036db2f4e1384ae26ad321 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 00:55:39 +0700 Subject: [PATCH 1/8] docs: expand cross-language ABI and C/C++ compatibility across standards --- 01_low_level/12_c_cpp/README.md | 130 +++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/01_low_level/12_c_cpp/README.md b/01_low_level/12_c_cpp/README.md index a03b8c1..dd0974a 100644 --- a/01_low_level/12_c_cpp/README.md +++ b/01_low_level/12_c_cpp/README.md @@ -1,4 +1,4 @@ -# 12. C/C++ Compatibility +# 12. C and C++ Compatibility from C++98 to C++26 This chapter focuses on **principal language and toolchain differences** that matter when you: - build mixed C/C++ projects @@ -9,7 +9,7 @@ This chapter focuses on **principal language and toolchain differences** that ma Some files are **C-only** (`.c`) and some are **C++** (`.cpp`) -## Files +## 1. Files 1. `01_name_mangling_and_overload.cpp` - C++ name mangling, why overloads work @@ -42,7 +42,7 @@ Some files are **C-only** (`.c`) and some are **C++** (`.cpp`) - C++ features that C doesn't have (RAII/classes/templates/etc.) - a correct "C ABI facade" pattern: opaque handles + create/destroy + error codes -## ABI safety checklist for mixed projects +## 2. ABI safety checklist for mixed projects - `extern "C"` changes **linkage / mangling**, not C++ semantics. - Never let C++ exceptions cross into C: catch inside and return error codes. @@ -51,6 +51,128 @@ Some files are **C-only** (`.c`) and some are **C++** (`.cpp`) --- +## 3. C and C++ are different languages sharing an ABI only by agreement + +The shared heritage of C and C++ makes mixed-language projects seem simpler than they are. Compiling the same header as C17 and C++20 can produce different types, different name lookup, different initialization, and different interpretations of an expression. The interoperability contract is therefore an explicit interface between two languages, not an assumption that C++ is a strict superset of C. + +### C linkage affects language linkage, not everything the ABI requires + +Declaring a function `extern "C"` gives it C language linkage, including rules governing its name and function type. This does not guarantee identical calling conventions across all targets, compatible definitions of C and C++ structs, or a stable exception ABI. + +```cpp +extern "C" int codec_decode(const unsigned char* bytes, + std::size_t length) noexcept; +``` + +The corresponding C header must use declarations legal in C, such as `size_t` from ``. Avoid putting `noexcept` in a shared C declaration; use conditional macros guarded by `__cplusplus`. An exported function catches every C++ exception internally and reports errors through a C-compatible result. + +### The same `struct` layout is a requirement to verify, not assume + +C and C++ often agree on the layout of simple record types under a matching target ABI. But a C++ class with virtual functions, nonstandard layout, inheritance, or implementation-specific packing is not a candidate for direct C interchange. + +```c +/* header visible to C and C++ */ +typedef struct device_packet { + unsigned int id; + unsigned char mode; +} device_packet; +``` + +Define the shared ABI in terms of exact available fundamental or fixed-width types, packing/alignment rules, and size/version fields. Check `sizeof`, `alignof`, and `offsetof` on every supported target. For long-lived disk/network interchange, serialize fields explicitly instead of copying raw struct bytes. + +### Allocation and deallocation must be paired across the boundary + +C's `malloc/free` allocation model and C++'s `new/delete` model are not interchangeable, especially when constructors and destructors are involved. A library that hands a resource to C callers must export the matching destroy operation; the caller should not guess the allocator family. + +```c +typedef struct codec_handle codec_handle; +codec_handle* codec_create(void); +void codec_destroy(codec_handle*); +``` + +The C++ implementation can use RAII internally while maintaining an opaque externally visible handle. This also shields the API from changes in internal class layout and C++ standard library implementation. + +### Function pointers and callbacks need a deliberate lifetime protocol + +A C callback typically consists of a function pointer plus a context pointer. A capturing C++ lambda cannot implicitly become a C function pointer; a noncapturing lambda can convert to a pointer to function with appropriate language linkage behavior as specified, but binary interfaces still require a compatible function-pointer contract. + +```c +typedef void (*on_result)(void* context, int status); +void codec_register(codec_handle*, on_result, void* context); +``` + +Never allow a callback to access an already-destroyed context. Document whether callbacks can run concurrently, after unregister, or during destruction; those decisions are more important than the lexical spelling of the callback declaration. + +### Exceptions, RTTI, and standard-library objects must stay inside the C++ side + +A C caller cannot portably catch a C++ exception or understand `std::string`, `std::vector`, `std::shared_ptr`, or a C++ polymorphic object's ABI. Catch exceptions at each C-exported entry point, record diagnostics, and return an integer error code plus an explicit output buffer or handle. + +This is not just conservatism: differing compiler versions, standard libraries, runtime settings, and exception models can disagree about object layout, allocation, and exception unwinding. A stable C facade permits the C++ implementation to evolve without requiring every consumer to share its toolchain. + +### C99 VLAs and compound literals are not ordinary C++ syntax + +A C variable-length array has runtime size and stack-storage semantics; standard C++ does not provide C99 VLAs. Some GCC and Clang C++ modes accept them as extensions, which can accidentally turn portable-looking code into vendor-specific code. + +```c +/* C99, not standard C++ */ +void compute(int n) { + int scratch[n]; + use(scratch); +} +``` + +A C compound literal also differs from C++ temporary-expression rules, including lifetime. Prefer `std::vector` or a bounded owner for dynamic C++ arrays, and explicitly manage the lifetime of any temporary view created while adapting a C API. + +### C99 `restrict` has no ISO C++ spelling + +C's `restrict` is a pointer-aliasing contract with specific block execution semantics. C++20/23/26 do not include a portable `restrict` keyword; compilers offer `__restrict` or `__restrict__` extensions with toolchain-specific meaning. + +Passing two overlapping buffers to an API that assumes non-aliasing can invalidate optimizer assumptions. Prefer documenting actual disjointness preconditions and encapsulating compiler extensions rather than simply copying a C `restrict` qualifier into a shared header. + +### C and C++ disagree about implicit conversions and type names + +C traditionally permits implicit conversion from `void*` to object pointers, while C++ requires a cast. C also has a distinct `struct` tag namespace, and C++ supports class names differently. These differences affect macros, generic API shims, and the shape of headers compiled by both compilers. + +```c +/* C permits this, standard C++ rejects the implicit conversion */ +int* p = malloc(100 * sizeof *p); +``` + +C++ should normally avoid raw `malloc` for objects requiring construction. A shared header can expose C-compatible opaque pointers while implementing typed ownership in the C++ translation unit. + +### Compound type qualifiers and atomics have different models + +C11 `_Atomic` and C++11 `std::atomic` are not universally interchangeable ABI types. Their spelling, type requirements, initialization, and layout are not guaranteed to coincide, even when the platform uses the same underlying instructions. + +Do not expose an atomic object directly through a supposedly portable C/C++ boundary. Instead export operations that synchronize internally, or agree on a platform-specific ABI and document toolchain assumptions. + +### C23 and C++23 share familiar tokens without sharing the whole language + +C23 introduced or standardized several spellings familiar to C++ developers, but C23's language rules are not C++23 rules. In mixed build systems compile `.c` sources with a chosen C standard and `.cpp` with a chosen C++ standard; do not expect one set of flags or feature-test macros to cover both languages. + +For example, `nullptr` and boolean support have evolving C definitions and availability, while C++ treats them through its own language type rules. Cross-language headers must explicitly conditionally declare facilities rather than assuming spellings imply binary compatibility. + +### C++20 modules do not make C headers into portable modules automatically + +A C header has textual macro and declaration semantics. Importing a C++20 header unit or including that header in the global module fragment changes build architecture but does not eliminate underlying ABI issues, macro dependencies, or the need to compile C code with a C compiler. + +A safe migration starts from a C-facing header that remains valid C, then wraps it behind C++ classes and module interfaces. The wrapper owns resource handles and translates errors, callbacks, and strings across the boundary. + +### C++26 reflection is not a shared ABI protocol + +Static reflection can help generate code around C++ types, but its existence does not automatically define a stable representation for C consumers, nor does it make C++ classes safe C structs. Use generated bindings only alongside a versioned C schema, explicit ownership rules, and ABI tests on all intended compilers. + +--- + +## 4. Cross-language review checklist for production APIs + +A C ABI should define allocator ownership, error handling, callback lifetime, thread-safety, calling convention, alignment, scalar widths, and versioning. For a plugin interface, keep an explicit versioned function table or handle-based API rather than passing compiler-specific C++ virtual objects through a foreign implementation. + +When in doubt, test the same public header under both C17/C23 and C++20/C++23 and compare binary exports with `nm`, `readelf`, and `dumpbin`. Runtime integration tests must exercise error paths and teardown paths, not only successful calls. + +--- + + ## Rules worth keeping in working memory The controlling distinction for C/C++ interoperability 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. @@ -188,7 +310,7 @@ No material change. Modern library vocabulary still belongs behind the facade. -### C++26: Forthcoming support +### C++26: Reflection does not define an ABI Future language/library changes cannot remove the need for an explicit ABI contract. From 7931987eb9da72fd2a186d88e01000b045674025 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 00:58:21 +0700 Subject: [PATCH 2/8] test: add reusable Python chapter structure checker --- scripts/check_chapter_structure.py | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/check_chapter_structure.py diff --git a/scripts/check_chapter_structure.py b/scripts/check_chapter_structure.py new file mode 100644 index 0000000..bb89f76 --- /dev/null +++ b/scripts/check_chapter_structure.py @@ -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|", 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()) From 9a8ef5ef0b33c876a56c72cabc79fbb003d481c0 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 00:58:23 +0700 Subject: [PATCH 3/8] ci: check modified chapter markdown --- .github/workflows/chapter-structure.yml | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/chapter-structure.yml diff --git a/.github/workflows/chapter-structure.yml b/.github/workflows/chapter-structure.yml new file mode 100644 index 0000000..f0b0042 --- /dev/null +++ b/.github/workflows/chapter-structure.yml @@ -0,0 +1,37 @@ +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 + mapfile -t files < <(git diff --name-only --diff-filter=ACMR "${{ github.event.pull_request.base.sha }}" 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 + else + python scripts/check_chapter_structure.py + fi From c23b881010200a04b95db58fb40fc8d736dad2b5 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 00:59:14 +0700 Subject: [PATCH 4/8] docs: correct advanced example and source references --- 01_low_level/12_c_cpp/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/01_low_level/12_c_cpp/README.md b/01_low_level/12_c_cpp/README.md index dd0974a..7efe3db 100644 --- a/01_low_level/12_c_cpp/README.md +++ b/01_low_level/12_c_cpp/README.md @@ -11,34 +11,34 @@ Some files are **C-only** (`.c`) and some are **C++** (`.cpp`) ## 1. Files -1. `01_name_mangling_and_overload.cpp` +1. `01_name_mangling_and_overload` examples - C++ name mangling, why overloads work - how to inspect symbols with `nm` / `objdump` - why C cannot overload by signature -2. `02_extern_c_and_callbacks.cpp` +2. `02_extern_c_callbacks` examples - `extern "C"`: linkage and ABI - C-callable functions exported from C++ - callbacks/function pointers across the boundary - pitfalls: exceptions, C++ objects across ABI -3. `03_static_init_and_main.cpp` +3. `03_static_init_and_main` examples - C++ startup: global constructors run **before `main`** - C startup: no user constructors/destructors - init-order fiasco + safe patterns -4. `04_headers_and_types.cpp` +4. `04_headers_and_types` examples - `` vs `` mapping - `void*` conversions, `malloc` in C vs C++ - `bool` vs `_Bool`, `nullptr` vs `NULL` - `restrict` in C99 and portable C++ replacements -5. `05_c_things_not_in_cpp.c` +5. `05_c_things_not_in_cpp` examples - Valid C features that are ill-formed in standard C++: VLAs, compound literals, array designators, `restrict`, implicit `void*` conversion, C struct-tag differences. -6. `06_cpp_things_not_in_c.cpp` +6. `06_cpp_things_not_in_c` examples - C++ features that C doesn't have (RAII/classes/templates/etc.) - a correct "C ABI facade" pattern: opaque handles + create/destroy + error codes From 98c22f583c2de9e096113c3f8bc53db32e3a32d1 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 01:00:24 +0700 Subject: [PATCH 5/8] docs: refine standard guarantees and subsection hierarchy --- 01_low_level/12_c_cpp/README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/01_low_level/12_c_cpp/README.md b/01_low_level/12_c_cpp/README.md index 7efe3db..916751b 100644 --- a/01_low_level/12_c_cpp/README.md +++ b/01_low_level/12_c_cpp/README.md @@ -55,7 +55,7 @@ Some files are **C-only** (`.c`) and some are **C++** (`.cpp`) The shared heritage of C and C++ makes mixed-language projects seem simpler than they are. Compiling the same header as C17 and C++20 can produce different types, different name lookup, different initialization, and different interpretations of an expression. The interoperability contract is therefore an explicit interface between two languages, not an assumption that C++ is a strict superset of C. -### C linkage affects language linkage, not everything the ABI requires +## 4. C linkage affects language linkage, not everything the ABI requires Declaring a function `extern "C"` gives it C language linkage, including rules governing its name and function type. This does not guarantee identical calling conventions across all targets, compatible definitions of C and C++ structs, or a stable exception ABI. @@ -66,7 +66,7 @@ extern "C" int codec_decode(const unsigned char* bytes, The corresponding C header must use declarations legal in C, such as `size_t` from ``. Avoid putting `noexcept` in a shared C declaration; use conditional macros guarded by `__cplusplus`. An exported function catches every C++ exception internally and reports errors through a C-compatible result. -### The same `struct` layout is a requirement to verify, not assume +## 5. The same `struct` layout is a requirement to verify, not assume C and C++ often agree on the layout of simple record types under a matching target ABI. But a C++ class with virtual functions, nonstandard layout, inheritance, or implementation-specific packing is not a candidate for direct C interchange. @@ -80,7 +80,7 @@ typedef struct device_packet { Define the shared ABI in terms of exact available fundamental or fixed-width types, packing/alignment rules, and size/version fields. Check `sizeof`, `alignof`, and `offsetof` on every supported target. For long-lived disk/network interchange, serialize fields explicitly instead of copying raw struct bytes. -### Allocation and deallocation must be paired across the boundary +## 6. Allocation and deallocation must be paired across the boundary C's `malloc/free` allocation model and C++'s `new/delete` model are not interchangeable, especially when constructors and destructors are involved. A library that hands a resource to C callers must export the matching destroy operation; the caller should not guess the allocator family. @@ -92,7 +92,7 @@ void codec_destroy(codec_handle*); The C++ implementation can use RAII internally while maintaining an opaque externally visible handle. This also shields the API from changes in internal class layout and C++ standard library implementation. -### Function pointers and callbacks need a deliberate lifetime protocol +## 7. Function pointers and callbacks need a deliberate lifetime protocol A C callback typically consists of a function pointer plus a context pointer. A capturing C++ lambda cannot implicitly become a C function pointer; a noncapturing lambda can convert to a pointer to function with appropriate language linkage behavior as specified, but binary interfaces still require a compatible function-pointer contract. @@ -103,13 +103,13 @@ void codec_register(codec_handle*, on_result, void* context); Never allow a callback to access an already-destroyed context. Document whether callbacks can run concurrently, after unregister, or during destruction; those decisions are more important than the lexical spelling of the callback declaration. -### Exceptions, RTTI, and standard-library objects must stay inside the C++ side +## 8. Exceptions, RTTI, and standard-library objects must stay inside the C++ side A C caller cannot portably catch a C++ exception or understand `std::string`, `std::vector`, `std::shared_ptr`, or a C++ polymorphic object's ABI. Catch exceptions at each C-exported entry point, record diagnostics, and return an integer error code plus an explicit output buffer or handle. This is not just conservatism: differing compiler versions, standard libraries, runtime settings, and exception models can disagree about object layout, allocation, and exception unwinding. A stable C facade permits the C++ implementation to evolve without requiring every consumer to share its toolchain. -### C99 VLAs and compound literals are not ordinary C++ syntax +## 9. C99 VLAs and compound literals are not ordinary C++ syntax A C variable-length array has runtime size and stack-storage semantics; standard C++ does not provide C99 VLAs. Some GCC and Clang C++ modes accept them as extensions, which can accidentally turn portable-looking code into vendor-specific code. @@ -123,13 +123,13 @@ void compute(int n) { A C compound literal also differs from C++ temporary-expression rules, including lifetime. Prefer `std::vector` or a bounded owner for dynamic C++ arrays, and explicitly manage the lifetime of any temporary view created while adapting a C API. -### C99 `restrict` has no ISO C++ spelling +## 10. C99 `restrict` has no ISO C++ spelling C's `restrict` is a pointer-aliasing contract with specific block execution semantics. C++20/23/26 do not include a portable `restrict` keyword; compilers offer `__restrict` or `__restrict__` extensions with toolchain-specific meaning. Passing two overlapping buffers to an API that assumes non-aliasing can invalidate optimizer assumptions. Prefer documenting actual disjointness preconditions and encapsulating compiler extensions rather than simply copying a C `restrict` qualifier into a shared header. -### C and C++ disagree about implicit conversions and type names +## 11. C and C++ disagree about implicit conversions and type names C traditionally permits implicit conversion from `void*` to object pointers, while C++ requires a cast. C also has a distinct `struct` tag namespace, and C++ supports class names differently. These differences affect macros, generic API shims, and the shape of headers compiled by both compilers. @@ -140,31 +140,31 @@ int* p = malloc(100 * sizeof *p); C++ should normally avoid raw `malloc` for objects requiring construction. A shared header can expose C-compatible opaque pointers while implementing typed ownership in the C++ translation unit. -### Compound type qualifiers and atomics have different models +## 12. Compound type qualifiers and atomics have different models C11 `_Atomic` and C++11 `std::atomic` are not universally interchangeable ABI types. Their spelling, type requirements, initialization, and layout are not guaranteed to coincide, even when the platform uses the same underlying instructions. Do not expose an atomic object directly through a supposedly portable C/C++ boundary. Instead export operations that synchronize internally, or agree on a platform-specific ABI and document toolchain assumptions. -### C23 and C++23 share familiar tokens without sharing the whole language +## 13. C23 and C++23 share familiar tokens without sharing the whole language C23 introduced or standardized several spellings familiar to C++ developers, but C23's language rules are not C++23 rules. In mixed build systems compile `.c` sources with a chosen C standard and `.cpp` with a chosen C++ standard; do not expect one set of flags or feature-test macros to cover both languages. For example, `nullptr` and boolean support have evolving C definitions and availability, while C++ treats them through its own language type rules. Cross-language headers must explicitly conditionally declare facilities rather than assuming spellings imply binary compatibility. -### C++20 modules do not make C headers into portable modules automatically +## 14. C++20 modules do not make C headers into portable modules automatically A C header has textual macro and declaration semantics. Importing a C++20 header unit or including that header in the global module fragment changes build architecture but does not eliminate underlying ABI issues, macro dependencies, or the need to compile C code with a C compiler. A safe migration starts from a C-facing header that remains valid C, then wraps it behind C++ classes and module interfaces. The wrapper owns resource handles and translates errors, callbacks, and strings across the boundary. -### C++26 reflection is not a shared ABI protocol +## 15. C++26 reflection is not a shared ABI protocol Static reflection can help generate code around C++ types, but its existence does not automatically define a stable representation for C consumers, nor does it make C++ classes safe C structs. Use generated bindings only alongside a versioned C schema, explicit ownership rules, and ABI tests on all intended compilers. --- -## 4. Cross-language review checklist for production APIs +## 16. Cross-language review checklist for production APIs A C ABI should define allocator ownership, error handling, callback lifetime, thread-safety, calling convention, alignment, scalar widths, and versioning. For a plugin interface, keep an explicit versioned function table or handle-based API rather than passing compiler-specific C++ virtual objects through a foreign implementation. From 31aeb2db374bd16658d7a8f4a527b63e5c58bf21 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Mon, 19 Oct 2026 01:03:54 +0700 Subject: [PATCH 6/8] ci: validate only changed chapters on push and PR; keep manual full-book audit --- .github/workflows/chapter-structure.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/chapter-structure.yml b/.github/workflows/chapter-structure.yml index f0b0042..f2afa8e 100644 --- a/.github/workflows/chapter-structure.yml +++ b/.github/workflows/chapter-structure.yml @@ -26,12 +26,16 @@ jobs: shell: bash run: | if [[ "${{ github.event_name }}" == "pull_request" ]]; then - mapfile -t files < <(git diff --name-only --diff-filter=ACMR "${{ github.event.pull_request.base.sha }}" 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 + 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 From 7779eda49fce163fe362a07edd4828ae496e30c1 Mon Sep 17 00:00:00 2001 From: Yurii Cherkasov Date: Sat, 24 Oct 2026 21:46:17 +0700 Subject: [PATCH 7/8] Merge master --- scripts/check_chapter_structure.py | 37 ++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/scripts/check_chapter_structure.py b/scripts/check_chapter_structure.py index bb89f76..a4ba286 100644 --- a/scripts/check_chapter_structure.py +++ b/scripts/check_chapter_structure.py @@ -17,16 +17,40 @@ 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|", re.I) HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*$", re.M) -FENCE = re.compile(r"^\s*(`{3,}|~{3,})", re.M) +FENCE_OPEN = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$") +FENCE_CLOSE = re.compile(r"^ {0,3}(`{3,}|~{3,})[ \t]*$") CHAPTER = re.compile(r"^(\d{2})_") + +def outside_fences(body: str) -> tuple[str, bool]: + visible: list[str] = [] + opening: tuple[str, int] | None = None + for line in body.splitlines(keepends=True): + if opening is None: + match = FENCE_OPEN.match(line) + if match and not (match.group(1).startswith("`") and "`" in match.group(2)): + delimiter = match.group(1) + opening = (delimiter[0], len(delimiter)) + visible.append("\n" if line.endswith("\n") else "") + else: + visible.append(line) + continue + match = FENCE_CLOSE.match(line) + if (match and match.group(1)[0] == opening[0] + and len(match.group(1)) >= opening[1]): + opening = None + visible.append("\n" if line.endswith("\n") else "") + return "".join(visible), opening is not None + + def validate(path: Path) -> list[str]: body = path.read_text(encoding="utf-8") + visible_body, unbalanced_fences = outside_fences(body) 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)] + headings = [(len(m.group(1)), m.group(2)) for m in HEADING.finditer(visible_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] @@ -41,13 +65,12 @@ def validate(path: Path) -> list[str]: 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: + if unbalanced_fences: errors.append("unbalanced fenced code blocks") - timeline_start = body.find("\n## Standards timeline") - migration_start = body.find("\n## Migration note") + timeline_start = visible_body.find("\n## Standards timeline") + migration_start = visible_body.find("\n## Migration note") if timeline_start >= 0 and migration_start > timeline_start: - timeline = body[timeline_start:migration_start] + timeline = visible_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)}") From 11ae0e0e5cbf31d79a41063339be2e00f7c19d3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Oct 2026 14:47:41 +0000 Subject: [PATCH 8/8] Clarify C callback linkage and VLA storage Co-authored-by: yuchdev <678436+yuchdev@users.noreply.github.com> --- 01_low_level/12_c_cpp/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/01_low_level/12_c_cpp/README.md b/01_low_level/12_c_cpp/README.md index 916751b..e57e011 100644 --- a/01_low_level/12_c_cpp/README.md +++ b/01_low_level/12_c_cpp/README.md @@ -94,7 +94,7 @@ The C++ implementation can use RAII internally while maintaining an opaque exter ## 7. Function pointers and callbacks need a deliberate lifetime protocol -A C callback typically consists of a function pointer plus a context pointer. A capturing C++ lambda cannot implicitly become a C function pointer; a noncapturing lambda can convert to a pointer to function with appropriate language linkage behavior as specified, but binary interfaces still require a compatible function-pointer contract. +A C callback typically consists of a function pointer plus a context pointer. A capturing C++ lambda cannot implicitly become a C function pointer; a noncapturing lambda converts to a pointer to a function with C++ language linkage, so it is not a portable match for a C-linkage callback type, even if common compilers accept it. When the callback contract requires C linkage, use a named `extern "C"` trampoline. ```c typedef void (*on_result)(void* context, int status); @@ -111,7 +111,7 @@ This is not just conservatism: differing compiler versions, standard libraries, ## 9. C99 VLAs and compound literals are not ordinary C++ syntax -A C variable-length array has runtime size and stack-storage semantics; standard C++ does not provide C99 VLAs. Some GCC and Clang C++ modes accept them as extensions, which can accidentally turn portable-looking code into vendor-specific code. +A locally declared C variable-length array has automatic storage duration; ISO C does not prescribe a stack allocation mechanism. Standard C++ does not provide C99 VLAs. Some GCC and Clang C++ modes accept them as extensions, which can accidentally turn portable-looking code into vendor-specific code. ```c /* C99, not standard C++ */