76

I don't get why does this code compile?

#include <stdio.h>
void foo() {
    printf("Hello\n");
}

int main() {
    const char *str = "bar";
    foo(str);
    return 0;
}

gcc doesn't even throw a warning that I am passing too many arguments to foo(). Is this expected behavior?

reddragon
  • 961
  • 8
  • 17

4 Answers4

91

In C, a function declared with an empty parameter list accepts an arbitrary number of arguments when being called, which are subject to the usual arithmetic promotions. It is the responsibility of the caller to ensure that the arguments supplied are appropriate for the definition of the function.

To declare a function taking zero arguments, you need to write void foo(void);.

This is for historic reasons; originally, C functions didn't have prototypes, as C evolved from B, a typeless language. When prototypes were added, the original typeless declarations were left in the language for backwards compatibility.

To get gcc to warn about empty parameter lists, use -Wstrict-prototypes:

Warn if a function is declared or defined without specifying the argument types. (An old-style function definition is permitted without a warning if preceded by a declaration which specifies the argument types.)

ecatmur
  • 152,476
  • 27
  • 293
  • 366
55

For legacy reasons, declaring a function with () for a parameter list essentially means “figure out the parameters when the function is called”. To specify that a function has no parameters, use (void).

Edit: I feel like I am racking up reputation in this problem for being old. Just so you kids know what programming used to be like, here is my first program. (Not C; it shows you what we had to work with before that.)

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 2
    Wait, are you serious? Why would you ever do that? – Drise Sep 28 '12 at 15:55
  • 5
    Type rules, object orientation, encapsulation, overloading, and other modern language features did not develop overnight. Decades ago, programming languages were much more primitive. And experimental. C was an advance over what came before it, and reasons for strong typing of function parameters or how you would implement them were not clear. A primary motivation at the time was giving programmers easy ways to do powerful things. The need for using strong typing to reduce bugs was not as big a motivation then. – Eric Postpischil Sep 28 '12 at 15:59
  • 5
    @Drise: you wouldn't, not anymore. However, prior to the 1989 standard, the C language didn't support prototype declarations (where the number and types of the parameters are declared); you could only specify the function's return type in a declaration. There's a *lot* of legacy code out there that would break if you changed the rules for empty parameter lists in declarations, so it's still supported, but new code should *always* use prototype syntax when declaring functions. – John Bode Sep 28 '12 at 16:12
  • 3
    In terms of type safety, early C was an advance only over B. There were plenty of safely typed languages (Simula, Pascal, even Algol) that had no problem enforcing strong typing of function parameters. C won by being more efficient and easier to implement on resource-constrained minicomputers, but the trade-offs involved were apparent at the time. – ecatmur Sep 28 '12 at 17:26
  • 1
    @John Bode is correct about pre-1989 C. IIRC it is also referred to as Traditional C. Another great 'feature' allowed using int for char. My first C compiler was traditional C and it taught me to love ANSI C. – jqa Sep 28 '12 at 17:33
  • We used the term "K&R C" instead of "Traditional C" to distinguish it from "ANSI C". Having both the 1st and 2nd editions of the K&R book was huge benefit for those working with both simultaneously and keeping the nuances straight between the two. @Drise, you also need to remember that Linux and gcc essentially changed the way we think about portability. Before then, we often had to resort to all sort of shenanigans for code to work across multiple systems and compilers. – mpdonadio Sep 28 '12 at 18:26
  • It must be remembered that BCPL and B (the languages from which C was derived) were typeless languages; everything was stored in a `cell`. The notion of type safety simply didn't apply. So naturally it took a few iterations for C to get it right. – John Bode Sep 28 '12 at 19:06
  • @EricPostpischil, I'm amazed by you being able to save your first program for decades. My first experiments were on ZX Spectrum, and even if I have saved audio tapes on which they were persisted, it would be non-trivial to read them back now... – Dmitry Frank Sep 05 '16 at 15:59
  • 1
    Hmm OP's code is not a simple function declaration. It is a definition. This answer addresses definition like `void foo();` but not `void foo() { ...}` defintion. IIRC, the later has C99 rules that make this the same as `void foo(void) { ...}` – chux - Reinstate Monica Aug 03 '17 at 21:30
  • 1
    C compilers started supporting prototypes shortly before the publication of the 1989 standard. Also, although the Standard didn't mandate such behavior, most compilers would allow calling code to omit arguments for unused parameters when calling functions without prototypes, which could sometimes be more efficient than having to pass meaningless values for such arguments. – supercat Aug 29 '17 at 20:35
11
void foo() {
    printf("Hello\n");
}

foo(str);

in C, this code does not violates a constraint (it would if it was defined in its prototype-form with void foo(void) {/*...*/}) and as there is no constraint violation, the compiler is not required to issue a diagnostic.

But this program has undefined behavior according to the following C rules:

From:

(C99, 6.9.1p7) "If the declarator includes a parameter type list, the list also specifies the types of all the parameters; such a declarator also serves as a function prototype for later calls to the same function in the same translation unit. If the declarator includes an identifier list,142) the types of the parameters shall be declared in a following declaration list."

the foo function does not provide a prototype.

From:

(C99, 6.5.2.2p6) "If the expression that denotes the called function has a type that does not include a prototype [...] If the number of arguments does not equal the number of parameters, the behavior is undefined."

the foo(str) function call is undefined behavior.

C does not mandate the implementation to issue a diagnostic for a program that invokes undefined behavior but your program is still an erroneous program.

ouah
  • 142,963
  • 15
  • 272
  • 331
7

Both the C99 Standard (6.7.5.3) and the C11 Standard (6.7.6.3) state:

An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.

Since the declaration of foo is part of a definition, the declaration specifies that foo takes 0 arguments, so the call foo(str) is at least morally wrong. But as described below, there are different degrees of "wrong" in C, and compilers may differ in how they deal with certain kinds of "wrong".

To take a slightly simpler example, consider the following program:

int f() { return 9; }
int main() {
  return f(1);
}

If I compile the above using Clang:

tmp$ cc tmp3.c
tmp3.c:4:13: warning: too many arguments in call to 'f'
  return f(1);
         ~  ^
1 warning generated.

If I compile with gcc 4.8 I don't get any errors or warnings, even with -Wall. A previous answer suggested using -Wstrict-prototypes, which correctly reports that the definition of f is not in prototype form, but this is really not the point. The C Standard(s) allow a function definition in a non-prototype form such as the one above and the Standards clearly state that this definition specifies that the function takes 0 arguments.

Now there is a constraint (C11 Sec. 6.5.2.2):

If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters.

However, this constraint does not apply in this case, since the type of the function does not include a prototype. But here is a subsequent statement in the semantics section (not a "constraint"), that does apply:

If the expression that denotes the called function has a type that does not include a prototype ... If the number of arguments does not equal the number of parameters, the behavior is undefined.

Hence the function call does result in undefined behavior (i.e., the program is not "strictly conforming"). However, the Standard only requires an implementation to report a diagnostic message when an actual constraint is violated, and in this case, there is no violation of a constraint. Hence gcc is not required to report an error or warning in order to be a "conforming implementation".

So I think the answer to the question, why does gcc allow it?, is that gcc is not required to report anything, since this is not a constraint violation. Moreover gcc does not claim to report every kind of undefined behavior, even with -Wall or -Wpedantic. It is undefined behavior, which means the implementation can choose how to deal with it, and gcc has chosen to compile it without warnings (and apparently it just ignores the argument).

Steve Siegel
  • 504
  • 5
  • 11
  • In the days before C89, most C compilers would ignore any unused arguments, and would allow callers to omit arguments associated with unused parameters. Validation of proper calling conventions is generally useful, but some existing APIs may require that functions be passed varying numbers and types of arguments. The normal way of handling that is with `...`, but parameter values for `...` functions are fetched differently from those for "normal" functions. – supercat Aug 29 '17 at 20:42