It's not uncommon for me to check out the C source code of some program that I
use and find lots of functions declared like this f()
rather than f(void)
.
Three main reasons that I can think of why people do this are:
f()
is a function that takes no arguments!
Why would I want to type out void
?f()
and f(void)
are equivalent.So... what's wrong with using f()
in C? You may have already guessed it from
point 1 and 3, but f()
and f(void)
in C are not equivalent.
f()
declares a function that takes an unspecified amount of arguments.f(void)
declares a function that takes no arguments.What this means in practice is that when you declare your function like
f(void)
, the compiler can and will type check it to make sure you're not
accidentally calling it like f("string");
or f(5);
. And if you are, the
error will be caught at compile time.
But since f()
says the function takes unspecified amount of arguments the
compiler cannot check weather the arguments you provide it are valid or not. And
worse, if you do end up making a mistake when calling such a function, it will
trigger undefined behavior.
static void f();
static void f2(void);
int main(void)
{
f("string"); /* this will compile fine */
f(69.420); /* so will this */
f2(5); /* compile time error */
}
/* dummy definition */
void f() { return; }
void f2(void) { return; }
Worst case scenario, you may end up silently corrupting your stack. These type of bugs, when they do occur, are pretty hard to debug. So it's best to rule out the possibility of them happening all together.
K&R 2nd edition did contain a strong warning not to use old-style function declaration near the end of chapter 1.7.
One of the easiest to make sure you're not using old style declaration is to use
the warning flag -Wstrict-prototypes
, which is supported both by GCC and Clang.
Additionally you could (and should) also enable -Wold-style-definition
.
Unfortunately, neither of these flags are enabled by default. Nor are they
enabled when using -Wall
or -Wextra
. So you'll have to enable them manually.
Short answer: Legacy braindeath.
Long answer: It's because pre-standard C (also called K&R C) did not have typed functions. So it was normal to have code like this:
/* file.h */
extern void f();
/* file.c */
void
f(argc, argv)
int argc;
char **argv;
{
/* do stuff */
}
Here f
is a function that takes an int
and a char **
as argument. However
there's no way to specify that in the prototype and it's up to the caller to
make sure he's calling the function properly.
When C89 (aka ANSI C) was standardized, making sure existing code worked was one
of the priority. And because of that it was decided that f()
will continue to
behave in legacy style where f(void)
would declare a no argument function.
This however was changed in C99, where old style declaration has been marked obsolete.
One valid use of the old style declaration might be where you're trying to
escape the type system. For example, you might want to have a function pointer
fp
which might either hold close (3)
or fclose (3)
, both of which return
an int
but take different type of arguments.
int close(int fd);
int fclose(FILE *stream);
In this case, it's possible to use an old style declaration to get around the type system:
int (*fp)();
fp = close;
fp = fclose;
But this is probably not the best example because in this case, you could've (and should've) simply used a union:
union {
int (*c)(int);
int (*fc)(FILE *);
} u;
u.c = close;
u.fc = fclose;
And given the fact that old style declaration are obsolete since C99,
it's best avoided entirely on new codebases with the help of
-Wstrict-prototypes
and -Wold-style-definition
.
I wrote this so I can use it as quick reference in the future since I see
people confusing f()
with f(void)
pretty often. Feel free to contact me via
email if you have any improvement or correction.
Tags: [ c ]