Lexical Structure and Style
|
This section documents C23 (ISO/IEC 9899:2024), per ISO/IEC JTC1/SC22/WG14’s freely available working draft N3220, which WG14 documents as differing from the published standard only editorially — the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the WG14 draft and cppreference.com’s C reference before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Before the compiler can parse anything it splits the preprocessed source into tokens: keywords, identifiers, constants, string literals and punctuators. This page covers those building blocks, the names you are not allowed to invent, and the formatting conventions the C world has settled on.
Character Sets
C distinguishes the source character set (what the compiler reads) from the execution character set (what ends up in the running program). Both must contain the basic character set: the Latin letters, the digits, the space, control characters, and 29 punctuation characters.
In C23, source files are UTF-8 by default in every mainstream compiler, and universal character names are
written \u plus four hex digits or \U plus eight:
#include <stdio.h>
int main(void)
{
const char *ucn = "caf\u00e9 \u2014 na\u00efve"; // universal character names
const char *direct = "café — naïve"; // UTF-8 bytes, fine in practice
printf("%s\n%s\n", ucn, direct);
return 0;
}
C23 also allows extended characters in identifiers (int café = 1; is legal), though almost no codebase uses
this. See Strings and Text Processing for
char8_t, u8"" literals and the conversion functions.
Comments
/* A block comment.
It does not nest -- the first closing delimiter ends it. */
// A line comment, from C99 onwards.
int value = 1 /* inline */ + 2; // both forms are just whitespace to the compiler
Because a block comment does not nest, commenting out a region that already contains /* … */ breaks. Use
#if 0 … #endif for that instead — it nests properly and the editor still highlights the code:
int keep(void)
{
return 1;
}
#if 0
int disabled(void)
{
/* even with a comment inside, this whole region is skipped */
return 0;
}
#endif
Identifiers
An identifier starts with a letter or and continues with letters, digits or . There is no length limit in
practice, though the standard only guarantees 31 significant characters for internal names and 31 for external
ones (C99 raised the external limit from 6).
Case matters: count, Count and COUNT are three different names.
Reserved Names — What You Must Not Invent
This is the part that surprises people. The implementation reserves whole families of names, and using them is undefined behavior even when it compiles:
| Pattern | Reserved for |
|---|---|
Anything beginning with two underscores ( |
The implementation, always, at any scope. |
An underscore followed by an uppercase letter ( |
The implementation, always, at any scope. |
An underscore followed by anything else ( |
The implementation at file scope — fine as a local variable or a struct member. |
|
Future library extensions — do not add your own |
static int _bad_at_file_scope; // reserved: leading _ at file scope
static int good_at_file_scope; // fine
void f(void)
{
int _ok = 1; // fine: not file scope, single leading underscore
(void)_ok;
}
Prefix your project’s public names with a short project tag instead — iru_buffer_init, IRU_MAX_ITEMS.
Keywords
C23 has 59 keywords. The 48 in everyday use are in the table below; the remaining 11 are _Underscore-prefixed
and follow it. The words that became keywords in C23 — previously macros or _Underscore spellings — are
marked:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The older underscore spellings — _Bool, _Alignas, _Alignof, _Static_assert, _Thread_local,
_Noreturn, _Complex, _Imaginary, _Decimal32/64/128 — remain valid, which is what lets one header
compile under both C11 and C23.
Punctuators
The operators and separators: [ ] ( ) { } . →, ++ — & * + - ~ !, / % << >> < > ⇐ >= == != ^ | && ||,
? : ; …, = = /= %= += -= <⇐ >>= &= ^= |=, , # #, and the digraph forms <: :> <% %> %: %:%:.
Trigraphs (??= for , and friends) were *removed in C23.
Literals at a Glance
Every constant form in one place; each is covered in depth on its own page.
#include <stdint.h>
#include <stdio.h>
#include <uchar.h>
int main(void)
{
int dec = 42;
int oct = 052; // leading 0 -- octal, still 42
int hex = 0x2A; // 42
int bin = 0b101010; // C23 binary literal -- 42
int grouped = 1'000'000; // C23 digit separators
unsigned u = 42u;
long l = 42L;
unsigned long long ull = 42ULL;
double d = 3.14;
double sci = 1.6e-19;
double hexfloat = 0x1.8p1; // C99 hex float -- 3.0
float f = 3.14f;
long double ld = 3.14L;
char c = 'A';
int wide_ok = L'A' == 65; // wchar_t constant
char32_t u32 = U'€'; // euro sign
const char *s = "two adjacent " "literals are one";
const char *nl = "line\ttab\nnewline\\backslash\"quote";
bool yes = true; // C23 keywords
void *nothing = nullptr; // C23 null pointer constant
printf("%d %d %d %d %d %u %ld %llu %g %g %g %g %Lg %c %d %u %s %s %d %p\n",
dec, oct, hex, bin, grouped, u, l, ull, d, sci, hexfloat, (double)f, ld,
c, wide_ok, (unsigned)u32, s, nl, (int)yes, nothing);
return 0;
}
Adjacent string literal concatenation (phase 6 of translation) is what makes long messages and the
<inttypes.h> format macros readable:
#include <inttypes.h>
#include <stdio.h>
int main(void)
{
int64_t big = 9007199254740993;
printf("value = %" PRId64 "\n", big); // PRId64 expands to a string literal
return 0;
}
See Basic Types and Values for what type each constant
actually has, and Constants,
Enumerations and Initialization for constexpr and compound literals.
Attribute Syntax
C23 adopts the double-bracket attribute syntax, replacing a thicket of __attribute__((...)) and
__declspec(...) extensions:
#include <stdlib.h>
[[nodiscard]] int must_check(void); // caller must use the result
[[deprecated("use parse_all instead")]]
void parse(const char *s);
[[noreturn]] void fatal(const char *msg);
void handle(int code, [[maybe_unused]] int debug_flag)
{
switch (code) {
case 1:
[[fallthrough]]; // deliberate fall-through, silences -Wimplicit-fallthrough
case 2:
break;
default:
break;
}
}
[[noreturn]] void fatal(const char *msg)
{
(void)msg;
abort();
}
The standard attributes are [[deprecated]], [[fallthrough]], [[maybe_unused]], [[nodiscard]],
[[noreturn]], [[unsequenced]] and [[reproducible]]. Vendor attributes are namespaced
([[gnu::always_inline]]), and __has_c_attribute tests availability — see
Preprocessor and Macros.
Formatting and Naming Conventions
C has no official style guide. The three that a codebase is likely to follow are the Linux kernel style, the GNU coding standards, and the LLVM style. What they agree on:
-
Indent consistently — tabs at 8 (kernel) or 2/4 spaces (LLVM/GNU); never mix.
-
snake_casefor functions and variables,UPPER_SNAKE_CASEfor macros and enumeration constants. ReservePascalCasefortypedef-ed types if you use it at all. -
One declaration per line, and declare at first use (C99 onwards) rather than at the top of the block.
-
Braces on every
if/for/whilebody, even one-liners — this is what the Apple "goto fail" bug was. -
Pointer asterisk binds to the name:
char name, notchar name, becausechar* a, bdoes not do what it looks like. -
Prefix public symbols with a project tag, since C has no namespaces.
Do not hand-maintain any of this. clang-format enforces a named style mechanically:
$ clang-format --style=LLVM -i src/*.c include/*.h
$ clang-format --style="{BasedOnStyle: LLVM, IndentWidth: 4, ColumnLimit: 100}" -i src/*.c
See Also
-
Basic Types and Values — the type of every literal form above.
-
Preprocessor and Macros — what happens in translation phases 1-6.
-
Build and Tooling —
clang-format,clang-tidyand the warning flags that police style. -
C++: Lexical Structure and Style — the same tokens, plus raw and user-defined string literals.
References
-
WG14 N3220 — the C23 working draft (§6.4 "Lexical elements", §7.1.3 "Reserved identifiers", §6.7.13.1 "Attributes").