Edit (2026-08-17): the mechanism in my first follow-up comment was wrong, and Observation 3 below is inaccurate about const. See the correction comment — the root cause appears to be a __cxx_global_var_init symbol-name collision, and it looks already fixed in 1.3+.
Summary
When two or more namespace-scope variables with dynamic (runtime) initializers are defined in separate inputs, the values read back afterwards are wrong: the first variable holds the value produced by the last initializer, and every subsequent variable reads back as 0.
The initializers themselves all run — a side-effect counter shows exactly the expected number of calls — so the storage that later inputs read does not appear to be the storage the initializers wrote to.
Defining the same variables within a single input is correct, which gives a clean A/B. Upstream clang-repl at the same LLVM version (18.1.8) does not reproduce it.
Reproducer
Enter one line at a time at the cling prompt:
#include <cstdio>
long long n = 0;
long long b(){ return ++n; }
long long P = b();
long long Q = b();
printf("P=%lld Q=%lld n=%lld\n", P, Q, n);
|
|
| Expected |
P=1 Q=2 n=2 |
| Actual |
P=2 Q=0 n=2 |
(Program output goes to stderr, so when scripting this use cling 2>&1 <<'EOF' ... EOF.)
Observations
1. Control — the same definitions in a single input are correct.
long long P = b(); long long Q = b(); printf("P=%lld Q=%lld n=%lld\n", P, Q, n);
→ P=1 Q=2 n=2 ✔
2. It scales with the number of definitions. Three definitions, each its own input:
→ P=3 Q=0 R=0 n=3 (expected 1 2 3)
3. Not specific to auto, to const, or to any library type. Plain int with explicit types, still one definition per input:
#include <cstdio>
int m = 0;
int f(){ return ++m; }
int A = f();
int B = f();
printf("A=%d B=%d m=%d\n", A, B, m);
→ A=2 B=0 m=2 (expected 1 2 2)
4. Does not reproduce with upstream clang-repl at the same LLVM/Clang version cling 1.2 is built on, in the same container with the same libraries and the identical input:
cling 1.2 → P=2 Q=0 n=2 (wrong)
clang-repl 18.1.8 → P=1 Q=2 n=2 (correct)
5. Block-scope and function-local declarations are unaffected. In the same session, wrapping the same code in { ... } or in a function gives correct results, so this looks specific to namespace scope across separate transactions.
Practical impact
The most likely way to run into this is the ordinary "take two timestamps and subtract" idiom, which is natural to type as two separate lines:
#include <chrono>
#include <cstdio>
auto t0 = std::chrono::steady_clock::now();
auto t1 = std::chrono::steady_clock::now();
printf("t0=%lld t1=%lld\n", (long long)t0.time_since_epoch().count(), (long long)t1.time_since_epoch().count());
→ t0=1196998825664793 t1=0
so t1 - t0 silently yields a large negative duration. There is no diagnostic, and the failure is easy to misattribute to the timing code itself.
Environment
cling 1.2 (conda-forge, cling-1.2-h48f18f5_1)
clang / LLVM 18.1.8 (conda-forge, cling_1.2 build)
Debian GNU/Linux 13 (trixie), x86_64
Reproduced identically in two environments that differ only in the C++ runtime (libstdcxx 14.1.0 and 16.1.0), so the behaviour does not appear to depend on the standard library version.
Caveat
Both environments print this at startup:
Warning in cling::IncrementalParser::CheckABICompatibility():
Possible C++ standard library mismatch, compiled with __GLIBCXX__ '20240521'
Extraction of runtime standard library version was: '20250605'
I could not get rid of it with the conda-forge packages. I don't think it explains the behaviour, because (a) the reproducer uses no C++ standard library types at all, (b) two environments with different libstdcxx versions produce identical results, and (c) function-scope declarations in the same session are correct. Flagging it in case it is relevant.
Tested by piping to the cling binary; I have not verified this against xeus-cling / Jupyter.
Summary
When two or more namespace-scope variables with dynamic (runtime) initializers are defined in separate inputs, the values read back afterwards are wrong: the first variable holds the value produced by the last initializer, and every subsequent variable reads back as
0.The initializers themselves all run — a side-effect counter shows exactly the expected number of calls — so the storage that later inputs read does not appear to be the storage the initializers wrote to.
Defining the same variables within a single input is correct, which gives a clean A/B. Upstream
clang-replat the same LLVM version (18.1.8) does not reproduce it.Reproducer
Enter one line at a time at the
clingprompt:P=1 Q=2 n=2P=2 Q=0 n=2(Program output goes to stderr, so when scripting this use
cling 2>&1 <<'EOF' ... EOF.)Observations
1. Control — the same definitions in a single input are correct.
→
P=1 Q=2 n=2✔2. It scales with the number of definitions. Three definitions, each its own input:
→
P=3 Q=0 R=0 n=3(expected1 2 3)3. Not specific to
auto, toconst, or to any library type. Plainintwith explicit types, still one definition per input:→
A=2 B=0 m=2(expected1 2 2)4. Does not reproduce with upstream
clang-replat the same LLVM/Clang version cling 1.2 is built on, in the same container with the same libraries and the identical input:5. Block-scope and function-local declarations are unaffected. In the same session, wrapping the same code in
{ ... }or in a function gives correct results, so this looks specific to namespace scope across separate transactions.Practical impact
The most likely way to run into this is the ordinary "take two timestamps and subtract" idiom, which is natural to type as two separate lines:
→
t0=1196998825664793 t1=0so
t1 - t0silently yields a large negative duration. There is no diagnostic, and the failure is easy to misattribute to the timing code itself.Environment
Reproduced identically in two environments that differ only in the C++ runtime (
libstdcxx14.1.0 and 16.1.0), so the behaviour does not appear to depend on the standard library version.Caveat
Both environments print this at startup:
I could not get rid of it with the conda-forge packages. I don't think it explains the behaviour, because (a) the reproducer uses no C++ standard library types at all, (b) two environments with different
libstdcxxversions produce identical results, and (c) function-scope declarations in the same session are correct. Flagging it in case it is relevant.Tested by piping to the
clingbinary; I have not verified this against xeus-cling / Jupyter.