https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126913
Bug ID: 126913
Summary: -E column padding is quadratic and its readability
rationale does not hold
Product: gcc
Version: 16.1.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: preprocessor
Assignee: unassigned at gcc dot gnu.org
Reporter: ismael at linux dot com
Target Milestone: ---
cpp pads with spaces so a token lands in its original source column. The
documented reason is "so the output is easy to read".
The padding does not fire only at line starts. Every linemarker restarts it,
and an object-like macro from a system header emits a linemarker pair
mid-line. So a long line with M such macros pays the column cost M times:
output is O(M * line_length).
Reproducer:
for n in 1000 2000 4000 8000; do
{ printf '#include <stdbool.h>\nstatic const _Bool a[] = {'
awk -v n=$n 'BEGIN{for(i=0;i<n;i++) printf "true,"}'
printf '0};\n'; } > b$n.c
gcc -E b$n.c -o b$n.i
echo "N=$n src=$(stat -c %s b$n.c) -E=$(stat -c %s b$n.i)"
done
N=1000 src=5055 -E=5093292
N=2000 src=10055 -E=20186292
N=4000 src=20055 -E=80372292
N=8000 src=40055 -E=320744292
Doubling N quadruples output. 40 KB in, 320 MB out, ~99.9% spaces.
Two conditions are required. The macro must be object-like: function-like
UINT8_C(c)->c yields the caller's own token, no context switch, no padding.
And it must come from a system header: an identical user-defined macro does
not trigger it.
Real case: zig 0.16.0's generated zig2.c (222 MB, longest line 575,257
chars, 223,092 true/false from <stdbool.h>). Under ccache, which runs a
standalone cc -E on a direct-mode miss, the .i reached 34,209,846,741 bytes
and filled a 100 GB filesystem, wedging cc1 in D state on ENOSPC. The last
gigabyte was one unbroken run of spaces.
--------------------------------------------------------------------------
On the rationale
--------------------------------------------------------------------------
Nobody reads column 287,000. The feature buys nothing past roughly the width
of a terminal, and past that it is pure cost — here, three orders of
magnitude of whitespace over real token content, with no diagnostic.
-E output is overwhelmingly machine input now: ccache, distcc/icecream,
--save-temps, editor tooling. Optimising its byte layout for a human reader
is the wrong default, and doing so unboundedly is a denial of service.
Suggested fix, cheapest first:
1. Cap padding at some sane width (~200 cols) and emit a single space
beyond it.
2. Or re-pad only at genuine source line starts, not after linemarkers
emitted mid-line for an expansion context switch.
-ftrack-macro-expansion=0 avoids it today (N=4000: 8,205 bytes, linemarkers
intact, byte-identical object), which locates the trigger in the expansion
location tracking.
--------------------------------------------------------------------------
Nothing filed for this: 0 hits across all comment text for "appears in the
same column" and variants, and ~17 quicksearch queries over
component:preprocessor found nothing matching. Adjacent but distinct:
PR 53525 track-macro-expansion performance regression. Same subsystem;
CPU, not output size.
PR 124679 cpp to infinite memory on systemtap-generated C. Sibling
generated-code case; RAM in collect_args, function-like macros.
PR 60723 Line directives with incorrect system header flag. Same
machinery, correctness only.