92

I'm relatively new to C. I've come across a form of function syntax I've never seen before, where the parameter types are defined after that parameter list. Can someone explain to me how it is different than the typical C function syntax?

Example:

int main (argc, argv)
int argc;
char *argv[];
{
return(0);
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
John
  • 1,324
  • 3
  • 14
  • 19

7 Answers7

72

That's the old-style syntax for parameter lists, which is still supported. In K&R C you could also leave off the type declarations and they would default to int. i.e.

main(argc, argv)
char *argv[];
{
    return 0;
}

would be the same function.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • 19
    Both C89/90 and C99 still officially support K&R style declarations. However, in C99 all parameters have to be explicitly declared (no "implicit int" rule anymore). – AnT stands with Russia Oct 18 '09 at 17:12
  • 1
    I hate to be late to the party (like 4 years later?), but do you mean to say that in C99 that they need to be explicitly declared *in the parameter list* or *in the function*? For example, the default int rule - since they no longer default to int, then their types need to be declared in the function before use **if** the types were not supplied in the parameter list? – Chris Cirefice Sep 30 '13 at 10:43
  • 4
    @ChrisCirefice To answer you 4 years later: in C99, all parameters must be declared in the parameter list. Variables which are declared in the function have never defaulted to `int`, and thus must be declared explicitly. – Frank Kusters Aug 14 '17 at 11:40
29

What's also interesting is the calling convention difference of functions with, and functions without a prototype. Consider an old style definition:

void f(a)
 float a; {
 /* ... */
}

In this case, the calling convention is that all arguments are promoted before being passed to the function (for example, a float argument is first promoted to double, before being passed). So if f receives a double but the parameter has type float (which is perfectly valid) the compiler has to emit code that converts the double to a float prior to executing the function's body.

If you include a prototype, the compiler does not do such automatic promotions anymore and any data passed is converted to the types of the parameters of the prototype as if by assignment. So the following is not legal and results in undefined behavior:

void f(float a);
void f(a)
  float a; {

}

In this case, the function's definition would convert the submitted parameter from double (the promoted form) to float because the definition is old-style. But the parameter was submitted as a float, because the function has a prototype. For example, clang gives

main.c:3:9: warning: promoted type 'double' of K&R function parameter is not compatible with the parameter type 'float' declared in a previous prototype [-Wknr-promoted-parameter]

Your options of solving the contradictions are the two following:

// option 1
void f(double a);
void f(a)
  float a; {

}

// option 2
// this declaration can be put in a header, but is redundant in this case, 
// since the definition exposes a prototype already if both appear in a 
// translation unit prior to the call. 
void f(float a); 

void f(float a) {

}

Option 2 should be preferred if you have the choice because it gets rid of the old style definition up front. If such contradicting function types for a function appears in the same translation unit, the compiler will usually tell you (but is not required). If such contradictions appear over multiple translation units, the error will possibly go unnoticed and can result in hard to predict bugs. It is best to avoid these old style definitions.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
11

This is the so caller K&R style or old-style declaration.

Note, that this declaration is significantly different from the modern declaration. K&R declaration does not introduce a prototype for the function, meaning that it doesn't expose the types of the parameters to the outside code.

gioele
  • 9,748
  • 5
  • 55
  • 80
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
7

There is no difference, it is just that that is the old syntax for function declarations in C -- it was used pre ANSI. Never write such code unless you plan to give it to your friends from the 80's. Also, never depend upon implicit type assumptions (as another answer seems to suggest)

aviraldg
  • 9,531
  • 6
  • 41
  • 56
5

While the old syntax for function definition still works (with warnings, if you ask your compiler), using them does not provide function prototypes.
Without function prototypes the compiler will not check if the functions are called correctly.

#include <stdio.h>
int foo(c)
int c;
{ return printf("%d\n", c); }

int bar(x)
double x;
{ return printf("%f\n", x); }

int main(void)
{
    foo(42); /* ok */
    bar(42); /* oops ... 42 here is an `int`, but `bar()` "expects" a `double` */
    return 0;
}

When the program is run, the output on my machine is

$ gcc proto.c
$ gcc -Wstrict-prototypes proto.c
proto.c:4: warning: function declaration isn’t a prototype
proto.c:10: warning: function declaration isn’t a prototype
$ ./a.out
42
0.000000
pmg
  • 106,608
  • 13
  • 126
  • 198
2

Its just the same but old fashion. You probably found it is some old, legacy code.

eyalm
  • 3,366
  • 19
  • 21
-1

Old or not, I would argue what is old and what not.. like the pyramids are ancient, but none of today’s so-called scientists have a clue how they were made. Looking back, old programs still work today without memory leaks, but these "new" programs tend to fail more than often. I see a trend here.

Probably they saw functions as structs which have an executable body. Knowledge of ASM is needed here to solve the mystery.

Edit, found a macro which indicates you do not need to supply argument names at all.

#ifndef OF /* function prototypes */
#  ifdef STDC
#    define OF(args)  args
#  else
#    define OF(args)  ()
#  endif
#endif

#ifndef Z_ARG /* function prototypes for stdarg */
#  if defined(STDC) || defined(Z_HAVE_STDARG_H)
#    define Z_ARG(args)  args
#  else
#    define Z_ARG(args)  ()
#  endif
#endif

Here is an usage example, library is zlib-1.2.11.

ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));

So my second guess would be for function overloading, otherwise these arguments had no use. One concrete function, and now infinite amount of functions with same name.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Ako
  • 956
  • 1
  • 10
  • 13