Question
C Function Declarations: Empty Parentheses, Prototypes, and Old-Style Parameters
Question
Why does the following C program compile even though the declaration appears to have no parameters, while the function definition has a parameter with no explicit type?
#include <stdio.h>
int func();
int func(param)
{
return param;
}
int main(void)
{
int bla = func(10);
printf("%d", bla);
}
Why is func(10) allowed, and what type does param have?
Short Answer
You will learn the important C distinction between an empty parameter list (()) and an explicit zero-parameter prototype ((void)). You will also see why this program uses historical, pre-standard C syntax, why some compilers still accept it, and how to write the modern equivalent safely.
Concept
In C, these two declarations do not mean the same thing:
int func(); // Parameter types are not specified.
int func(void); // The function accepts exactly zero arguments.
int func(); is a declaration of a function returning int, but it is not a function prototype. It gives the compiler no information about the number or types of parameters. Therefore, a call such as func(10) is permitted at that point.
The definition in the question uses old-style (K&R) function-definition syntax:
int func(param)
{
return param;
}
Historically, C allowed a missing type to default to int. In that old syntax, param is therefore treated as an int parameter. The program effectively behaves like this modern code:
int func( param)
{
param;
}
Mental Model
Think of a function declaration as a label on a package-delivery counter.
int func();says: “There is a counter namedfuncthat gives back anint, but I do not know what packages it accepts.” The compiler lets you hand it arguments because it has no argument rules to enforce.int func(void);says: “This counter accepts no packages.” Passing even one argument is an error.int func(int param);says: “This counter accepts exactly oneintpackage.” The compiler can inspect what you provide.
Old-style C leaves parts of the label blank and relies on historical defaults. Modern prototypes write the full label so the compiler can help you.
Syntax and Examples
A modern function declaration should include the type of every parameter.
int func(int param);
int func(int param)
{
return param;
}
int main(void)
{
int result = func(10);
return result;
}
Here, the declaration and definition agree:
funcreturns anint.functakes oneintnamedparam.func(10)is checked by the compiler.
To declare a function that really accepts no arguments, use void:
void show_message(void)
{
puts("Hello");
}
int
{
show_message();
}
Step by Step Execution
Consider the original program under the historical rules that its syntax uses:
int func();
int func(param)
{
return param;
}
int main(void)
{
int bla = func(10);
}
int func();tells the compiler thatfuncreturnsint.- Because the parentheses are empty, the declaration does not specify parameter types or a parameter count.
- At
func(10), the compiler has no prototype information that would reject or validate10as an argument. - The old-style definition lists
paramby name:int func(param). - No explicit declaration gives
parama type. Historical C rules treat that omitted type asint. 10is passed as anint,paramreceives , and returns .
Real World Use Cases
Modern function prototypes are used everywhere C code crosses boundaries:
- Standard-library calls: Headers declare functions such as
printfso the compiler knows their return types and parameters. - Module interfaces: A
.hfile publishes functions implemented in a.cfile. - Embedded systems: Driver APIs declare exact parameter widths, such as
uint8_toruint32_t. - System APIs: Operating-system and networking functions depend on correctly typed pointers, lengths, and handles.
- Callbacks: Function-pointer types specify exactly what arguments a callback receives.
In all of these cases, precise prototypes let the compiler detect incorrect calls before the program runs.
Real Codebase Usage
In a real C project, put each public function's prototype in a header and include that header in both the implementation and callers.
/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int left, int right);
#endif
/* math_utils.c */
#include "math_utils.h"
int add(int left, int right)
{
return left + right;
}
/* main.c */
#include "math_utils.h"
int main(void)
{
return add(4, 6);
}
This pattern prevents declaration/definition drift. If someone changes the implementation to int add(int left) but forgets to update the header, the compiler can report the mismatch.
For a function with no inputs, codebases use consistently:
Common Mistakes
Mistake: assuming () means no parameters
int log_status(); // Not a zero-parameter prototype in C.
Use this instead:
int log_status(void);
Mistake: omitting parameter types
int square(value)
{
return value * value;
}
This is obsolete old-style syntax. Write:
int square(int value)
{
return value * value;
}
Mistake: declaring a function without including its real header
int process(); // A guess about a function defined elsewhere.
A guessed declaration can hide argument mismatches. Include the header that owns the API instead.
Comparisons
| Form | Meaning in C | Is argument checking available at calls? | Recommended? |
|---|---|---|---|
int func(); | Returns int; parameter list is unspecified | No | No, except for legacy compatibility |
int func(void); | Returns int; accepts zero arguments | Yes | Yes, for zero parameters |
int func(int value); | Returns int; accepts one int | Yes | Yes |
int func(param) { ... } | Old-style definition; parameter types are declared separately or historically defaulted |
Cheat Sheet
int f();means:freturnsint; its parameter list is unspecified.int f(void);means:freturnsintand takes zero arguments.int f(int x);is a prototype: it specifies oneintparameter.- A prototype enables compile-time argument checking.
int f(x) { ... }is old-style K&R function-definition syntax.- A missing parameter type historically defaulted to
int; do not rely on that rule. - Write
int main(void), notint main(), whenmaintakes no parameters. - Place shared function declarations in header files and include those headers.
- Compile with a modern standard mode and warnings, for example:
cc -std=c17 -Wall -Wextra -Wpedantic program.c
Exact diagnostics vary by compiler and selected language standard.
FAQ
Why does int func() accept arguments in C?
Because it does not specify a parameter list. It declares only that func returns int, so the compiler cannot use it to check the arguments of a call.
How do I declare a C function with no parameters?
Use void inside the parentheses:
int func(void);
What type is param in the old definition?
Under historical C rules, its missing type defaults to int. Modern code must write the type explicitly.
Is int func(param) valid modern C?
It is obsolete old-style syntax. Support depends on the compiler and language mode. Do not use it in new code.
Why do compilers still compile this code?
Many compilers support old C constructs for compatibility with existing codebases. They may emit warnings, especially when strict warning or standard options are enabled.
Is int main() correct in C?
It compiles, but int main(void) is clearer because it explicitly says that takes no arguments.
Mini Project
Description
Create a small temperature-conversion module using modern C function prototypes. The project demonstrates how a header communicates exact function parameter types to every source file that calls or implements the functions.
Goal
Build a program that converts Celsius to Fahrenheit and prevents incorrect function calls through explicit prototypes.
Requirements
Create a header file that declares the conversion function with a double parameter and return type.
Implement the function in a separate C source file.
Write main to convert a sample Celsius value and print the result.
Use (void) for any function that takes no parameters.
Do not use empty parameter lists to mean “no parameters.”
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.