13

I have a simple code, where my functions are declared before main function like that:

int function1();
int function2();

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }

And after my main function I have definitions of functions:

Is there some difference, when I declare functions before main like this?

int function1(int x, float y);
int function2(int x, float y);

int main() {
   /* ... */
   function1(x,y);
   function2(x,y);
   /* .... */
}

int function1(int x, float y) { /* ... */ }
int function2(int x, float y) { /* ... */ }
jotik
  • 17,044
  • 13
  • 58
  • 123
Kristián Stroka
  • 698
  • 1
  • 8
  • 23
  • Your first two lines declare functions with a different signature (no parameters) to those you later declare. So the first two lines are not needed. Function declarations describe the name, number and types of parameters and the return type. Two functions can have the same name but different parameters. They can't differ only in return type. – BryanT Mar 03 '16 at 19:52
  • 10
    @BryanT This is incorrect (it would be correct in C++ though). In C, empty parentheses in the function declaration mean it can take _any_ number of arguments of _any_ type. If you explicitly want zero arguments, use `(void)`; see one of `main`'s standard signatures: `int main(void) { ... }`. – user4520 Mar 03 '16 at 20:19
  • I agree. I was thinking C++. But isn't still better to declare them exactly as they will be defined? – BryanT Mar 03 '16 at 20:47
  • 3
    May I first say, this question is totally worthy of 5 upvotes, and the top answer, those 75 points, brilliant, I really hope SO scores influence careers and answers like this should indeed matter. But unfortunately it's not all good news. If SO is to influence our careers you should either be culled or sent into negative points for not using `-Wall -Wextra` and then the compiler would have told you the difference. – Alec Teal Mar 04 '16 at 00:49
  • The accepted answer is still incorrect for the implications. While it talks about the general case of using default argument promotions and not providing prototypes, it fails to notice that the first program specifically, because it takes `float` as an argument, **always** leads to undefined behaviour, as a `float` is not something that can be a result of default argument promotions. – Antti Haapala -- Слава Україні Mar 04 '16 at 05:47

4 Answers4

18

Yes, they are different.

In the first example, you are just telling the compiler about the name and return type of the function and nothing of its expected arguments.

In the second example you are telling the compiler the full signature of the functions, both return type and expected arguments, prior to calling them.

The second form is pretty much universally better as it helps you compiler do a better job warning you when you have the wrong type or number of arguments when calling the function.

Also note int function() in C is a function that can accept any arguments, not a function that accepts no arguments. For that you need an explicit void, i.e int function(void). This mostly trips up those coming to C from C++.

See also: Why does a function with no parameters (compared to the actual function definition) compile?

To demonstrate why the first, antiquated form is bad in modern C, the following program compiles without warning with gcc -Wall -ansi -pedantic or gcc -Wall -std=c11.

#include<stdio.h>
int foo();

int main(int argc, char**argv)
{
  printf("%d\n", foo(100));
  printf("%d\n", foo(100,"bar"));
  printf("%d\n", foo(100,'a', NULL));
  return 0;
}

int foo(int x, int y)
{
  return 10;
}

UPDATE: M&M brought to my attention that my example uses int not float for the functions. I think we can all agree that declaring int function1() is bad form, but my statement that this declaration accepts any arguments is not quite correct. See Vlad's answer for relevant spec section explaining why that is the case.

Community
  • 1
  • 1
Brian McFarland
  • 9,052
  • 6
  • 38
  • 56
14

Yes, they're different; the second is correct, the first one as a whole is wrong. It is so wrong that GCC 5.2.1 refuses to compile it altogether. That it works for you at all is just a fluke:

/* this coupled with */
int function1();

int main() {
    /* this */
    function1(x, y);
}

/* and this one leads to undefined behaviour */
int function1(int x, float y) {
    /* ... */
}

In the code above, the declaration int function1(); does not specify the argument types (it does not have a prototype), which is considered an obsolescent feature in C11 (and C89, C99 for that matter) standard. If that kind of function is called, default argument promotions are done on the arguments: int is passed as is, but float is promoted to double.

As your actual function expects arguments of (int, float), not (int, double), this will result in undefined behaviour. Even if your function expected a (int, double) but y was an integer, or say you called it with function1(0, 0); instead of function(0, 0.0);, your program would still have undefined behaviour. Fortunately GCC 5.2.1 notices that the declaration and definition of function1 are conflicting:

% gcc test.c
test.c:9:5: error: conflicting types for ‘function1’
 int function1(int x, float y) {
     ^
test.c:9:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration
 int function1(int x, float y) {
 ^
test.c:1:5: note: previous declaration of ‘function1’ was here
 int function1();
     ^
test.c:12:5: error: conflicting types for ‘function2’
 int function2(int x, float y) {
     ^
test.c:12:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration
 int function2(int x, float y) {
 ^
test.c:2:5: note: previous declaration of ‘function2’ was here
 int function2();
     ^

and the compiler exits with error code, whereas my tcc compiles it happily, no diagnostics, nothing. It just produces broken code. The same is true of course, if you had the declaration in a header file, and the definition in a different compilation unit that did not include that declaration.


Now, should the compiler not detect this case, at runtime, anything could happen, as expected for undefined behaviour.

For example, suppose a case that the arguments were passed on stack; on 32-bit processors int and float could fit in 4 bytes, whereas double could be 8 bytes; the function call would then push x as int and y as double even if it was float - in total the caller would have pushed 12 bytes and the callee would only expect 8.

In another case, suppose you'd call the function with 2 integers. The calling code would then load these into integer registers, but the caller would expect a double in a floating point register. The floating point register could contain a trap value, which, when accessed would kill your program.

What is worst, your program might now behave as expected, thus containing a heisenbug that could cause problems when you recompile the code with a newer version of compiler, or port it to another platform.

Community
  • 1
  • 1
14

The difference is that then there is a function prototype as in the second code snippet then the compiler checks that the number and types of arguments correspond to the number and types of the parameters. The compiler can issue an error at the compilation-time if it will find an inconsistence.

If there is no function prototype as in the first code snippet then the compiler performs default argument promotions on each argument that includes the integer promotions and conversion of expressions of type float to type double. If after these operations the number and types of promoted arguments do not correspond to the number and types of parameters the behaviour is undefined. The compiler can be unable to issue an error because the function definition can be in some other compilation unit.

here are relevant quotes from the C Standard (6.5.2.2 Function calls)

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. Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.

6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined, except for the following cases:

— one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;

— both types are pointers to qualified or unqualified versions of a character type or void.

As for your code snippets then if the second parameter had type double then the code would be well-formed. However as the second parameter has type float but the corresponding argument will be promoted to type double then the first code snippet has undefined behaviour.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
2

In the first case, main() performs integer promotions on each argument and float-to-double promotions. These are called "default argument promotions". So you'll probably end up calling the functions incorrectly, by passing an int and a double wheras the functions expect an int and a float.

See Default argument promotions in C function calls and the answers for more details.

jotik
  • 17,044
  • 13
  • 58
  • 123