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