November 24, 2024·5 min read·engineeringcmemory-safetystandardsarrays

N3395 sketches restricted C modes for buffer safety

Martin Uecker proposes static and dynamic safety modes as subsets of C. The preliminary wording connects array bounds to expression restrictions while leaving substantial memory-safety work open.

A bounds check needs a bound associated with the operation being checked. A bare pointer expression does not always supply one. Martin Uecker's N3395, dated 23 November, approaches that difficulty by restricting which expressions a safety mode permits. The paper proposes preliminary wording for static and dynamic modes within C. It is a preview seeking feedback, with no claim to complete the language's memory-safety work.

C's small definition remains appealing to me, but writing it correctly requires understanding where the standard leaves behavior undefined. A restricted mode is interesting because it can make some of that boundary explicit in the permitted source. Its usefulness depends on which obligations the restriction actually establishes.

The proposal defines a subset of ordinary C

The WG14 document log records N3395 under 24 November. The paper itself carries the preceding day's date. Its status is a proposal for discussion, not an adopted change to C23. That distinction is especially important for a paper whose examples use a plausible standard pragma spelling.

The proposed directive selects STATIC, DYNAMIC or OFF. Each translation unit initially uses OFF. The mode applies to complete statements and declarations according to the setting active when their first token is processed. Static and dynamic modes add translation-time constraints, and dynamic mode adds runtime constraints that trap when violated.

The author explicitly wants these modes to remain strict subsets of unrestricted C. Removing the pragmas should not make a strictly conforming program invalid or change its observable behavior. This is a design constraint on the proposal. It aims to restrict programs without introducing a separate interpretation for ordinary successful executions.

That has a practical compatibility implication. A restricted region needs to be valid under the ordinary language rules as well as the extra constraints. The mode cannot serve as a reason to reinterpret an otherwise invalid operation. Its purpose is to impose additional obligations or checks on code that still has a conventional C meaning.

Array subscripting needs an identifiable array

For subscripting in either safety mode, the proposed pointer operand must result from conversion of a complete array, or be a function parameter declared with an array form containing static. This ties the permitted expression to information the proposal can use as a bound. It excludes general pointer subscripting simply because an unrelated variable happens to contain a length.

In static mode, the array length must be a known constant. The index must also be an integer constant expression, non-negative and below the array length. This is a deliberately strong restriction. It permits the required condition to be checked at translation time by constraining the shape of the source.

Dynamic mode permits a runtime index, with a trap when it is negative or at least the array length. The important distinction is where the condition can be decided. A static mode restricts the program until the relevant fact is available during translation. A dynamic mode retains an execution-time obligation and defines a response when the condition fails.

An application loop using a runtime index therefore has a different relationship with the two modes. Its intended accesses might all be in range, but that intention alone would not satisfy the proposed constant-expression rule for static mode. The dynamic path is relevant when the index must depend on runtime data. The proposal makes that cost and restriction visible in the mode choice.

Pointer operations face separate restrictions

N3395 also restricts pointer arithmetic in both safe modes, except for array subscripting. Its cast constraints reject conversions to pointer types except where the specified assignment rules permit them. These are source restrictions intended to prevent unrestricted pointer manipulation from bypassing the reasoning used for bounds.

Static indirection has a constrained form. Its operand must come from taking the address of a complete object or from the permitted array-style function parameter. Dynamic indirection instead includes a runtime null check that traps on null. These provisions address specific invalid accesses, with different conditions in the two modes.

A null check alone cannot establish that a non-null pointer still refers to a live object. That is an important limit when interpreting the paper. Its introduction explicitly focuses on bounds checking and expects further constraints and revisions. The proposed checks should be judged against that stated scope, without presenting them as a finished lifetime discipline.

I prefer that qualification to a broad claim that C has acquired memory safety. It makes the technical discussion assessable. A reviewer can ask whether the allowed expressions preserve the intended invariant, and identify the lifetime cases that need additional design. The result is a discussion about concrete rules instead of a label applied to the whole language.

The granularity affects how interfaces can be used

Applying a mode to statements and declarations gives the proposal a local integration point. A project could discuss a restricted region without requiring every source file to satisfy the same rules immediately. The ordinary interfaces crossing that region still matter, because the proposal only accepts particular pointer expressions.

That is a meaningful tension for existing C APIs. A function returning an unadorned pointer may be perfectly conventional, yet its result may not have the expression form static mode requires. Adopting the restriction can therefore require interface changes or a different mode choice. The issue is the information the interface conveys to the restricted region.

The array-parameter case is especially relevant. The source declaration carries an obligation about the supplied storage, and the proposed mode uses that declaration when determining what accesses to permit. It does not create evidence about an arbitrary caller merely by giving a parameter a different spelling. The caller and callee still have to satisfy the ordinary contract.

For this preview, the useful contribution is a concrete set of questions for further wording. The permitted operations identify where bounds information must remain available. The static rules expose which programs can be checked through constant expressions, while the dynamic rules identify checks deferred to execution. Those details give implementers and API authors specific constraints to evaluate before any wider claim about memory safety is justified.