GCC 12 separates C2x syntax from runtime support
GCC 12 adds draft C2x preprocessing and format checks, while dynamic object-size analysis extends hardening. These changes operate at different stages of a C build and have different runtime requirements.
A compiler can recognize a format conversion before the target library implements it. That distinction is easy to miss in GCC 12's C2x additions. Its format checker understands binary integer conversions, but the compiler does not supply the printf implementation that ultimately consumes the format string. The GCC 12 series begins with GCC 12.1 on May 6, bringing new draft-language syntax alongside changes to optimization and hardening.
The C release notes identify digit separators and the #elifdef and #elifndef preprocessing directives as additions. They also say -Wformat understands C2x %b conversions for formatted input and output, plus the recommended %B output conversion. Each addition removes a particular incompatibility between the compiler and the developing specification. None means that C2x is finished or that every runtime dependency has adopted the same interfaces.
I like C's relatively small language, but useful C programming depends on understanding where the compiler's responsibilities end. This release makes that boundary worth examining because some additions change source parsing, while others describe calls into separately supplied code.
A preprocessing improvement with limited scope
The new directives express an alternative definedness test within a conditional preprocessing group. They provide direct spellings for conditions otherwise written through defined. In GCC 12's C2x mode, a selection among compile-time configuration macros can use the new forms.
#if defined(USE_FAST_PATH)
#define BLOCK_BYTES 4096
#elifdef USE_SMALL_PATH
#define BLOCK_BYTES 256
#elifndef USE_CUSTOM_PATH
#define BLOCK_BYTES 1024
#endif
This example chooses a default only when neither earlier branch applies and USE_CUSTOM_PATH is absent. The directives do not test a macro's numeric value. A configuration system that defines a macro to zero still defines that macro, so presence-based configuration and value-based configuration remain different conventions.
That is a modest syntactic change, but it belongs to a part of C source that can become difficult to review. A long conditional group already mixes selection policy with platform names. A direct definedness directive reduces some spelling without inventing another configuration mechanism. Existing equivalent expressions remain useful when source must compile under older modes.
The release notes name -std=c2x and -std=gnu2x for the draft revision. They also explain that some additions are available as extensions in older modes. Choosing a language mode therefore remains part of a build's compatibility contract. A feature compiling under one compiler's extension policy is different from a program that requires the draft language mode deliberately.
Separators change the token's readability
Digit separators give long literals a more legible spelling. They do not change the represented value. This C2x declaration places separators in a decimal literal so that the grouping is visible in source.
static const unsigned long sample_limit = 1'000'000UL;
The grouping is for the reader. The suffix still specifies the literal's type category, and the declaration still has C's existing semantics for a const object. Adding separator syntax does not make that object a new kind of constant expression. It also does not establish that a source generator or another compiler in the same build accepts the new token spelling.
This is an example of a change whose runtime implications are limited by construction. The generated program uses the literal's value. The compatibility question lies in the tools that tokenize the source. A project that shares generated headers with other toolchains has a reason to keep that requirement explicit even when the application itself builds only with GCC 12.
Format diagnostics and formatting are separate
For %b, GCC's change is in checking the relationship between a format string and its arguments. The release notes state that both printf and scanf checking gain the draft binary format. Their argument contracts differ because output consumes a value and input needs storage for the converted result. Recognizing a conversion is necessary for useful diagnostics about those calls.
The runtime must independently understand the conversion. glibc 2.35's February announcement says its printf family implements %b and %B. That source supports a specific conclusion about formatted output on that library version. It does not establish the same input support or make the conversion portable to every C library used with GCC.
This separation affects how a successful build should be interpreted. An accepted format string means the compiler's checker considers the call consistent with the selected interface rules. The linked library still determines the operation performed at runtime. Cross-compilation makes that distinction especially relevant because the compiler's installation and the target library need not come from the same release schedule.
Object sizes can now require computation
The release also adds __builtin_dynamic_object_size, compatible with Clang's extension. Unlike a syntax addition, this can affect generated work at runtime. The GCC 12.1 object-size manual explains that the returned size need not be constant. That permits useful estimates in cases the existing constant-only builtin cannot describe precisely.
Both builtins describe bytes remaining from a pointer to the end of an object. Their type argument controls which object boundary is relevant and how to combine estimates when a pointer may designate more than one object. The low bit chooses between the complete object and the closest surrounding subobject. Another bit chooses a maximum or minimum remaining size.
That distinction matters for a pointer into a structure member. The complete structure can contain bytes beyond the end of that member, while the member has its own smaller boundary. A check intended to protect a member array should not casually treat the rest of its enclosing structure as available destination storage. The manual's separate modes make the chosen boundary part of the caller's request.
When the compiler cannot determine the relevant object, the documented fallback is (size_t)-1 for modes zero and one, or zero for modes two and three. Dynamic analysis does not eliminate unknown cases. It changes the set of cases in which the compiler can construct a useful estimate, potentially at the cost of runtime size computation.
My preference is to understand that tradeoff as additional checking coverage with a defined implementation cost. The language remains responsible for the validity of the program's accesses. Object-size analysis provides information to checking code, and the precision of that information depends on what the compiler can follow.
Checked calls use the size information
The same manual describes checked variants of common memory and string operations. A checked copy receives the destination's remaining size as an additional argument. If the compiler proves that the operation fits, it can reduce the call to the ordinary function. If an overflow is known at compile time, it can issue a warning. Cases with a known destination bound and a runtime length can retain a runtime check.
Optimization also affects the analysis. The manual says following pointer assignments through nontrivial control flow and finding sizes across function boundaries rely on optimization passes available at -O2. That connects the usefulness of a checking facility to the compilation settings used for the translation unit.
An unknown size has different consequences. For the documented memory-operation builtins, a final size argument of (size_t)-1 permits conversion to the unchecked operation. This is why computing a useful bound can matter more than merely enabling a nominal hardening option. A check needs information about the destination to distinguish an oversized write.
The glibc 2.35 release connects this compiler facility to fortification level 3 when compiling with GCC 12 or later. It explicitly describes additional checking for nonconstant bounds and associated runtime costs. The compiler and library changes consequently form a usable combination, while retaining separate version requirements.
Existing optimization settings also change
The May 6 announcement calls out vectorization at -O2 with the very-cheap cost model. It says that model imposes extra constraints on code-size expansion. This changes an existing optimization level without requiring any new C2x syntax in application code.
The announcement also introduces -ftrivial-auto-var-init for implicit stack-variable initialization as a mitigation and diagnostic aid for uninitialized-variable flaws. That facility addresses a different problem from destination-size checking. Initializing automatic storage does not tell a copy operation how much space its destination has, and a checked copy does not establish that every value read by the application was initialized.
GCC 12 thus changes both the source it accepts and the work it can generate for familiar source. A build can adopt the compiler while retaining an older language mode, or use the draft syntax with a separately specified runtime. For binary formatting and fortification, naming the target library version is part of describing what the resulting executable can actually do.