HN Debrief

an ambiguity in C89 which will never be fixed

  • Programming
  • Developer Tools
  • Open Source
  • Standards

The post walks through an ambiguity in C89 created by the interaction of old K&R-era function declaration rules, implicit int, and the now-removed rule that undeclared functions are assumed to return int with unspecified parameters. The author’s point is narrow but interesting: because later standards deleted the behavior instead of clarifying the original wording, there will never be an official answer to what C89 meant in this edge case.

If you maintain old C, assume pre-C99 rules can still bite in surprising ways when ancient compilers or build flags are involved. Audit for missing prototypes, crank warnings up aggressively, and do not rely on C++ mode as a lint pass for C code.

Discussion mood

Mostly amused and dismissive about the specific C89 puzzle, with a strong undercurrent of respect for how much pain these legacy rules still cause in real maintenance work. The mood was that this is old debris, not an active language problem, but dangerous debris if you are stuck on old codebases or old compilers.

Key insights

  1. 01

    Legacy toolchains keep dead rules alive

    Ancient build environments still make implicit declarations a live operational problem, not a museum piece. One maintainer rebuilding code that talks to UNIVAC OS2200 CITA and VAX ACMS had to recover missing prototypes by hand because the production binary was tied to Oracle Linux 5 and GCC 4.1.2, where today’s default diagnostics were not the norm. That grounds the whole post in a reality startup engineers forget. Standards may move on, but the shipped system often does not.

    When inheriting old C, check the exact compiler version and warning flags before assuming a dangerous pattern would have been caught. Budget time for prototype recovery and modernization before you touch behavior.

      Attribution:
    • chasil #1 #2
    • ronsor #1
    • uecker #1 #2
  2. 02

    Prototype extraction was solved decades ago

    There were dedicated tools for this problem even in 1989. cproto and mkproto were built to mine function prototypes out of old C sources, which says a lot about how common and painful the transition from pre-prototype C already was when C89 landed. This was not an obscure academic flaw. It was a known migration tax with tooling around it from the start.

    If you are cleaning up legacy C, use purpose-built prototype generators before doing manual archaeology. They can turn a brittle porting job into a mechanical cleanup pass.

      Attribution:
    • geocar #1
    • 1vuio0pswjnm7 #1
  3. 03

    C++ mode can introduce new bugs

    Using a C++ compiler as a stricter checker for C sounds clever until it changes program semantics. One concrete example was compound literals. GCC and Clang may accept the syntax in C++ mode, but give the object expression lifetime instead of C’s block lifetime, which can create silent use-after-free bugs. That is much worse than a helpful rejection. The safer path is to compile as C and enable more warnings.

    Do not add a C++ compile pass to your C CI pipeline and assume it improves safety. Keep validation in C mode, then add sanitizers and aggressive warning sets that preserve C semantics.

      Attribution:
    • flohofwoe #1
    • uecker #1
    • wahern #1
  4. 04

    C and C++ split earlier than many assume

    The divergence is not a late C99-era story. It starts in C89, because C++ intentionally stayed close to C only where that did not weaken its stronger type system. That makes advice like “just try g++ first” misleading even for old code that looks portable on the surface. Shared syntax does not mean shared language rules.

    Treat C and C++ as separate targets from the beginning of a port or library design. If you need both, test both explicitly instead of assuming one is a useful proxy for the other.

      Attribution:
    • pjmlp #1
  5. 05

    Committee veterans accepted flaws to get a standard

    A former X3J11 participant said they had a 30-page list of C89 errors and ambiguities, but still voted yes because getting a standard was more valuable than holding out for a cleaner document. That puts the blog post in context. Some rough edges were not hidden mistakes waiting to be discovered later. They were tolerated because the cost of no standard was worse.

    Do not assume venerable standards are internally pristine. When building on old specs, expect compromises made for adoption pressure and read ambiguous corners defensively.

      Attribution:
    • benj111 #1
    • jibal #1

Against the grain

  1. 01

    The issue is already fixed in practice

    Calling this something that “will never be fixed” overstates the present-day problem. C99 removed implicit function declarations, and modern GCC and Clang reject them in current language modes, so there is no open standards problem for anyone writing contemporary C. What remains is historical curiosity and compatibility baggage.

    Separate spec archaeology from current engineering risk. If your code is built in modern C modes, spend more attention on current undefined behavior than on dead C89 corners.

      Attribution:
    • flohofwoe #1
  2. 02

    Every language has its own footguns

    C is easy to single out because its traps are old and famous, but weird edge cases are not unique to C or C++. JavaScript, Python, Go, and SQL all have their own collections of surprising behavior. The useful distinction is not whether a language has pitfalls. It is whether your team uses the available tools and discipline to catch them.

    Avoid turning language choice into a moral argument. Pick the language you need, then invest in linters, sanitizers, and style rules that match its failure modes.

      Attribution:
    • maccard #1

In plain english

C89
The 1989 ANSI standard for the C programming language, often used as a minimal common compiler target.
C99
The 1999 revision of the C standard, which removed some old C89 behavior and added newer language features.
clang
A compiler front end from the LLVM project, commonly used for C, C++, and Objective-C.
g++
The GNU C++ compiler driver, used to compile and link C++ programs.
GCC
GNU Compiler Collection, a major open-source compiler suite for C, C++, and other languages.
implicit int
An old C rule where a type could default to int if it was omitted, which later standards removed.
Oracle Linux 5
An old release of Oracle’s Linux distribution, notable here because it shipped an older GCC toolchain.
prototype
A function declaration that specifies the function’s return type and parameter types so the compiler can check calls.
UNIVAC OS2200 CITA
A legacy enterprise computing environment involving the OS 2200 operating system and CITA transaction-processing software.
use-after-free
A memory bug where code keeps using a pointer after the memory it points to has already been released.
VAX ACMS
Application Control and Management System, a transaction-processing environment used on DEC VAX systems.
X3J11
The ANSI committee that standardized the C programming language in the 1980s.

Reference links

Legacy C tooling and archives

C and C++ compatibility references

Compiler behavior and diagnostics

Historical standards records

Safety coding guidance