C23 Changes to K&R Unprototyped Functions
The C23 revision of the ISO C standard officially eliminates traditional Kernighan and Ritchie (K&R) style function declarations and definitions, ending decades of backward compatibility with pre-standardized C. This article covers the specific syntactic and semantic shifts made in C23, detailing the complete removal of old-style identifier-list function definitions, the reinterpretation of empty parameter lists to mean zero arguments, and the resulting improvements to type safety and C++ compatibility.
Removal of K&R-Style Function Definitions
Prior to C23, the C language retained support for original K&R function definitions, where parameter names were listed inside the parentheses and their types were declared immediately before the opening brace:
/* Valid in C89–C17, removed in C23 */
int add(a, b)
int a;
int b;
{
return a + b;
}C23 completely removes support for this syntax. The grammar rules allowing an identifier list followed by declaration specifiers have been excised from the ISO standard. Compiling a K&R-style definition in a conforming C23 compiler results in a syntax error. Functions must now be written using modern prototype syntax where parameter types and names are defined directly inside the parameter list.
Empty Parameter Lists Now Mean "Void"
The most impactful change to everyday code involves declarations with empty parentheses. From C89 through C17, an empty parameter list in a function declaration specified that the function took an unspecified number and type of arguments:
/* In C17 and earlier: takes an unspecified number of arguments */
/* In C23: takes zero arguments */
void log_message(); Because an unprototyped declaration disabled argument type-checking,
callers could pass any number of arguments without triggering a
compile-time constraint violation. To explicitly declare a function
taking no arguments, developers were required to use the
void keyword:
void log_message(void);In C23, an empty parameter list () is syntactically and
semantically identical to (void). The declaration
void log_message(); now explicitly signifies that the
function accepts zero arguments. While (void) remains valid
for backward compatibility, it is no longer required to guarantee
zero-argument enforcement.
Enhanced Compile-Time Type Safety
Because unprototyped functions bypassed compiler argument checking, mismatched types between call sites and function implementations frequently caused undefined behavior, stack corruption, and runtime crashes. Under C23, all function declarations act as prototypes.
If arguments are passed to a function declared with empty parentheses:
void reset_state();
int main(void) {
reset_state(1, 2); /* Constraint violation in C23 */
}A C23 compiler must emit a diagnostic error because the function does not accept arguments.
Alignment with C++
In C++, an empty parameter list has always denoted a function taking
no arguments (() == (void)). The divergence between the two
languages often forced library maintainers to write defensive header
macros or rely heavily on explicit (void) declarations when
sharing interfaces across C and C++. C23 closes this historical gap,
standardizing function declaration behavior across both languages and
simplifying dual-language headers.