G.0 How to Use This Annex
This is the complete list of differences between ISO C99 and Ocean Edition I. It is informative, and every entry points at the normative clause that governs.
Ocean Edition I derives from ISO C99, as clause 1.3 states. The differences fall into six kinds:
Removed. A construct ISO C99 has that this edition does not. In every case an existing C spelling already expresses the operation.
Defined. A behavior ISO C99 leaves undefined, unspecified, or implementation-defined that this edition gives one rule.
Canonicalized. A construct ISO C99 spells several ways that this edition spells one way.
Changed. A behavior that differs in result. This kind is rare and each instance is argued at its clause.
Adopted. A facility taken from a C standard published after 1999, or from settled practice that no standard has specified. Each adoption passes the test in clause 1.4.1.
Added. A facility no C standard provides. Exactly one exists, it passes the stricter test in clause 1.5.1, and clause G.6 records how.
G.1 Removed
| Construct | Replacement | Clause |
|---|---|---|
auto storage-class specifier |
ordinary block-scope declaration | 6.4.1.1 |
register storage-class specifier |
ordinary declaration | 6.4.1.1 |
| Function declarations with empty parentheses | f(void) |
6.7.5.3 |
| K&R function definitions | prototype-style definitions | 6.7.5.3 |
| Tentative definitions | one definition, plus extern declarations |
6.9.2 |
extern declaration with an initializer |
a definition without extern |
6.2.2.3 |
Bare inline and extern inline |
static inline |
6.7.4 |
| Implicit array-to-pointer conversion | &array[0] |
6.3.2.1 |
| Implicit function-to-pointer conversion | &function |
6.3.2.1 |
| Array-syntax function parameters | T *p, or T (*p)[N] |
6.7.5.2 |
static and qualifiers inside parameter array brackets |
write the pointer type | 6.7.5.2 |
| Variable-length arrays | fixed arrays, or allocated storage | 6.7.5.2 |
| Multi-character character constants | build the value explicitly | 6.4.4.4 |
| Trigraphs | write the character | 5.2.1.1 |
| Digraphs | write the punctuator | 5.2.1.1 |
| Sequence points | defined evaluation order | Annex E |
| Undefined behavior as a category | defined result, trap, or unsafe memory | 0.3 |
| Complex arithmetic as a required facility | optional extension | 6.2.5.2 |
gets |
fgets |
7.2.17 |
Integer constant 0 as a null pointer constant |
null or NULL |
6.3.2.3 |
NULL as a library macro |
a keyword owned by the language | 6.4.1.2 |
Note on what was not removed. goto, assignment expressions, the comma operator, compound literals, designated initializers, flexible array members, bitfields, unions, separate name spaces, C declarators, typedef, function pointers, and octal constants are all retained. The question was never whether a construct is old or unfashionable. The question is whether its rules are coherent. See syntax.md section 45.
G.2 Defined
G.2.1 Arithmetic
| ISO C99 | Ocean Edition I | Clause |
|---|---|---|
| Signed overflow is undefined | wraps, two's complement | 6.5.6 |
| Signed left shift overflow is undefined | defined bit pattern | 6.5.7 |
| Out-of-range shift count is undefined | defined trap | 6.5.7 |
| Signed right shift is implementation-defined | arithmetic shift | 6.5.7 |
| Division by zero is undefined | defined trap | 6.5.5 |
INT_MIN / -1 is undefined |
defined trap | 6.5.5 |
| Narrowing to a signed type is implementation-defined | truncates | 6.3.1.3 |
| Out-of-range float-to-integer conversion is undefined | defined trap | 6.3.1.4 |
| Signed representation is one of three | two's complement | 6.2.6.2 |
| Integer types may have padding bits | none | 6.2.6.2 |
| Integer types may have trap representations | none | 6.2.6.2 |
| Byte width is at least 8 | exactly 8 | 6.2.6.1 |
Plain char signedness is implementation-defined |
never negative | 6.2.6.3 |
Plain int bitfield signedness is implementation-defined |
signed | 6.7.2.2 |
G.2.2 Evaluation
| ISO C99 | Ocean Edition I | Clause |
|---|---|---|
| Subexpression order is unspecified | left to right | 6.5.0.1 |
| Argument order is unspecified | left to right | 6.5.2.2 |
| Multiple modification between sequence points is undefined | defined by order | 6.5.0.1 |
| Loop termination may be assumed | shall not be assumed | 6.8.5.4 |
| Optimizer may derive facts from undefined behavior | shall not | 4.7 |
G.2.3 Objects and storage
| ISO C99 | Ocean Edition I | Clause |
|---|---|---|
| Automatic objects are indeterminate | zero-initialized | 6.7.9.2 |
| Padding bytes are unspecified | zeroed on zero initialization | 6.2.6.1 |
| Structure copy leaves padding unspecified | deterministic | 6.2.6.1 |
| Effective type governs access | no effective type | 6.2.6.5 |
| Type-based aliasing permits assumptions | ordinary pointers may alias | 6.5.0.4 |
| Union type punning is not clearly permitted | defined | 6.5.2.3 |
| Pointer comparison across objects is undefined | compares addresses | 6.5.8 |
| Pointer arithmetic outside an array is undefined | computes an address | 6.5.6 |
setjmp locals are indeterminate without volatile |
last-stored value survives | 7.2.12 |
G.2.4 Declarations and diagnostics
| ISO C99 | Ocean Edition I | Clause |
|---|---|---|
| Enumeration compatible type is implementation-defined | first type that fits, from a fixed list | 6.7.2.3 |
Enumeration constants have type int |
have the enumerated type | 6.4.4.3 |
| An unrecognized escape sequence is undefined | constraint violation | 6.4.4.4 |
| An unrepresentable integer constant is undefined | constraint violation | 6.4.4.1 |
| Duplicate designated initializer silently wins | constraint violation | 6.7.9.4 |
| External identifiers may be case-insensitive and 31 characters | case-sensitive, at least 1024 | Annex B |
| Falling off a value-returning function may be harmless | diagnosed, or traps | 6.8.6.4 |
| Provable errors need not be diagnosed | shall be diagnosed | 4.3 |
| Nothing is said about translation output | artifacts shall be reproducible | 4.10, Annex K |
__DATE__ and __TIME__ read the clock |
the declared translation timestamp | K.5.2 |
__FILE__ may carry the working directory |
recorded under a documented path rule | K.6.3 |
G.2.5 Library
| ISO C99 | Ocean Edition I | Clause |
|---|---|---|
Overlapping memcpy is undefined |
overlap-safe | 7.1.6.3 |
| Zero-size copy with null pointers is undefined | no effect | 7.2.19 |
malloc(0) may return null or a unique pointer |
returns null | 7.2.18 |
abs(INT_MIN) is undefined |
defined trap | 7.2.18 |
Math functions may or may not set errno |
set errno |
7.2.6 |
A negative char to <ctype.h> is undefined |
impossible | 7.2.3 |
| Handler-safe function set is left to folklore | shall be published | 7.2.14 |
time_t range is unconstrained |
at least 64 bits | 7.2.23 |
| Format string mismatch need not be diagnosed | required diagnostic for literal formats | 7.2.17 |
G.3 Canonicalized
| ISO C99 accepts | Ocean Edition I | Clause |
|---|---|---|
long int, signed int, unsigned long int, and similar |
one canonical spelling per type | 6.7.2.1 |
| Declaration specifiers in any order | storage, qualifiers, type, declarator | 6.7.8 |
memcpy and memmove as distinct contracts |
one behavior, both names | 7.1.6.3 |
| Several inline linkage categories | static inline |
6.7.4 |
_Bool and bool as two names for one type |
bool |
6.7.2.1 |
Definition, tentative definition, extern declaration, extern definition |
definition, external declaration | 6.9.2 |
G.4 Changed
These are the entries where a conforming ISO C99 program produces a different result under Ocean Edition I. There are five, and each is argued at its clause.
G.4.1 Mixed signed and unsigned comparison
-1 < 1u
is false in ISO C99 and true in Ocean Edition I. Comparison operators compare mathematical values. Clause 6.3.1.7.
Arithmetic operators are unchanged, so -1 + 1u still produces a large unsigned value, and clause 6.3.1.6 requires a diagnostic for it.
G.4.2 String literal type
sizeof("hello")
is unchanged at 6, but the type is const char [6] rather than char [6], so assigning a literal to a plain char * is now an error. Clause 6.4.5.
G.4.3 strncpy termination
strncpy always null-terminates and no longer pads the destination. The traditional behavior is available as strncpy_padded. Clause 7.2.19.
G.4.4 The integer constant zero in pointer position
int *p = 0;
if (p == 0)
are constraint violations. Write null or NULL. Clause 6.4.4.6.
A related consequence is that NULL can no longer be assigned to an integer, so int n = NULL; stops compiling. Both changes catch code that was relying on a literal zero meaning two different things.
G.4.5 Default initialization
An automatic object with no initializer reads as zero rather than as whatever was in the storage. A program that depended on the previous contents was already relying on undefined behavior, so this is a change in result only for programs that were already broken. Clause 6.7.9.2.
G.5 Adopted
Every entry here comes from a source cited in clause 2.4, and each passed the adoption test of clause 1.4.1. An adopted facility is respecified on this edition's terms, so the rule that governs is the Ocean Edition I clause rather than the standard of origin.
G.5.1 From ISO/IEC 9899:2024
| Facility | Ocean Edition I | Clause |
|---|---|---|
bool as a keyword |
the canonical spelling of the boolean type; _Bool is rejected |
6.2.5.2, 6.4.1.2 |
true and false as keywords |
boolean constants of type bool |
6.4.4.5 |
nullptr typed null constant |
adopted under the spellings null and NULL, which become keywords |
6.4.4.6 |
nullptr_t |
adopted as null_t, with nullptr_t provided as an alias |
6.2.5.2, 7.2.16 |
__has_include |
required, usable in #if and #elif |
6.10.1 |
#warning |
required | 6.10.5.1 |
| Trigraphs removed | the character means itself | 5.2.1.1 |
| Old-style function definitions removed | prototype-style only | 6.7.5.3 |
| Two's complement required | the only signed representation | 6.2.6.2 |
Ocean Edition I goes further than C23 on the null constant as well. C23 adds nullptr while retaining NULL as a macro and retaining integer 0 as a null pointer constant, so it has three ways to write a null pointer. This edition has one constant with two spellings, and integer 0 is not among them.
Ocean Edition I goes further than C23 on the last three of the rows above. C23 removed trigraphs and K&R definitions, and this edition also removes the empty parameter list, which C23 retains. C23 requires two's complement and still permits padding bits and trap representations, which this edition does not.
G.5.2 From ISO/IEC 9899:2011
| Facility | Ocean Edition I | Clause |
|---|---|---|
max_align_t |
defined by <stddef.h> |
7.2.16 |
| Alignment query | alignof through <stdalign.h>, required |
7.2.21 |
gets removed |
use fgets |
7.2.17 |
quick_exit |
permitted in a signal handler where provided | 7.2.14 |
| Anonymous members, atomics, threads, static assertions | recommended extension spellings only | 6.11.5 |
The last row is not an adoption. It records that where an implementation chooses to provide one of these, it should use the C11 spelling rather than invent one. Clause 1.4.2 explains the distinction.
G.5.3 From settled practice
| Facility | Ocean Edition I | Clause |
|---|---|---|
#pragma once |
required, with a specified failure direction | 6.10.2.4 |
SOURCE_DATE_EPOCH |
recognized as a source of the declared translation timestamp | K.5.1 |
| Reproducible builds | required, with verification tooling | 4.10, Annex K |
strtok_r |
required alongside strtok |
7.2.19 |
Reentrant strerror, gmtime, localtime |
required, names documented | 7.2.19, 7.2.23 |
64-bit time_t |
required | 7.2.23 |
| Format string checking | required diagnostic for literal formats | 7.2.17 |
G.5.4 What was considered and not adopted
These are the near misses, recorded so that the adoption test can be seen working rather than merely stated.
| Facility | Source | Why not |
|---|---|---|
constexpr |
C23 | static const already serves, once clause 6.6.6 admits it as a constant expression. Fails test 3. |
typeof |
C23 | Adds a keyword to solve a macro problem, and this edition prefers static inline to macros. Fails tests 1 and 3. |
Attribute syntax [[...]] |
C23 | Large syntactic surface, and its uses are diagnostics rather than semantics. Fails tests 3 and 4. |
| Enumerations with a fixed underlying type | C23 | Clause 6.7.2.3 already fixes the underlying type deterministically, so the syntax adds nothing. Fails test 1. |
Generic selection _Generic |
C11 | Adds capability rather than removing a category, and belongs to the non-goals of syntax.md section 4. Fails test 1. |
| Atomics and threads | C11 | Large, and each requires a memory model this edition declines to define. Recommended extensions instead. Fails test 3. |
_Alignas |
C11 | An alignment query is needed and is required by clause 7.2.21. An alignment specifier is a new declaration form. Fails test 3. |
| Variably modified types | C99, retained in C23 | Removed by this edition, under clause 6.7.5.2. |
Binary literals 0b1010 |
C23 | Genuinely borderline. Adds no category and reads well, but adds lexical surface to solve a formatting preference. An implementation may provide it as an extension. Fails test 1 narrowly. |
| Digit separators | C23 | Same reasoning as binary literals. |
u8, u, and U string prefixes |
C11 | Source and execution character sets are both UTF-8 by default under clause 5.2.1, so ordinary literals already carry UTF-8. Fails test 1. |
G.6 Added
One facility is in this category: namespaces, comprising the namespace definition form, the #namespace directive, the :: qualification operator, and the #import directive.
It is the only surface syntax this edition adds. syntax.md section 5.1 records the decision, and clause 1.5.1 states the test.
G.6.1 What it consists of
| Construct | Clause | Purpose |
|---|---|---|
namespace a { } |
6.7.10 | places a region of declarations into a namespace |
namespace a::b { } |
6.7.10 | the same, nested |
#namespace "a::b" |
6.10.10 | places the containing source file into a namespace |
a::name |
6.5.1.1 | names a member of a namespace |
::name |
6.5.1.1 | names a member of the global namespace |
#import <h>, #import "h" |
6.10.11 | contributes a header's declarations rather than its text |
G.6.2 How it passes the addition test
Test 1, it solves a problem C programmers already solve by hand. Every large C program invents a prefix convention. net_socket_open is a namespace written by hand, spelled by agreement, and invisible to the compiler.
Test 2, it produces the same result the manual practice produces. net::open has the linkage name net__open. A C translation unit declares int net__open(const char *); and calls it. No shim, no wrapper, no attribute. See clause 6.9.6.5.
Test 3, it introduces no new runtime concept. No object model, no lifetime, no storage duration, no control flow, no calling convention. Clause 6.9.6.3 lists what membership does not affect.
Test 4, it lowers to existing C constructs. Removing the namespace and renaming each member to its linkage name produces an equivalent C program.
#import lowers to #include with two mechanical adjustments: an include guard is added to any imported header that lacks one, which reproduces idempotency, and any macro the header relied on receiving from its own imports is obtained by the corresponding #include at the top of the header, which self-contained headers already satisfy under clause 6.10.11.2.
The one behavior that does not survive the rewrite is the macro isolation of clause 6.10.11.2. A lowered header sees the includer's macros again. This is a loss of a guarantee rather than a change of meaning for any header that does not inspect macros it never defined, which is what a self-contained header is. A header that does inspect them is not mechanically lowerable, and clause H.2 says so. Annex H clause H.2 gives the full rewrite.
Test 5, it leaves the ABI unchanged. Layout, alignment, parameter passing, and linkage are untouched. Only the spelling of an external name changes, and that spelling is an ordinary C identifier.
Test 6, it changes no existing program. Source declaring no namespace is entirely in the global namespace and behaves as it did.
Test 7, it conflicts with the white papers, so they were amended first. syntax.md section 5 listed import among vocabulary the edition should not need, and section 53 declined to invent import syntax. Section 5.1 now records the exception, and sections 4 and 53 point at it. Clause 6.11.3 released import from the reserved list and kept module and export reserved.
G.6.3 What was deliberately left out
The facility is a naming mechanism, and everything that would make it a module system was excluded.
| Not provided | Why |
|---|---|
using, aliases, name injection |
qualification and outward lookup already reach every name |
| re-export | a namespace does not acquire another's members |
visibility control beyond static |
static already means not exported |
| interface files, module boundaries | dependency remains the business of the header |
| defining a member from outside its namespace | a second way to say one thing |
| overloading on namespace | membership is not part of a type |
extern "C" equivalent |
declaring at global scope already expresses it |
| implementation-defined mangling | C interop requires a name a programmer can write |
G.6.4 The cost
Two restrictions are the price of an injective, writable mangling.
Consecutive underscores are reserved. A namespace name, and an exported identifier inside a namespace, shall not contain __. Otherwise a::b__c and a__b::c collide. Clause 6.4.2.0.
A C library may collide. A C library exporting net__open collides with net::open. The mapping is injective within this edition and cannot police another language. An implementation diagnoses what it can see, and the residual risk is the one the prefix convention already carries. Clause 6.9.6.7.
G.7 Programs That Behave Identically
A program that satisfies all of the following behaves identically under ISO C99 and Ocean Edition I:
- it declares every function with a complete prototype,
- it writes
&array[0]and&functionwhere a pointer is wanted, - it does not rely on signed overflow, on out-of-range shifts, or on division by zero,
- it does not depend on evaluation order,
- it initializes every object before reading it,
- it does not compare a signed value with an unsigned value of equal or greater rank,
- it writes
NULLrather than a bare0in pointer position, and does not assignNULLto an integer, - it does not use variable-length arrays,
register,auto, K&R definitions, tentative definitions, or multi-character constants, - it treats string literals as immutable.
That description fits a large fraction of carefully written modern C, which is the point. Most of what this edition requires is what a careful C programmer already does. What changes is that the compiler now agrees.