6.4.0 Tokens
token:
keyword
identifier
constant
string-literal
punctuator
preprocessing-token:
header-name
identifier
pp-number
character-constant
string-literal
punctuator
each non-white-space character that cannot be one of the above
A token is the minimal lexical element of the language in translation phases 3 and later. A preprocessing token is the minimal lexical element in phase 2.
If the input has been parsed into preprocessing tokens up to a given character, the next token is the longest sequence of characters that could constitute a token. This is the maximal munch rule, and it is unchanged from C:
x+++y /* parses as x ++ + y */
An implementation should diagnose an expression whose meaning depends on maximal munch in a way a reader is likely to misread.
Whitespace separates tokens where separation is needed and carries no other meaning. A comment is replaced by one space.
ISO C99 mapping: 6.4.
6.4.1 Keywords
The keywords of Ocean Edition I are:
bool break case char const
continue default do double else
enum extern false float for
goto if inline int long
namespace null NULL restrict return
short signed sizeof static struct
switch true typedef union unsigned
void volatile while
A keyword shall not be used as an identifier.
6.4.1.1 Keywords that are not part of this edition
auto removed; ordinary block-scope declaration means the same thing
register removed; register allocation belongs to the compiler
_Bool removed; the canonical spelling is bool
_Complex optional extension; see clause 6.2.5.2
_Imaginary optional extension; see clause 6.2.5.2
auto, register, and _Bool are reserved identifiers under clause 6.4.2.1 rather than keywords. Using auto or register as a declaration specifier is a constraint violation with a required diagnostic that names the replacement. Using either as an ordinary identifier is a constraint violation, so that source moving in either direction between editions does not silently change meaning.
_Bool used as a type specifier is a constraint violation with a required diagnostic naming bool, under clause 6.7.2.1.
Design note. auto communicates nothing, because automatic storage is already what a block-scope declaration means. register belongs to an era when a source-level hint could reasonably influence physical register allocation, and the compiler now knows more about liveness, pressure, calling convention, and escape than the keyword can express. See syntax.md sections 10 and 11.
_Bool is removed under the canonicalization rule of clause 6.7.2.1 rather than because anything is wrong with it. Two spellings of one type is one spelling too many, and bool is the one every programmer writes.
6.4.1.2 The null keywords
null and NULL are the two spellings of the null constant, described in clause 6.4.4.6. They are keywords of the language rather than macros supplied by a header.
Adopted from ISO/IEC 9899:2024, whose nullptr serves the same purpose under a different spelling. See clause 6.4.4.6 for what the promotion changes and clause 1.4 for the adoption policy.
6.4.1.3 The boolean keywords
bool is the canonical spelling of the boolean type, described in clause 6.2.5.2.
true and false are boolean constants, described in clause 6.4.4.5.
Adopted from ISO/IEC 9899:2024. In ISO C99 these three are macros defined by <stdbool.h>, expanding to _Bool, 1, and 0. Making them macros has three consequences a programmer eventually meets: a header must be included before a fundamental type can be named, true and false have type int rather than boolean type, and any translation unit can quietly redefine them.
The adoption passes clause 1.4.1. It removes a category, since a fundamental type stops being a library facility. It adds no capability. It adds keywords, which normally requires clearing a high bar, and it clears the bar because existing C syntax genuinely cannot express "this is a fundamental type" through a macro. Source that already writes bool looks unchanged. See clause 7.2.15 for what happens to <stdbool.h>.
6.4.1.4 The namespace keyword
namespace introduces a namespace definition, described in clause 6.7.10.
It is the only identifier the namespace mechanism adds to the language. The #namespace and #import directives spell their names after a #, so neither namespace in that position nor import anywhere is an ordinary identifier collision, and a program may use import as an identifier.
Added under clause 1.5. This is the one facility admitted by the addition test, and syntax.md section 5.1 records the reasoning.
6.4.1.5 Keywords retained with narrowed meaning
static, extern, and inline are retained with the meanings given in clause 6.7.1 and clause 6.7.4. Their historical secondary uses are removed:
staticinside array parameter brackets is not accepted. See clause 6.7.5.2.externon a declaration with an initializer is not accepted. See clause 6.2.2.4.- bare
inlineandextern inlinewith C99 linkage semantics are not accepted. See clause 6.7.4.
ISO C99 mapping: 6.4.1.
6.4.2 Identifiers
identifier:
identifier-nondigit
identifier identifier-nondigit
identifier digit
identifier-nondigit:
nondigit
universal-character-name
other implementation-defined characters
nondigit:
_ a b c d e f g h i j k l m
n o p q r s t u v w x y z
A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z
digit:
0 1 2 3 4 5 6 7 8 9
An identifier denotes an entity according to its scope and its name space. See clause 6.2.1 and clause 6.2.3.
Identifier comparison is case sensitive, so value, Value, and VALUE denote different entities.
An implementation shall support at least the ASCII letters, the underscore, and the decimal digits after the first character. An implementation may accept additional Unicode identifier characters, and should follow Unicode Standard Annex 31 when it does. An implementation that accepts them shall document the set and shall apply a documented normalization so that two identifiers that look identical are either the same identifier or are diagnosed as a collision.
An implementation shall distinguish identifiers by at least the number of initial characters given in Annex B clause B.1. Implementations should place no limit at all.
6.4.2.0 Identifiers and namespace qualification
Constraint. An identifier declared with external linkage inside a namespace shall not contain two consecutive underscores. A namespace name shall not contain two consecutive underscores.
The restriction keeps the linkage name mapping of clause 6.9.6.4 injective. Without it, a::b__c and a__b::c would both have the linkage name a__b__c, and the collision would be undetectable across translation units.
namespace net
{
int read_frame(void); /* fine */
int read__frame(void); /* constraint violation */
}
namespace net__util { } /* constraint violation */
The restriction applies only where it is needed. An identifier outside any namespace may contain consecutive underscores, and so may an identifier with internal linkage or no linkage inside a namespace, because neither receives a linkage name.
An implementation shall diagnose a violation and shall say that consecutive underscores are reserved to namespace qualification.
ISO C99 mapping: 6.4.2.1.
6.4.2.1 Reserved identifiers
The following are reserved for the implementation, and a program that declares one has behavior this specification does not define:
- every identifier beginning with an underscore followed by an uppercase letter or by a second underscore, in any context,
- every identifier beginning with an underscore, at file scope, in either the ordinary or the tag name space,
- every identifier with external linkage declared by any standard header the program includes,
- every macro name defined by any standard header the program includes, while that header's definitions are in effect,
- the identifiers
autoandregister.
6.4.2.2 Predefined identifiers
Inside every function body the identifier __func__ is implicitly declared as if by:
static const char __func__[] = "function-name";
where function-name is the name of the enclosing function.
Because arrays do not convert to pointers, passing __func__ to a function that takes a const char * is written:
log_line(&__func__[0], message);
ISO C99 mapping: 6.4.2.2.
6.4.3 Universal Character Names
universal-character-name:
\u hex-quad
\U hex-quad hex-quad
hex-quad:
hexadecimal-digit hexadecimal-digit hexadecimal-digit hexadecimal-digit
A universal character name designates the character with the given ISO/IEC 10646 short identifier.
A universal character name shall not designate a code point below 00A0, other than 0024 for $, 0040 for @, and 0060 for a backtick, and shall not designate a surrogate code point in the range D800 through DFFF.
Because source text is UTF-8, a program may write the character directly instead, subject to the implementation's identifier and literal support. Direct spelling is preferred where the implementation supports it.
ISO C99 mapping: 6.4.3.
6.4.4 Constants
constant:
integer-constant
floating-constant
enumeration-constant
character-constant
boolean-constant
null-constant
Every constant has a type determined by its form and value.
ISO C99 mapping: 6.4.4.
6.4.4.1 Integer constants
integer-constant:
decimal-constant integer-suffix-opt
octal-constant integer-suffix-opt
hexadecimal-constant integer-suffix-opt
integer-suffix:
unsigned-suffix long-suffix-opt
unsigned-suffix long-long-suffix
long-suffix unsigned-suffix-opt
long-long-suffix unsigned-suffix-opt
The suffixes are u or U for unsigned, l or L for long, and ll or LL for long long. A suffix shall not mix cases within the long long spelling, so lL is invalid.
Type of an integer constant.
The type is the first type in the applicable list below in which the value can be represented:
| Form | Candidate list |
|---|---|
| decimal, no suffix | int, long, long long |
| octal or hexadecimal, no suffix | int, unsigned int, long, unsigned long, long long, unsigned long long |
suffix u |
unsigned int, unsigned long, unsigned long long |
suffix l |
long, long long |
suffix u and l |
unsigned long, unsigned long long |
suffix ll |
long long |
suffix u and ll |
unsigned long long |
If the value cannot be represented by any type in its list, the constant is a constraint violation with a required diagnostic. An implementation shall not silently give the constant an extended type, and shall not wrap it.
Difference: ISO C99 makes an unrepresentable constant undefined in some paths and permits an extended integer type in others. Ocean Edition I rejects it.
Note. Octal constants are retained. A leading zero introduces an octal constant, which is a genuine trap for readers, so an implementation should diagnose a multi-digit octal constant whose digits are all valid decimal digits and whose value would differ if read as decimal.
ISO C99 mapping: 6.4.4.1.
6.4.4.2 Floating constants
floating-constant:
decimal-floating-constant
hexadecimal-floating-constant
A decimal floating constant has a significand, an optional exponent introduced by e or E, and an optional suffix. A hexadecimal floating constant has a 0x prefix, a hexadecimal significand, a binary exponent introduced by p or P, and an optional suffix. The binary exponent is required in the hexadecimal form.
The suffix f or F gives type float. The suffix l or L gives type long double. No suffix gives type double.
The value is the nearest representable value, or one of the two nearest, chosen in an implementation-defined manner consistent with the translation-time rounding mode. An implementation should round to nearest.
If the value is outside the range of the type, the constant is a constraint violation.
ISO C99 mapping: 6.4.4.2.
6.4.4.3 Enumeration constants
enumeration-constant:
identifier
An enumeration constant declared in an enum specifier has the enumerated type being declared. See clause 6.7.2.2.
Difference: ISO C99 gives an enumeration constant type int. Ocean Edition I gives it the enumerated type, so that the declaration means what it looks like it means. Ordinary integer conversions still apply where a program needs them. See behavior.md section 19 and syntax.md section 51.
ISO C99 mapping: 6.4.4.3.
6.4.4.4 Character constants
character-constant:
' c-char-sequence '
L' c-char-sequence '
A c-char-sequence shall contain exactly one character or one escape sequence.
An ordinary character constant has type char. A wide character constant prefixed with L has type wchar_t.
Multi-character constants are not part of this edition.
'abcd' /* invalid */
A program that needs a packed value builds it explicitly:
uint32_t tag = ((uint32_t)'a' << 24)
| ((uint32_t)'b' << 16)
| ((uint32_t)'c' << 8)
| (uint32_t)'d';
Difference: ISO C99 gives a multi-character constant type int with an implementation-defined value. The construct is easily confused with a string literal, and its value cannot be relied upon. See syntax.md section 43 and behavior.md section 83.
Escape sequences.
| Escape | Meaning |
|---|---|
\' |
apostrophe |
\" |
quotation mark |
\? |
question mark |
\\ |
backslash |
\a |
alert |
\b |
backspace |
\f |
form feed |
\n |
new line |
\r |
carriage return |
\t |
horizontal tab |
\v |
vertical tab |
\0 through \777 |
octal escape, one to three octal digits |
\xhh... |
hexadecimal escape, one or more hexadecimal digits |
\uXXXX, \UXXXXXXXX |
universal character name |
An octal or hexadecimal escape whose value exceeds the range of the character type of the literal is a constraint violation. A backslash followed by any character not listed above is a constraint violation, so that a mistyped escape is caught rather than silently meaning itself.
Difference: ISO C99 makes an unrecognized escape undefined and an out-of-range escape implementation-defined. Both are rejected here.
ISO C99 mapping: 6.4.4.4.
6.4.4.5 Boolean constants
boolean-constant: one of
true false
true and false are constants of type bool, with the values 1 and 0.
They are integer constant expressions under clause 6.6.2, so they may appear in an array bound, in a case label, in a bitfield width, and in the controlling expression of a #if directive:
bool ready = false;
if (ready == true)
{
...
}
#if true
...
#endif
Difference: ISO C99 defines true and false as macros in <stdbool.h> expanding to the integer constants 1 and 0, giving them type int. Here they are keywords with boolean type. The practical consequences are that sizeof(true) is sizeof(bool) rather than sizeof(int), and that a program cannot redefine them.
Note. Because a bool converts to int under the integer promotions, true remains usable wherever the integer constant 1 was usable, including in arithmetic. What changes is the type it starts with, not where it can appear.
ISO C99 mapping: 7.16, promoted to the core language. Adopted from ISO/IEC 9899:2024. See clause 1.4.
6.4.4.6 Null constants
null-constant: one of
null NULL
null and NULL are two spellings of one constant. They are identical in every respect, and an implementation shall not distinguish them.
The constant has type null_t, described in clause 6.2.5.2. Its value is the null pointer value, and it is the sole value of its type.
int *p = null;
int *q = NULL; /* identical */
if (p == null)
{
...
}
Style. New code should choose one spelling and use it throughout. null sits alongside true and false as a lowercase language constant, and NULL matches the spelling that existing C uses. The language expresses no preference, and an implementation shall not diagnose either.
What the promotion changes.
NULL in ISO C99 is a macro supplied by several headers, defined as either 0 or (void *)0 at the implementation's choice. Three consequences follow from that, and all three disappear here.
A program could not rely on the type. Where NULL expanded to 0 it was an int, so printf("%p ", NULL) passed an int where a pointer was required, and a sentinel argument in a variadic call was the wrong width on any target where a pointer is wider than an int.
A program could assign it to an integer. int count = NULL; compiled silently, because it was an assignment of the integer constant 0.
A program could redefine it. Any translation unit could #undef NULL and supply its own, and any header could disagree with any other.
Because the null constant now has its own type, none of these is possible. It converts to any pointer type, it does not convert to any integer type, and clause 6.10.3.3 forbids defining or removing it.
int *p = null; /* fine */
int count = null; /* constraint violation */
char c = NULL; /* constraint violation */
printf("%p
", null); /* passes a pointer-sized null */
The integer constant 0 is not a null pointer constant.
int *p = 0; /* constraint violation; write null */
if (p == 0) /* constraint violation; write p == null */
This is the rule of clause 6.3.2.3, and it follows from Law 2. An integer constant has integer type, and it does not acquire pointer type because of the context it appears in. The category that let a literal zero mean an address is the same kind of context-sensitive retyping that this edition removes from arrays and from function designators.
Difference: ISO C99 defines NULL as a library macro and makes any integer constant expression with value 0 a null pointer constant. ISO/IEC 9899:2024 adds nullptr with a distinct type while retaining both older behaviors alongside it. Ocean Edition I takes the C23 improvement and does not retain what it replaced, so there is one way to write a null pointer instead of three.
ISO C99 mapping: 7.17, promoted to the core language. The typed-constant behavior is adopted from ISO/IEC 9899:2024 nullptr. See clause 1.4.
6.4.5 String Literals
string-literal:
" s-char-sequence-opt "
L" s-char-sequence-opt "
Type.
An ordinary string literal has type const char [N], where N is the number of characters in the sequence plus one for the terminating null character.
sizeof("hello") /* 6 */
A wide string literal prefixed with L has type const wchar_t [N].
Immutability.
A string literal designates an object with static storage duration whose contents shall not be modified. The const in its type states this, so an attempt to modify it through a valid access path is a constraint violation rather than a runtime surprise.
Difference: ISO C99 gives a string literal type char [N] while making modification undefined, which is a type that disagrees with the rule. Ocean Edition I makes the type tell the truth. A consequence is that assigning a string literal to a plain char * is a qualification error, and programs write const char *. See behavior.md section 81.
Concatenation.
Adjacent string literal tokens are concatenated in translation phase 3 into a single literal. A wide literal adjacent to an ordinary literal produces a wide literal.
const char *message =
"hello "
"world";
Identity.
Whether two string literals with the same contents designate the same object is unspecified. Programs shall not depend on the identity or the distinctness of string literal objects. This is one of the few unspecified behaviors the edition retains, because merging identical literals is a real and valuable size optimization on every target. See annex-c-implementation-defined-behavior.md clause C.9.
Conversion to a pointer.
A string literal converts to a pointer to its first element only in the positions listed in clause 6.3.2.4.
ISO C99 mapping: 6.4.5.
6.4.6 Punctuators
[ ] ( ) { } . ->
++ -- & * + - ~ !
/ % << >> < > <= >= == != ^ | && ||
? : :: ; ...
= *= /= %= += -= <<= >>= &= ^= |=
, # ##
Digraph spellings are not part of this edition. See clause 5.2.1.1.
The :: punctuator.
:: performs namespace qualification, described in clause 6.5.1.1. It is the only punctuator the namespace mechanism adds.
Because tokenization takes the longest sequence that could form a token, a : immediately followed by :: is read as :: followed by :, which is not what a program writing a global qualification in a conditional operator intends. A space separates them:
a ? b : ::open() /* correct */
a ? b :::open() /* invalid: reads as :: then : */
An implementation shall diagnose the second form rather than reporting a syntax error further along, since the intent is evident.
No other adjacency is affected. A case label, a bitfield width, and a nested conditional all place : where a following :: cannot begin.
ISO C99 mapping: 6.4.6.
6.4.7 Header Names
header-name:
< h-char-sequence >
" q-char-sequence "
A header name is recognized only within a #include directive and within a __has_include operator where the implementation provides one.
The characters between the delimiters are mapped to a header or to a source file in an implementation-defined manner. A backslash, a double quote inside angle brackets, or a // or /* sequence in either form is a constraint violation, because the intent cannot be determined.
ISO C99 mapping: 6.4.7.
6.4.8 Preprocessing Numbers
pp-number:
digit
. digit
pp-number digit
pp-number identifier-nondigit
pp-number e sign
pp-number E sign
pp-number p sign
pp-number P sign
pp-number .
A preprocessing number has no value and no type until it is converted to an integer constant or a floating constant in translation phase 3. A preprocessing number that does not convert to a valid constant is a constraint violation.
ISO C99 mapping: 6.4.8.
6.4.9 Comments
Two comment forms exist, and both are replaced by one space.
/* a block comment, which does not nest */
// a line comment, which ends at the newline
A // inside a block comment has no special meaning, and a /* inside a line comment has no special meaning.
An unterminated block comment is a constraint violation.
ISO C99 mapping: 6.4.9.