Killing Ghosts in C: Removing UB #28 and #29

October 2026 | Glenn Coates

My paper N3904 removes two entries from the list of undefined behaviour in C: UB 28 and UB 29. Both concern invalid characters in identifiers, and neither can occur in a conforming program since C23 changed the identifier rules.

WG14 voted N3904 into C2y at its meeting in Ottawa in August 2026.

Where UB 28 and UB 29 Came From

C17 used a permissive grammar for identifiers and then restricted it with semantic rules. The grammar accepted universal character names (UCNs) in identifiers without restriction. Separate “shall” rules then limited which characters those UCNs could designate, and which could appear first.

Breaking a “shall” rule outside a constraint results in undefined behaviour. Annex J.2 listed two such cases:

  • UB 28: A UCN in an identifier does not designate a character in one of the permitted ranges.
  • UB 29: The first character of an identifier is a UCN designating a digit.

Take this example from the paper:

\u0030foo

\u0030 is the UCN for the digit 0.

Under C17, this was lexed as an identifier preprocessing token. A later semantic rule then stated that an identifier must not start with it, and breaking that rule resulted in undefined behaviour.

C23 Already Fixed the Grammar

C23 rewrote the identifier grammar around two Unicode character sets: XID_Start, which defines the characters allowed at the start of an identifier, and XID_Continue, which defines the characters allowed after the first character.

The grammar itself now enforces these rules.

Digits are included in XID_Continue but not XID_Start. This means an identifier cannot begin with the digit 0, whether it is written directly or as a UCN.

Under C23, the example is never lexed as an identifier, so there is nothing left for a later rule to make undefined.

A conforming C23 program therefore cannot trigger UB 28 or UB 29. WG14 describes these as ghost undefined behaviours: entries that remain in the Standard even though no program can trigger them.

The C23 wording retained the old “shall” requirements in its semantic rules, which is why the two entries remained in Annex J.2.

What N3904 Changes

N3904 makes three changes:

  1. Removes UB 28 and UB 29 from Annex J.2.
  2. Deletes redundant “shall” wording concerning XID_Start and XID_Continue from the semantic rules, since the grammar already enforces these requirements.
  3. Moves the Unicode Normalization Form C (NFC) requirement for identifiers from the semantic rules into the constraints.

The final change is particularly important in practice.

An identifier can match the grammar and still fail NFC. Under the previous wording, this resulted in undefined behaviour. Under N3904, it becomes a constraint violation, meaning the implementation must issue a diagnostic.

Why It Matters

Compilers and static analysers should apply consistent rules, and those rules should be straightforward to understand.

Programmers should not have to work through interactions between the lexical grammar, a semantic “shall” requirement and the general rules on undefined behaviour to determine whether an identifier is valid.

With N3904, the answer is clearer: the grammar and constraints define what is permitted, and invalid identifiers must be diagnosed.

Removing two obscure entries does not make C memory-safe. That still depends on good engineering, tools and coding practices. However, where the compiler has enough information to identify a problem, the Standard should require a diagnostic rather than list undefined behaviour that cannot occur.

Read the paper: N3904, Slaying the Earthly Demons of Invalid Identifier Characters (UB28, UB29).