C, Ocean Edition I

Annex H. Migration and Interoperation

Annex
H
ISO C99 mapping
new
Status
Informative

H.0 The Goal

Moving code between ISO C99 and Ocean Edition I should be an engineering task rather than a port to a different programming model. In both directions, the work is usually mechanical cleanup.

A C programmer should be able to read Ocean Edition I immediately. An Ocean Edition I programmer should be able to return to C99 without learning another paradigm. See syntax.md section 3.


H.1 Moving C99 Source into Ocean Edition I

H.1.1 What the compiler will tell you

A conforming implementation produces a diagnostic for each construct that needs changing, and shall offer a suggested rewrite wherever one exists. The diagnostics a migration typically produces:

function declaration does not specify parameters
implicit array-to-pointer conversion
implicit function-to-pointer conversion
variable-length array
obsolete storage specifier
extern declaration contains initializer
duplicate file-scope definition
noncanonical declaration spelling
noncanonical type spelling
array-syntax function parameter
old-style function definition
multi-character character constant
string literal assigned to a non-const character pointer
mixed signed and unsigned arithmetic conversion
noncanonical type spelling: _Bool
macro definition of a keyword
integer constant used as a null pointer
null constant assigned to an integer

H.1.2 The rewrite table

Diagnostic Rewrite
function declaration does not specify parameters int f(); becomes int f(void);
implicit array-to-pointer conversion f(a) becomes f(&a[0])
implicit function-to-pointer conversion f(g) becomes f(&g)
array-syntax function parameter void f(int a[]) becomes void f(int *a, size_t n)
variable-length array allocate, or use a fixed maximum
obsolete storage specifier delete register or auto
extern declaration contains initializer delete extern
duplicate file-scope definition keep one definition; make the others extern
noncanonical declaration spelling reorder to storage, qualifiers, type, declarator
noncanonical type spelling unsigned long int becomes unsigned long
old-style function definition move the parameter types into the parameter list
multi-character character constant build the value with shifts
string literal assigned to char * make the pointer const char *, or copy the literal
bare or extern inline add static
gets fgets with an explicit size
noncanonical type spelling _Bool bool
macro definition of bool, true, false, null, or NULL delete the definition; they are keywords
integer constant used as a null pointer int *p = 0 becomes int *p = null
p == 0 on a pointer p == null
int n = NULL int n = 0

H.1.3 Order of work

A migration goes faster in this order, because each step reduces the noise in the next.

  1. Fix declarations: prototypes, canonical spellings, canonical order, storage specifiers.
  2. Fix definitions: remove tentative definitions and extern initializers.
  3. Fix inline.
  4. Fix explicit addressing: arrays and functions at call sites.
  5. Fix remaining diagnostics: literals, parameters, variable-length arrays.
  6. Review the warnings that are not errors, especially mixed signed and unsigned arithmetic.

Steps 1 through 3 are almost entirely automatable. Step 4 is mechanical but touches many lines. Step 6 is the only step that requires thought, and it is the step that finds real defects.

H.1.4 What you gain, quietly

After migration, several classes of defect become impossible or become visible:

H.1.5 Adopting namespaces in a prefixed codebase

A codebase using a prefix convention is already namespaced by hand, and converting it is a rename.

For a module whose names all begin with net_:

  1. add #namespace "net" at the top of each of its source files and headers,
  2. remove the net_ prefix from each declaration in those files,
  3. change each use from another module to net::,
  4. change #include to #import for those headers.

The linkage names change from net_open to net__open, so the whole module shall be converted together, and any C code calling it shall be updated to the new spelling. A codebase that cannot change its exported names should keep the prefix and skip this facility, which costs nothing since namespaces are optional.

Check for the underscore restriction first. Clause 6.4.2.0 forbids __ in an exported name inside a namespace. A codebase already using net__internal_helper shall rename before converting. An implementation diagnoses this, so the check is a compile rather than an audit.

H.1.6 Code that will not migrate cleanly

Two categories need judgment rather than a rewrite.

Code that relies on type-based aliasing for speed. Ocean Edition I does not infer non-aliasing from types, so a loop that was fast because the compiler assumed float * and int * were distinct may slow down. The fix is restrict, which says the same thing where a reader can see it. See clause 6.7.3.3.

Code that uses variable-length arrays structurally. A function taking int m[rows][cols] has no direct Ocean Edition I spelling. The rewrite is a pointer plus explicit index arithmetic, or a structure carrying the extents, and it is a real change to the interface.


H.2 Moving Ocean Edition I Source into C99

Most Ocean Edition I source is already valid C99, because the edition adds almost no syntax. The namespace mechanism of clause 1.5.2 is the exception, and it lowers by renaming. The constructs needing a lowering:

Ocean Edition I construct C99 form
&array[0] valid C99 already
&function valid C99 already
canonical declarations valid C99 already
static inline in headers valid C99 already
defined evaluation order name the intermediate values
default zero initialization write = { 0 } or = 0 explicitly
wrapping signed arithmetic use unsigned types, or compiler flags
trapping division and shifts insert explicit checks
value-preserving mixed comparison insert an explicit sign check
const char[] string literals usually already correct
namespace and :: rename each member to its linkage name
#namespace "a" delete, and prefix each declaration with a__
#import #include, with an include guard added to the header
bool, true, false keywords #include <stdbool.h>
null keyword NULL, with #include <stddef.h>
null_t void *, or C23 nullptr_t
#pragma once an include guard

Evaluation order.

foo(a(), b());

lowers to:

T0 _a = a();
T1 _b = b();

foo(_a, _b);

Zero initialization.

struct Buffer buffer;

lowers to:

struct Buffer buffer = { 0 };

Boolean keywords.

Source using bool, true, and false compiles as C99 once <stdbool.h> is included, which a translator can add unconditionally. The only visible difference is that sizeof(true) becomes sizeof(int), and a program that depends on that was doing something unusual.

Namespaces.

A namespace lowers by renaming. Each member with external linkage takes its linkage name, and each qualified use is rewritten to match:

#namespace "net"

int open(const char *host)
{
	return 0;
}

becomes:

int net__open(const char *host)
{
	return 0;
}

and net::open(&host[0]) becomes net__open(&host[0]).

A member with internal linkage keeps its name, since it has no linkage name and cannot collide across units. Where two static members of different namespaces share a name in one translation unit after lowering, the translator renames one.

#import lowers to #include. Since an imported header needs no guard under clause 6.10.11.4 and an included one does, the translator adds a guard to any header that lacks one.

The macro isolation of clause 6.10.11.2 does not survive the rewrite, because #include has no way to express it. A lowered header sees the including file's macros again. For a self-contained header this changes nothing, since such a header does not consult a macro it did not define or import. A header that does consult one is not mechanically lowerable, and shall be rewritten by hand or kept in this edition.

Null constants.

NULL compiles as C99 once <stddef.h> is included. null lowers to NULL. The distinct type is lost, so a C99 back-translation reintroduces the possibility of assigning a null constant to an integer, which the original program did not do.

Header protection.

#pragma once

lowers to an include guard using a name derived from the file path:

#ifndef OCEAN_GUARD_buffer_h
#define OCEAN_GUARD_buffer_h
...
#endif

Mixed comparison.

if (i < u)

lowers to:

if (i < 0 || (unsigned)i < u)

A translator performing these lowerings mechanically produces portable C99 from Ocean Edition I source, which makes the edition usable as a source language for targets whose only compiler is a C compiler.


H.3 Interoperation Without Migration

Migration is not always necessary. Ocean Edition I links with C directly.

H.3.1 Calling C from Ocean Edition I

Declare the C function with a complete prototype, include its header, and call it. Arguments are passed under the target ABI, which both editions share.

#include <stdio.h>

char line[256];

if (fgets(&line[0], sizeof(line), stdin) != NULL)
{
	process(&line[0]);
}

The only adjustment is the explicit element pointer.

H.3.2 Calling Ocean Edition I from C

An Ocean Edition I function is an ordinary function in the target ABI. A C translation unit declares it and calls it. Where the function is a member of a namespace, the C declaration uses its linkage name, and clause H.3.3 gives the rule.

A header shared by both editions should be written so that it is valid in each. That means complete prototypes, no array-syntax parameters, canonical type spellings, and static inline rather than bare inline. Such a header is valid C99 and valid Ocean Edition I with no conditional compilation.

Four facilities need care in a shared header. A header included by ISO C source shall not use namespace, ::, or #import, since none exists in that edition; declare linkage names directly and let an Ocean Edition I file supply the namespaced view. Use an include guard rather than #pragma once, because only this edition requires the pragma. Include <stdbool.h> before writing bool, which is harmless in Ocean Edition I under clause 7.2.15 and necessary in C99. Write NULL rather than null and include <stddef.h>, since NULL is spelled the same in both and null exists only here.

H.3.3 Namespaces across the boundary

Calling Ocean Edition I from C. Declare the linkage name, which clause 6.9.6.4 fixes:

/* Ocean Edition I: net.c */
#namespace "net"

int open(const char *host)
{
	...
}
/* ISO C: caller.c */
extern int net__open(const char *host);

int main(void)
{
	return net__open("example.org");
}

The rule is mechanical. Join the namespace names and the identifier with two underscores.

Calling C from Ocean Edition I. A C entity is a member of the global namespace and needs no qualification:

#namespace "net"

#include <stdio.h>

int open(const char *host)
{
	printf("opening %s\n", host);      /* the global printf */
	return 0;
}

Exporting an unqualified name. Declare it outside any namespace. In a file governed by #namespace, that means putting the declaration in a different file, since the directive governs the whole file. In a file using the definition form, it means putting the declaration outside the braces.

H.3.4 The semantic boundary

Semantics do not cross the call. An Ocean Edition I function evaluates its body under this specification, and a C function evaluates its body under ISO C99, whichever unit called which. See clause 4.6.2.

Practical consequences:

An implementation should document how it satisfies the library guarantees of clause 7 when the host platform's C library does not.

H.3.5 System headers

System headers are written in C and are not under the program author's control. They frequently use array-syntax parameters and noncanonical spellings.

An implementation should provide a documented way to consume such a header under C99 rules while compiling the including unit as Ocean Edition I, checking declarations for compatibility at the boundary. See clause 6.10.9.


H.4 Mixed Codebases

A codebase can hold both editions indefinitely. A workable arrangement:

This is the arrangement the front end architecture of syntax.md section 58 was designed to support. Legacy semantics live in the C99 front end, edition semantics live in the Ocean Edition I front end, and the back end serves both.