Context
This issue collects the remaining findings from a dependency-structure and header-hygiene review of libdedx, grouped into one place so we can discuss tradeoffs and decide what (if anything) to act on before opening focused PRs. The dedx_const.h dead-code finding is tracked separately in #126.
TL;DR of the overall review (the good news first)
The dependency architecture is fundamentally sound:
- No circular dependencies. The full header→header graph is a clean DAG. Hub headers are
dedx.h (included by 6 headers) and dedx_elements.h (4) — exactly what you'd expect for core type headers.
- Include guards are correct and consistent. All 21 internal + 16 embedded headers use
#ifndef/#define/#endif; every guard macro matches its #ifndef, and all 37 macro names are unique.
- Clean public/private split. Public
include/ headers never pull in a src/ header.
- Headers are self-contained for std types (
bool/size_t/FILE/intN_t each pull their own <std*.h>).
- Modern CMake — object library feeding static + shared,
target_include_directories with PUBLIC/PRIVATE + $<BUILD_INTERFACE>/$<INSTALL_INTERFACE>, namespaced dedx:: exports.
So the items below are refinements, not fixes for anything broken. Listed roughly by impact.
1. static const data arrays defined in headers (highest impact)
src/dedx_periodic_table.h defines data, not just declarations:
static const float dedx_amu[112] = { ... };
static const int dedx_nucl[112] = { ... };
This header is #included by 4 translation units: dedx.c, dedx_wrappers.c, dedx_validate.c, dedx_periodic_table.c. Because the arrays are static, each TU gets its own private copy baked into its object file — duplicated data and a latent footgun if anyone ever assumes shared state.
src/dedx_program_const.h is the same pattern at larger scale — 7 static const tables, including char[1000][40] and char[120][40] (tens of KB). Today it's included by only 1 TU (dedx.c), so there's no duplication yet — but the moment a second file includes it, the bloat is silent.
Best-practice shape: declare extern const in the header, define once in a .c:
/* dedx_periodic_table.h */
extern const float dedx_amu[112];
extern const int dedx_nucl[112];
/* dedx_periodic_table.c */
const float dedx_amu[112] = { ... };
const int dedx_nucl[112] = { ... };
Discussion points:
- Any reason these were intentionally
static in headers (e.g. to let the compiler constant-fold / inline lookups, or a past single-TU assumption)?
- Worth measuring the actual
.so/static-lib size delta before/after — for WASM builds in particular, dead-stripping may already hide most of the cost.
- If we change
dedx_program_const.h, do we want to keep it header-only with inline-style accessors instead, or commit to the extern + .c split for consistency with the rest of the codebase?
2. No extern "C" guards in the public headers (API correctness)
None of the installed public headers — include/dedx.h, dedx_tools.h, dedx_wrappers.h, dedx_elements.h, dedx_error.h — wrap declarations in:
#ifdef __cplusplus
extern "C" {
#endif
...
#ifdef __cplusplus
}
#endif
These headers are installed and exported via the CMake package, so a C++ consumer that #includes them gets name-mangled declarations and link errors unless they wrap the include themselves.
Discussion points:
- Are there any C++ consumers today, or is the consumer set effectively WASM/JS + Python ctypes (where this doesn't matter)? The change is cheap and harmless either way, so the question is just whether it's worth the churn now vs. when a C++ consumer actually appears.
- If we add them, do it for all 5 installed headers in one pass for consistency.
3. Orphaned embedded data headers (dead artifacts?)
Three generated data headers exist but are never #included anywhere:
src/data/embedded/dedx_estar.h
src/data/embedded/dedx_icru90_e.h
src/data/embedded/dedx_icru90_pos.h
These look like electron / positron tables. They're either leftovers from the data generator or features that were never wired into dedx_embedded_data.c.
Discussion points:
- Are electron/positron (ESTAR / ICRU90 e⁻ / e⁺) tables a planned feature? If yes, this is a "wire it up" task; if no, they're dead generator output that should be dropped (or excluded from the generator) to avoid confusion.
4. Minor / stylistic
- Include-guard style: everything uses classic
#ifndef guards (portable, standard, consistent). I'd keep this rather than switch to #pragma once. Noting it only because "modern guards" sometimes means #pragma once — flagging that the project deliberately doesn't use it, in case there's a preference to revisit.
- Test include path is over-broad:
tests/CMakeLists.txt adds src/ to the include path for all test targets, though only the white-box tests (test_validate_internal.c, test_core_api.c, which include dedx_validate.h / dedx_mpaul.h) actually need internal headers. Pragmatic as-is; the only downside is that any test can accidentally couple to internals. Could scope src/ to just those targets if we want stricter encapsulation.
Proposed disposition (for discussion)
| # |
Finding |
Suggested action |
| 1 |
static const arrays in headers |
Refactor to extern const + single .c definition (start with dedx_periodic_table.h) — pending agreement on rationale |
| 2 |
Missing extern "C" guards |
Add to all 5 public headers if any C++ consumption is anticipated |
| 3 |
Orphaned embedded headers |
Decide: wire up e⁻/e⁺ data, or remove |
| 4 |
Test include path / guard style |
Likely leave as-is; documented for completeness |
Happy to open focused PRs for whichever of these we agree on. cc maintainers for input on #1's rationale and #3's roadmap intent.
Context
This issue collects the remaining findings from a dependency-structure and header-hygiene review of libdedx, grouped into one place so we can discuss tradeoffs and decide what (if anything) to act on before opening focused PRs. The
dedx_const.hdead-code finding is tracked separately in #126.TL;DR of the overall review (the good news first)
The dependency architecture is fundamentally sound:
dedx.h(included by 6 headers) anddedx_elements.h(4) — exactly what you'd expect for core type headers.#ifndef/#define/#endif; every guard macro matches its#ifndef, and all 37 macro names are unique.include/headers never pull in asrc/header.bool/size_t/FILE/intN_teach pull their own<std*.h>).target_include_directorieswithPUBLIC/PRIVATE+$<BUILD_INTERFACE>/$<INSTALL_INTERFACE>, namespaceddedx::exports.So the items below are refinements, not fixes for anything broken. Listed roughly by impact.
1.
static constdata arrays defined in headers (highest impact)src/dedx_periodic_table.hdefines data, not just declarations:This header is
#included by 4 translation units:dedx.c,dedx_wrappers.c,dedx_validate.c,dedx_periodic_table.c. Because the arrays arestatic, each TU gets its own private copy baked into its object file — duplicated data and a latent footgun if anyone ever assumes shared state.src/dedx_program_const.his the same pattern at larger scale — 7static consttables, includingchar[1000][40]andchar[120][40](tens of KB). Today it's included by only 1 TU (dedx.c), so there's no duplication yet — but the moment a second file includes it, the bloat is silent.Best-practice shape: declare
extern constin the header, define once in a.c:Discussion points:
staticin headers (e.g. to let the compiler constant-fold / inline lookups, or a past single-TU assumption)?.so/static-lib size delta before/after — for WASM builds in particular, dead-stripping may already hide most of the cost.dedx_program_const.h, do we want to keep it header-only withinline-style accessors instead, or commit to theextern+.csplit for consistency with the rest of the codebase?2. No
extern "C"guards in the public headers (API correctness)None of the installed public headers —
include/dedx.h,dedx_tools.h,dedx_wrappers.h,dedx_elements.h,dedx_error.h— wrap declarations in:These headers are installed and exported via the CMake package, so a C++ consumer that
#includes them gets name-mangled declarations and link errors unless they wrap the include themselves.Discussion points:
3. Orphaned embedded data headers (dead artifacts?)
Three generated data headers exist but are never
#included anywhere:src/data/embedded/dedx_estar.hsrc/data/embedded/dedx_icru90_e.hsrc/data/embedded/dedx_icru90_pos.hThese look like electron / positron tables. They're either leftovers from the data generator or features that were never wired into
dedx_embedded_data.c.Discussion points:
4. Minor / stylistic
#ifndefguards (portable, standard, consistent). I'd keep this rather than switch to#pragma once. Noting it only because "modern guards" sometimes means#pragma once— flagging that the project deliberately doesn't use it, in case there's a preference to revisit.tests/CMakeLists.txtaddssrc/to the include path for all test targets, though only the white-box tests (test_validate_internal.c,test_core_api.c, which includededx_validate.h/dedx_mpaul.h) actually need internal headers. Pragmatic as-is; the only downside is that any test can accidentally couple to internals. Could scopesrc/to just those targets if we want stricter encapsulation.Proposed disposition (for discussion)
static constarrays in headersextern const+ single.cdefinition (start withdedx_periodic_table.h) — pending agreement on rationaleextern "C"guardsHappy to open focused PRs for whichever of these we agree on. cc maintainers for input on #1's rationale and #3's roadmap intent.