Can the C language itself be changed so that the class of problems examined in this book can be prevented by the language?
There is an obvious objection to asking this question. C is not an unconstrained design exercise. It is an international standard with an established purpose, an existing body of programs and implementations, and a committee with an explicit charter governing how the language is to evolve.
That charter is therefore the proper place to begin.
The current C Standard charter describes the work of WG14 as a balancing act. It calls for new ideas to be incorporated without disrupting the basic structure and nature of the language, while also avoiding the invalidation of existing programs. Among its stated principles are maintaining the character of C, keeping the language small and simple, facilitating portability, preserving programming freedom, easing migration to newer editions, and avoiding quiet changes to existing programs.
At the same time, the charter explicitly identifies software security and safety as areas the committee should address. It states that memory safety, type safety and related properties are prerequisites for reasoning about security and reliability, and calls for the language to enable secure programming. It also calls for the enlargement of analyzable subsets of the language and for the elimination of unbounded undefined behaviours that represent single points of failure.
These principles do not produce a simple answer.
They establish a tension.
The charter asks C to address memory safety while also asking C to remain C. It asks for improvements without abandoning the language's character, and for security without turning C into a fundamentally different language. It asks for greater analyzability while preserving programming freedom and portability.
That tension is not incidental to the question of memory safety.
A language cannot guarantee a property that its semantics do not define.
For C to provide a language-level memory-safety guarantee, the language would have to define enough information to establish at least the following:
- which object a memory access concerns;
- what portion of that object may be accessed;
- whether the object is currently alive;
- whether a pointer or reference remains valid;
- and whether the operations used to obtain those properties preserve them.
Traditional C does define objects, lifetimes, pointer types, arrays and rules governing access to objects. The problem is that these concepts do not form a comprehensive system in which the validity of every memory access can be established from the program itself.
Consider a pointer obtained from dynamic allocation.
The programmer may know:
- this pointer refers to an array of N objects;
- the allocation contains exactly N objects;
- the pointer may be used until the allocation is released;
- the index must remain within those N objects.
Traditional C does not require all of that knowledge to travel with the pointer.
The programmer maintains the relationship.
The language provides only part of it.
That is the underlying difficulty exposed throughout this book.
If the language itself is to guarantee memory safety, some of those relationships must become language-level properties.
This does not necessarily prescribe a particular mechanism. Bounds, ownership, lifetimes, capabilities, contracts and other techniques are different ways of expressing or enforcing such properties.
But the requirement is unavoidable.
The language must know enough to distinguish a valid memory operation from an invalid one.
That immediately creates a problem for the C charter.
A language with significantly richer semantic relationships between values, objects and pointers is more expressive, but it is also more complex.
The charter explicitly says that C should remain small and simple and that language issues should be addressed with minimal new machinery. It also says that C should preserve programming freedom and remain broadly portable.
There is therefore a limit to how much semantic machinery can reasonably be added to C.
This is not a matter of whether the machinery can be invented.
It is a matter of whether adding it would still satisfy the principles under which C is maintained.
There is another, more fundamental constraint.
The charter places considerable importance on existing programs and on migration between language editions. The committee seeks to avoid forcing the bulk of existing C code to change merely to remain acceptable to a conforming implementation.
Comprehensive memory safety creates an unavoidable difficulty here.
Some existing C programs perform operations that a memory-safe language cannot permit without additional information or restrictions.
If a program constructs a pointer whose bounds cannot be established, a memory-safe language cannot simply assume the bounds are correct.
If a program retains a pointer after the lifetime of its object has ended, a memory-safe language cannot regard that pointer as valid.
If a program uses an integer as a memory extent without the language being able to establish the relationship between that integer and the object it describes, the language cannot guarantee that the resulting access is valid.
These are not shortcomings that can be repaired merely by changing the wording of a diagnostic.
If the language is required to guarantee the property, programs that violate the property must either acquire enough information to establish their correctness or cease to be valid programs under the guarantee.
This is where memory safety comes directly into conflict with the charter's compatibility and migration principles.
There is, however, an important distinction that prevents the problem from becoming a simple choice between unrestricted C and a completely different language.
A language can define a safe subset while retaining operations outside that subset.
The distinction is already familiar in other programming languages. An unsafe operation is not claimed to be safe. Rather, the language makes clear that its normal safety guarantees do not apply.
This is particularly relevant to C because programming freedom is one of the principles of the charter.
C is deliberately capable of expressing machine-dependent operations. Its value as a systems programming language depends partly upon that freedom.
It would therefore be quite different to say:
C must no longer permit this operation.
than to say:
this operation is outside the guarantees of the safe subset.
The second approach preserves the operation.
It does not, however, solve the whole problem.
The question becomes whether a safe subset can be defined without introducing so much additional machinery that it conflicts with the requirements for simplicity, portability, compatibility and the character of C.
That is the real constraint.
An unsafe subset therefore cannot simply be used to declare the problem solved.
If every difficult operation can be placed in an unsafe region, then the language may retain its compatibility but provide little meaningful memory-safety guarantee.
The safe part must cover enough ordinary C programming that the guarantee has practical value.
At the same time, the boundary must be meaningful.
Unsafe code must not be able to produce a value that the safe language blindly accepts as satisfying safety properties that have not actually been established.
Otherwise the safe subset becomes merely a syntactic convention.
The language would need a defined semantic boundary between code for which it provides memory-safety guarantees and code for which it does not.
That boundary itself would become part of C.
The charter therefore produces an unusual situation.
It does not say that memory safety is outside the scope of C.
Quite the opposite.
Security, safety, memory safety and analyzability are explicitly within the areas the committee is expected to consider.
But the same charter places equally important constraints on how that objective is pursued.
C is expected to remain simple.
It is expected to preserve programming freedom.
It is expected to remain portable.
It is expected to avoid invalidating existing programs.
It is expected to make migration practical.
It is expected to preserve its character.
And it is expected to avoid adding specialised machinery where the same problem can reasonably be addressed outside the language.
The consequence is important.
The fact that a language feature could make C safer is not sufficient reason for that feature to belong in C.
The feature must also be consistent with what the C committee is trying to make C.
This eliminates many of the easy answers to the question.
If "C" means a language with no unsafe escape hatch, the answer appears difficult to reconcile with the charter.
Such a change would impose substantial restrictions on existing C programming practices. It would introduce new semantic machinery and would necessarily reject programs that are currently valid C or require them to be rewritten.
That would conflict with several of the committee's established principles.
If "C" instead means a language with a memory-safe subset and an explicit unsafe subset, the situation is more plausible.
It would preserve programming freedom.
It would provide a mechanism for writing code under stronger guarantees.
It would allow existing low-level techniques to remain expressible.
And the charter explicitly identifies subsetting the Standard as an area for consideration.
But even this approach has a cost.
A genuinely useful safe subset would require enough additional language semantics to establish object bounds, lifetime and related properties. Those additions would have to be evaluated against the charter's requirements for simplicity, portability, compatibility and conceptual economy.
The question therefore cannot be answered merely by pointing to a feature from another language and saying that C could adopt it.
The feature would have to fit C.
This may be the most important part of the question.
The C committee's charter does not define C merely as a collection of syntax.
It refers explicitly to the "character" of the language and to its existing semantics, idioms and habits.
C has historically provided a relatively direct relationship between the programmer and the machine.
It gives programmers considerable control over representation and memory.
It permits machine-dependent programming.
It is designed to be widely implementable.
It aims for an economy of concepts rather than a large collection of specialised abstractions.
Those characteristics are not incidental.
They are part of what the committee is charged with preserving.
A memory-safety system that required a pervasive ownership calculus, lifetime annotations, specialised pointer types and extensive static reasoning might provide strong guarantees.
But if the resulting language required programmers to understand a large new semantic system merely to perform ordinary C programming, the question would no longer be whether the system works.
It would be whether the system belongs in C.
That is a much harder question.
The charter does leave room for evolution.
It explicitly allows new ideas where they address genuine deficiencies, recognises security and safety as legitimate concerns, permits subsetting, and accepts that the principles are not absolute.
This means the conclusion cannot simply be that the charter makes memory safety impossible.
It does not.
Nor does the charter require the committee to preserve every historical property of C indefinitely.
What it does require is justification for significant departures from the established character of the language.
That makes the burden of proof clear.
A proposal for memory safety in C would need to demonstrate not merely that it prevents memory errors, but that it does so while preserving enough of C's simplicity, portability, programming freedom, compatibility and existing programming model to remain recognisably C.
That is a much more demanding standard.
There are really two different questions.
Can a language descended from C provide comprehensive memory safety?
Yes. There is no fundamental theoretical obstacle.
Can the C Standard itself acquire comprehensive memory safety while remaining within the principles that govern its evolution?
That is much less certain.
The difficulty is not the invention of safe language mechanisms.
The difficulty is satisfying the competing requirements simultaneously.
The stronger the guarantee, the more information the language must represent and enforce.
The more information it represents and enforces, the more constraints it places on programs.
The more constraints it places on programs, the greater the compatibility and migration cost.
The more machinery it introduces to manage those constraints, the greater the pressure against C's requirements for simplicity and conceptual economy.
And the more the resulting programming model differs from traditional C, the stronger the argument becomes that the result is a new language rather than an evolution of C.
This is not a proof that memory-safe C is impossible.
It is a demonstration that the problem cannot be solved independently of the charter.
The most interesting conclusion is therefore not that C cannot be made safe.
Nor is it that C simply needs to adopt the mechanisms used by another language.
The real question is whether the C committee's existing principles leave enough room to make memory safety a language-level property.
The charter says that C should enable secure and functionally safe programming.
It also says that C should remain small, simple, portable, interoperable and capable of low-level programming, while preserving programming freedom and avoiding unnecessary disruption to existing code.
Those objectives are compatible up to a point.
The question is where that point lies.
If the memory-safety guarantees can be achieved with a sufficiently small addition to the language, while preserving the existing programming model, then the charter provides a basis for pursuing them.
If achieving those guarantees requires a fundamentally different type, pointer, ownership and lifetime model, substantial incompatibility with existing programs, or a language substantially more complex than C, then the charter itself becomes an argument against putting the complete solution into C.
In that case, the appropriate conclusion would not be that memory safety is unimportant.
It would be that C has reached the boundary of what its own design principles permit it to become.
This may be the most useful way to frame the issue.
The question is not:
"Can we invent a memory-safe language that looks like C?"
Of course we can.
Nor is it:
"Can we add enough checks around C to make C programs safer?"
We already know that we can.
The question is:
"Can the C programming language itself acquire meaningful memory-safety guarantees without ceasing to satisfy the principles under which the C Standard is maintained?"
That question has a technical component, but it is not purely technical.
It requires us to consider what information the language must represent, what guarantees it can make, what restrictions those guarantees require, and whether those restrictions are compatible with C's established character.
The C committee has explicitly accepted memory safety and secure programming as legitimate objectives.
It has not, however, abandoned the other objectives that define C.
That leaves an intriguing possibility.
Perhaps C can become substantially safer while remaining recognisably C.
Perhaps an explicit distinction between guaranteed-safe and programmer-responsible operations provides enough room to achieve that.
Or perhaps the guarantees required to solve the problems examined in this book demand changes that are too fundamental for the C Standard to absorb.
The important point is that this can be argued on technical and charter grounds.
And that is where the question becomes genuinely interesting.
If the answer is ultimately "no", it would not mean that the problems are unsolvable.
It would mean that they are outside the design space of C.
And if the answer is "yes", then the burden is equally clear: the solution must demonstrate not merely that C can be made safer, but that C can become safer while remaining C.