Placement Prep

Function Prototype in C: K&R Style, ANSI Syntax, Header Files

Learn C prototype syntax: the ANSI form, the K&R legacy style, the void vs empty-parentheses trap, and how to structure header files correctly.

By FACE Prep Team 7 min read
c-programming placement-prep coding-interview function-prototype c-language

Two function declaration forms co-exist in C code today: the ANSI prototype introduced in C89, and the older K&R style inherited from 1978 that most compilers still accept silently.

Knowing the difference matters more than most students expect. Placement MCQs at TCS NQT and AMCAT regularly test output-tracing questions that turn on whether a declaration is an ANSI prototype or a K&R-style declaration. The programs look similar; the compiler behaves differently.

Prototype vs Definition: Syntax Rules

An ANSI function prototype has four required parts:

  • Return typeint, float, void, or any other valid type
  • Function name — the identifier the caller uses
  • Parameter list — the types (and optionally names) of each argument, inside parentheses
  • Semicolon — the terminator that makes this a declaration, not a definition

The semicolon is the critical signal. Compare:

int add(int a, int b);    /* prototype — declaration only */

int add(int a, int b) {   /* definition — implementation */
    return a + b;
}

The prototype ends at the semicolon. The definition replaces the semicolon with a brace-enclosed body. A .c file can have multiple declarations of the same function (every #include of its header counts as one), but exactly one definition.

Parameter names in prototypes are optional. The compiler checks only the types, not the names. All three of these are equivalent and valid ANSI prototypes:

int add(int a, int b);   /* names included — common for readability */
int add(int x, int y);   /* different names — still valid */
int add(int, int);       /* names omitted — type-only form */

The type-only form is compact and often appears in header files for widely-used library functions. Either form satisfies the compiler’s need to check call sites.

One more rule: if the function returns nothing, the return type is void, not omitted. Writing multiply(int, int); without a return type relies on the implicit-int rule, which C99 abolished. Any modern C compiler will reject it.

ANSI Syntax vs K&R-Style Declarations

Before the ANSI C standard arrived in 1989, C used a different declaration form now called K&R style (after Kernighan and Ritchie, the language’s authors). Understanding it explains a class of legacy code still found in production systems and in older campus practicals.

A K&R function declaration looks like this:

int add();   /* K&R style — no parameter information at all */

The parentheses are empty. The compiler sees only the function name and return type; it has no information about how many arguments add expects or what types they should be. This is not an error under C90: it is a valid declaration that simply promises nothing about parameters.

A K&R function definition is even more unusual for readers accustomed to ANSI syntax:

int add(a, b)   /* K&R definition — parameter names in signature */
int a;          /* parameter types declared separately */
int b;
{
    return a + b;
}

The parameter names appear in the signature line, but their types follow on separate lines before the opening brace. This form was officially removed from the C standard in C11, but GCC with -std=c89 or -std=c90 still compiles it. The C language specification on cppreference describes both forms and marks K&R-style definitions as deprecated from C99 onward.

The danger with K&R declarations: because they carry no parameter type information, the compiler cannot catch type mismatches. Passing a double where the function expects an int produces no warning under C90 with a K&R declaration visible; it only becomes a visible error when an ANSI prototype is in scope.

In practice, if you encounter empty-parentheses declarations in older codebases, treat them as a signal that type checking is incomplete for those functions. Adding ANSI prototypes is the fix.

The f() vs f(void) Trap in C

This distinction catches students frequently because C++ treats the two forms as identical, and many C tutorials are implicitly written in a C++-influenced style.

In standard C:

  • int f() declares a function that returns int and takes unspecified parameters
  • int f(void) declares a function that returns int and takes no parameters

“Unspecified” is not the same as “none.” A declaration of int f() does not prevent a caller from writing f(1, 2, 3). The compiler, having no parameter information, cannot object. The result is undefined behaviour at runtime.

void greet(void) {        /* no parameters — explicit */
    printf("hello\n");
}

void greet2() {           /* unspecified parameters — K&R compatible */
    printf("hello\n");
}

int main() {
    greet(42);    /* compile error: too many arguments */
    greet2(42);   /* compiles quietly — undefined behaviour */
    return 0;
}

The greet(42) call fails to compile because the prototype void greet(void) tells the compiler exactly what to expect. The greet2(42) call compiles without complaint, because void greet2() tells the compiler nothing about parameters.

This distinction is a regular topic in placement C MCQs. The question format is usually: “Which call will produce a compile-time error?” followed by four options mixing void f(void) and void f() declarations with different call patterns. The key is to read the declaration form, not just the function name.

In C++, both f() and f(void) are equivalent and mean “no parameters.” If you are writing code that needs to compile as both C and C++, use f(void) to get consistent behaviour across both languages.

Placing Prototypes in Header Files

Single-file programs put prototypes at the top of the .c file, above main(). That is the minimum correct approach. Multi-file programs require a more deliberate layout.

The standard pattern for a multi-file C project:

/* mathops.h — prototypes and type declarations only */
#ifndef MATHOPS_H
#define MATHOPS_H

int add(int a, int b);
float average(const int arr[], int n);

#endif /* MATHOPS_H */
/* mathops.c — definitions */
#include "mathops.h"

int add(int a, int b) {
    return a + b;
}

float average(const int arr[], int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) sum += arr[i];
    return (float)sum / n;
}
/* main.c */
#include <stdio.h>
#include "mathops.h"

int main() {
    printf("%d\n", add(3, 7));
    return 0;
}

Three rules govern what belongs in the header:

  • Prototypes for any function that other .c files need to call
  • Type definitions (typedef, struct declarations, enum values) that multiple files share
  • Macro definitions (#define) used across translation units

What stays out of the header:

  • Function definitions — putting a definition in a header and including it in two .c files causes a “multiple definition” linker error
  • Non-extern variable definitions — same linker issue

The include guard (#ifndef MATHOPS_H / #define MATHOPS_H / #endif) prevents the header’s contents from being processed twice if two source files both include it and one of those files includes the other. Without the guard, the int add(int, int); prototype appears twice in the same translation unit, which causes a “function declared twice” error in strict compilation modes.

Static Functions and Internal Prototypes

Not every function needs to be in a header. Helper functions that only one .c file uses should be declared static. The static keyword restricts the function’s linkage to its own translation unit.

/* helpers.c — internal only, not exported */
static int clamp(int value, int low, int high);

static int clamp(int value, int low, int high) {
    if (value < low) return low;
    if (value > high) return high;
    return value;
}

The static prototype goes at the top of helpers.c, not in any header. Exporting it via a header would be incorrect: other .c files cannot call a static function even if they see the prototype, because the linker does not expose the symbol externally.

This pattern keeps the public interface narrow. Only the functions that other files genuinely need appear in the header. The rest are implementation details.

What GCC Reports When a Prototype Is Missing

The compiler error most engineering students encounter first in C is this one:

warning: implicit declaration of function 'sqrt' [-Wimplicit-function-declaration]

or, in stricter modes:

error: implicit declaration of function 'compute' is invalid in C99 [-Wimplicit-function-declaration]

Both messages mean the same thing: the compiler reached a function call before seeing any prototype or definition for that function. The fix is always one of two things: add a prototype above the call site, or add the correct #include for a library function.

The GCC warning documentation describes -Wimplicit-function-declaration as enabled by default at -Wall. Under C99 and later, it can be promoted to a hard error with -Werror=implicit-function-declaration, which is common in professional C projects to prevent the C90 implicit-int assumption from silently compiling wrong code.

For competitive and placement coding, these flags matter because online judges often compile with -std=c11 -Wall -Wextra. A missing prototype that compiles silently on an old local setup may fail on the judge’s machine. Writing the prototype explicitly eliminates that risk.

Understanding the declaration-before-use discipline also helps with the kind of multi-function programs that appear in placement coding rounds. The Pascal’s triangle in C walkthrough is a good example: it requires several helper functions, and the order in which they appear in the file determines whether prototypes are necessary. Working through that example with deliberate prototype placement builds the habit. The C output-tracing practice questions then test the same concept in MCQ form, which is the format that appears in actual placement screens.

The same interface-before-implementation discipline that .h/.c separation enforces shows up in every layer of software engineering. An AI API’s schema is its header file: it tells you the function name, the parameter types, and the return shape before you write a single line of calling code. Getting that contract right before writing implementation code is the same skill, applied to a different layer. TinkerLLM at ₹299 is the entry point for engineering students who want to apply that discipline to real LLM-based projects, building the kind of portfolio additions that supplement a strong C fundamentals profile during placement season.

Primary sources

Frequently asked questions

What is the difference between a K&R-style and an ANSI function declaration?

K&R style writes just `int f();` with no parameter info; an ANSI prototype writes `int f(int a, int b);` with full types. The compiler can only type-check call sites that have ANSI prototypes visible.

In C, does `int f()` mean the function takes no arguments?

No. In C, `int f()` means the function returns int and its parameter types are unspecified. Only `int f(void)` explicitly declares no parameters. In C++, the two forms are equivalent, but standard C treats them differently.

Do parameter names need to appear in a function prototype?

No. Only the types are required. `int add(int, int);` is a valid ANSI prototype; the names a and b are optional and can differ from the names used in the function definition.

What is an include guard and why does every header file need one?

An include guard is a #ifndef MYFILE_H / #define MYFILE_H / #endif triplet that prevents the header's contents from being processed twice if included from multiple source files. Without it, a struct or typedef declared in the header causes a redefinition error.

What does GCC's implicit declaration of function error mean?

It means the compiler reached a function call before seeing any prototype or definition for that function. The fix is to add a prototype above the call site or include the header file that contains the prototype.

Should a static helper function have its prototype in the header file?

No. A static function has internal linkage, meaning it is visible only within its own .c file. Its prototype belongs at the top of that .c file, not in any header that other translation units include.

Build AI projects

A self-paced playground for building with LLMs.

TinkerLLM is FACE Prep's sister property. A guided environment for shipping real LLM applications, the kind of project that earns a paragraph on your resume, not a line.

Try TinkerLLM (₹299 launch)
Free AI Roadmap PDF