C, Ocean Edition I

6.4 Lexical Elements

Clause
6.4
ISO C99 mapping
6.4
Status
Normative

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:

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:

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.