I note that section 3.4 of
https://www.gnu.org/prep/standards/standards.html#Standard-C recommends
that we continue to write K&R style function headers.

I am at a loss to understand how this section has remained intact for so
long after it should have been removed. It's years - nay decades - past
time when we should have been *removing* extant K&R function headers, not*
creating* them.
The rationale given is this:

> *You need such a declaration anyway, in a header file, to get the benefit
> of prototypes in all the files where the function is called. And once you
> have the declaration, you normally lose nothing by writing the function
> definition in the pre-standard style. *
>
The assertion of "*normally lose nothing*" is highly suspect:

   - ISO-9899:2024 has dropped support for K&R function headers.
   - Most compilers already complain about them, and some outright reject
   them.
   - Separate prototypes are *not* needed for "static" functions unless
   they're part of a mutual recursion ring, as long as they're in bottom-up
   order.
   - To younger programmers, K&R-style declarations look "weird" and take
   extra effort and thought, both to write and to read. I'm in a very small
   minority - I'm old enough to have written C code before 1989 but not quite
   old enough to retire - and even *I* think they're weird. The only K&R
   code I've touched *this century* has been in GNU projects.
   - Realistically if the author of some new package written today isn't
   planning to support some niche legacy platform, it's unlikely that anyone
   else is going to put in all the *other* extra work to port it,
   regardless of K&R definitions.
   - GNU flagship projects such as "Bash" have already actively removed K&R
   function headers.

Please can this section be excised or heavily revised? At minimum, could
there be guidance added for projects that are intended for use in "general
purpose computing platforms" that they should target a (much) newer version
of the C standard such as C11, and that they should do so consistently.
(Don't leave in a mix of new-style code and old-style work-arounds; settle
on one or the other, and clean up any associated technical debt from time
to time.)

If you need more reasons, consider:

   - This approach means writing the name of each parameter multiple times;
   once in the argument list, once to declare its type, and once once in the
   prototype. Yes the names are *technically* not required in the
   prototype, but including them make the prototypes (more) self-documenting.
   - This offers no guidance for static functions within a translation
   unit, but the implication is that they should follow the same duplication,
   with a prototype separate from the function header even when the latter
   appears before all possible callers.
   - Even without considering parameter names, misspelling a function name
   in its prototype may result in some callers seeing the unprototyped
   function, potentially with erroneous behaviour.

Personally, I would go further, and declare at least C99 (or even C11) as
the minimum target. Yes there are still some older platforms out there, but
are they general purpose computing environments rather than specialized
environments where code could be cross-compiled on a modern more capable
platform?
There's significant loss of expressive power when targeting anything before
C99, which results in worse maintainability:

   - Discouraging a newer standard results in most programmers abandoning
   *all* its newer facilities, and making do with their own work-alikes,
   since for compatibility, they can't simply define the missing symbols, in
   case they're *not* actually missing. This leads to multitudinous
   disparate names for things like fixed-width integer types and string
   functions. We should instead be actively promoting the use of standard
   names, and if by chance you're on a platform that lacks them, you can
   simply define them for that platform.
   - "Putting all the declarations at the top" was popular pedagogy in
   1979, but by 1989 it was *already* outdated. By 1999 it was a known
   anti-pattern, since it actively interferes with other recommendations (like
   using "const"), and so C99 dropped the requirement that declarations must
   precede statements.
   - Designated initializers are *much* easier to read, since you can see
   at a glance which values go into which fields; it also helps that we don't
   need to insert all the intermediate zeroes. Compound literals improve
   readability for similar reasons.

Lastly, the importance of writing software so that it can run on
non-standard platforms is overstated. The vast majority of devices run
Linux (including Android), Windows, or Darwin, all of which have multiple
C23 compilers available, as do other minor platforms such as FreeBSD or QNX.

-Martin

Reply via email to