Question

C Function Declarations: Empty Parentheses, Prototypes, and Old-Style Parameters

cparametersvoidfunction-prototypesfunction-parameter

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(int param)
{
    return param;
}

This style is obsolete. Modern C requires parameter types to be written explicitly, and modern compilers may accept the old code only as a compatibility extension or emit warnings/errors depending on their language mode.

This distinction matters because a non-prototype declaration prevents the compiler from checking function arguments at the call site. Missing checks can allow bugs to compile.

Mental Model

Think of a function declaration as a label on a package-delivery counter.

  • int func(); says: “There is a counter named func that gives back an int, 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 one int package.” 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.

Take Quiz

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:

  • func returns an int.
  • func takes one int named param.
  • 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 main(void)
{
    show_message();
}

Do not use void show_message(); when you mean “no arguments.” In C, that form means the parameter list is unspecified.

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);
}
  1. int func(); tells the compiler that func returns int.
  2. Because the parentheses are empty, the declaration does not specify parameter types or a parameter count.
  3. At func(10), the compiler has no prototype information that would reject or validate 10 as an argument.
  4. The old-style definition lists param by name: int func(param).
  5. No explicit declaration gives param a type. Historical C rules treat that omitted type as int.
  6. 10 is passed as an int, param receives 10, and return param; returns 10.
  7. bla receives 10.

The equivalent modern, fully checked version is:

int func(int param)
{
    return param;
}

int main(void)
{
    int bla = func(10);
    return bla;
}

Real World Use Cases

Modern function prototypes are used everywhere C code crosses boundaries:

  • Standard-library calls: Headers declare functions such as printf so the compiler knows their return types and parameters.
  • Module interfaces: A .h file publishes functions implemented in a .c file.
  • Embedded systems: Driver APIs declare exact parameter widths, such as uint8_t or uint32_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.

Take Quiz

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 (void) consistently:

int initialize(void);

This prevents accidental calls such as initialize(1).

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.

#include "process.h"

Mistake: believing successful compilation proves the code is portable

Compilers often keep extensions for legacy projects. A compiler may accept old syntax with a warning, while another compiler or stricter build configuration rejects it. Enable warnings and treat warnings seriously.

Mistake: confusing declarations and definitions

A declaration describes a function to other code. A definition supplies its body. In modern C, their function types should match exactly.

Comparisons

FormMeaning in CIs argument checking available at calls?Recommended?
int func();Returns int; parameter list is unspecifiedNoNo, except for legacy compatibility
int func(void);Returns int; accepts zero argumentsYesYes, for zero parameters
int func(int value);Returns int; accepts one intYesYes
int func(param) { ... }Old-style definition; parameter types are declared separately or historically defaultedLimited/legacy behaviorNo
int func(int param) { ... }Modern function definitionYesYes

C differs from C++ here. In C++, int func() means a function with no parameters. In C, use int func(void) to express that meaning.

Take Quiz

Cheat Sheet

  • int f(); means: f returns int; its parameter list is unspecified.
  • int f(void); means: f returns int and takes zero arguments.
  • int f(int x); is a prototype: it specifies one int parameter.
  • 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), not int main(), when main takes 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 main takes no arguments.

Can int func() cause real bugs?

Yes. Without a prototype, the compiler may fail to catch an incorrect number or type of arguments, particularly across source files.

Is this behavior the same in C++?

No. In C++, int func() means that func has no parameters. This page describes C behavior.

Take Quiz

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.”

Take Quiz

Keep learning

Related questions

View more