86

What is useful about this C syntax — using 'K&R' style function declarations?

int func (p, p2)
    void* p;
    int  p2;
{
    return 0;
}

I was able to write this in Visual Studios 2010beta

// yes, the arguments are flipped
void f()
{
    void* v = 0;
    func(5, v);
}

I don't understand. What's the point of this syntax? I can write:

int func (p, p2)
    int  p2;
{
    return 0;
}
// and write
int func (p, p2)
{
    return 0;
}

The only thing it seems to specify is how many parameters it uses and the return type. I guess parameters without types is kind of cool, but why allow it and the int paranName after the function declarator? It's weird.

Also is this still standard C?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • It's an older style of function declarations. Please see this link. [https://stackoverflow.com/questions/22500/what-are-the-major-differences-between-ansi-c-and-kr-c](https://stackoverflow.com/questions/22500/what-are-the-major-differences-between-ansi-c-and-kr-c "https://stackoverflow.com/questions/22500/what-are-the-major-differences-between-ansi-c-and-kr-c") [link text](https://stackoverflow.com/questions/22500/what-are-the-major-differences-between-ansi-c-and-kr-c "https://stackoverflow.com/questions/22500/what-are-the-major-differences-between-ansi-c-and-kr-c") – Leonidas Tsampros Oct 27 '09 at 13:04

6 Answers6

166

The question you are asking is really two questions, not one. Most replies so far have tried to cover the entire thing with a generic blanket "this is K&R style" answer, while, in fact, only a small part of it has anything to do with what is known as K&R style (unless you see the entire C language as "K&R-style" in one way or another :)

The first part is the strange syntax used in the function definition

int func(p, p2)
void *p;
int  p2; /* <- optional in C89/90, but not in C99 */
{
  return 0;
}

This one is actually a K&R-style function definition. Other answers have covered this pretty well. And there's not much to it, actually. The syntax is deprecated but still fully supported even in C18 (except for "no implicit int" rule in C99 or later, meaning that in C99 you can't omit the declaration of p2).

The second part has little to do with K&R-style. I refer to the fact that the function can be called with "swapped" arguments, i.e. no parameter type checking takes place in such a call. This has very little to do with K&R-style definition per se, but it has everything to do with your function having no prototype. You see, in C, when you declare a function like this

int foo();

it actually declares a function foo that takes an unspecified number of parameters of unknown type. You can call it as

foo(2, 3);

and as

j = foo(p, -3, "hello world");

and so on (you get the idea);

Only the call with proper arguments will "work" (meaning that the others produce undefined behavior), but it is entirely up to you to ensure its correctness. The compiler is not required to diagnose the incorrect ones, even if it somehow magically knows the correct parameter types and their total number.

Actually, this behavior is a feature of C language. A dangerous one, but a feature nevertheless. It allows you to do something like this:

void foo(int i);
void bar(char *a, double b);
void baz(void);

int main()
{
  void (*fn[])() = { foo, bar, baz };
  fn[0](5);
  fn[1]("abc", 1.0);
  fn[2]();
}

i.e. mix different function types in a "polymorphic" array without any typecasts (variadic function types can't be used here, though). Again, the inherent dangers of this technique are quite obvious (I don't remember ever using it, but I can imagine where it can be useful), but that's C after all.

Finally, the bit that links the second part of the answer to the first. When you make a K&R-style function definition, it doesn't introduce a prototype for the function. As far as function type is concerned, your func definition declares func as

int func();

i.e. neither the types nor the total number of parameters are declared. In your original post, you say, "... it seems to specify is how many params it uses ...". Formally speaking, it doesn't! After your two-parameter K&R-style func definition, you still can call func as

func(1, 2, 3, 4, "Hi!");

and there won't be any constraint violation in it. (Normally, a quality compiler will give you a warning).

Also, a sometimes overlooked fact is that

int f()
{
  return 0;
}

is also a K&R-style function definition that does not introduce a prototype. To make it "modern" you'd have to put an explicit void in the parameter list:

int f(void)
{
  return 0;
}

Finally, contrary to popular belief, both K&R-style function definitions and non-prototyped function declarations are fully supported in C99 and beyond (up to C18, but not in C23). The former has been deprecated since C89/90, if I remember correctly. C99 requires the function to be declared before the first use, but the declaration is not required to be a prototype. The confusion apparently stems from the popular terminological mix-up: many people call any function declaration "a prototype", while, in fact, "function declaration" is not the same thing as "prototype".

Note that C23 will change the rules. The K&R style function definition will no longer be standard C. Further, a function declaration such as extern int func(); will declare a function prototype equivalent to extern int func(void); — just as in C++. And a function definition such as int func() { … } will define a prototype for the function.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 1
    But what is the difference now between a varargs function and an unprototyped one. – Sebastian Sep 26 '13 at 09:09
  • 1
    @Sebastian Godelet varargs are unnececairly strict. For example calling a vararg function with one int, and popping it as bytes is invalid. So is defining a function with only vararg parameters. – yyny Jun 04 '16 at 21:57
  • 1
    @Sebastian Vararg functions can actually be called with different number of parameters correctly, such as `printf`. Functions without prototypes can be called correctly only with a certain fixed number of arguments, but the compiler does not know which one and can not check (so you must do that). – not-a-user Apr 09 '19 at 06:28
  • I guess its time to change `supported even in C99` . `C99 to C11`. :-). – sjsam Apr 17 '19 at 12:45
  • I remember when I started with C on the Amstrad PCW that I was confused because the compiler used that old function declaration syntax but the tutorial book I had was using the newer syntax. Those were the days ... 25 years ago ...! – Andrew Truckle Jun 25 '19 at 04:27
  • If K&R function definition doesn't introduce function prototype, what's is the purpose of enumerating all the function arguments and their types? You listed all parameters and their types but still get a function of _unspecified number of parameters of unknown type_ – aryndin Jul 15 '21 at 21:17
  • 1
    @aryndin: In K&R the purpose of listing the parameters is purely *internal*: to make them accessible *inside* the function. It has no external effect, i.e. it does not introduce a prototype. – AnT stands with Russia Jul 16 '21 at 19:25
  • 1
    @not-a-user *Functions without prototypes can be called correctly only with a certain fixed number of arguments* No, the number of arguments can vary if the function only accesses "optional" variables when they're known to have been passed. The classic example is `open()`, which would only get a third `mode` argument if the `O_CREAT` flag was set in the second `flags` argument - and that's why `open()` is a varargs function in today's standard C. – Andrew Henle May 19 '23 at 04:01
  • _"polymorphic" array_ ... Cool! [Obfuscated C](https://en.wikipedia.org/wiki/International_Obfuscated_C_Code_Contest), anyone? :) – RichieD May 24 '23 at 18:28
20

This is pretty old K&R C syntax (pre-dates ANSI/ISO C). Nowadays, you should not use it anymore (as you have already noticed its major disadvantage: the compiler won't check the types of arguments for you). The argument type actually defaults to int in your example.

At the time, this syntax was used, one sometimes would find functions like

foo(p, q) 
{
    return q + p;
}

which was actually a valid definition, as the types for p, q, and the return type of foo default to int.

Dirk
  • 30,623
  • 8
  • 82
  • 102
6

This is simply an old syntax, that pre-dates the "ANSI C" syntax you might be more familiar with. It's called "K&R C", typically.

Compilers support it to be complete, and to be able to handle old code bases, of course.

unwind
  • 391,730
  • 64
  • 469
  • 606
3

That's a relic from when C had no prototypes for functions. Way back then, (I think) functions were assumed to return int and all its arguments were assumed to be int. There was no checking done on function parameters.

You're much better off using function prototypes in the current C language.
And you must use them in C99 (C89 still accepts the old syntax).

And C99 requires functions to be declared (possibly without a prototype). If you're writing a new function from scratch, you need to provide a declaration ... make it a prototype too: you lose nothing and gain extra checking from the compiler.

pmg
  • 106,608
  • 13
  • 126
  • 198
  • 5
    Incorrect. In C99 functions have to be explicitly declared before they are called. Nevertheles, C99 does not requre a prorotyped declaration. C99 fully supports K&R syntax with one change: implicit `int` rule has been removed. – AnT stands with Russia Oct 27 '09 at 13:50
3

This is the original K&R syntax before C was standardized in 1989. C89 introduced function prototypes, borrowed from C++, and deprecated the K&R syntax. There is no reason to use it (and plenty of reasons not to) in new code.

Robert Gamble
  • 106,424
  • 25
  • 145
  • 137
1

Unfortunately, Ritchie has passed, so we can no longer ask him. However, looking at the documented history of C gives some really important clues that will answer your original question. This answer is an inference based on evidence.

C has its roots in a language called B, which in turn, is a syntax-simplified version of BCPL. BCPL is a language which operates on ints only; that is, the sole data type available to you in this language is exactly however many bits a single machine word is. On a 16-bit machine, BCPL lets you manipulate 16-bit values; on a 32-bit machine, 32-bit values, etc.

Correspondingly, when you define a function in BCPL, it will not have any type annotations:

LET qux(a,b,c) = VALOF $(
    /* ...etc... */
    RESULTIS someValueHere;
$)

Note that $( and $) can now be replaced with { and }, but when BCPL was most popular (mid-60s to mid-70s), not all terminal keyboards had a { and } character.

Thompson simplified BCPL's syntax to create B, so as to fit the compiler in the resource constraints on the PDP-7 he was using at the time. The LET and VALOF keywords were removed, since they didn't offer anything to the programmer or the compiler, and the block delimiters were replaced with braces, simplifying the lexer. RESULTIS's verbosity was replaced with "return", and we now get:

qux(a,b,c) {
  /* ...etc... */
  return someValueHere;
}

That looks like valid C code, but it could just as well be B code too. Remember that B, like BCPL, was a single-typed language.

Supporting multiple types didn't come until C, when the overhead of processing bytes embedded inside of machine words became more significant. If I'm allowed to speculate even more here, Ritchie (who had worked with Thompson to create B) most likely just re-used B's parser code and just tweaked it to support different types. Thus, it becomes fairly natural for C's syntax back then to look like this:

qux(a,b,c)
char a;
float b;
{
    /* ...etc... */
    return someVal;
}

This syntax would allow more or less effortless porting of a largish body of B code into the C ecosystem of the day.

C has always been something of a fluid language, especially in its early days. Did you know C's original method for statically declaring a constant looked like this?

someVal 1024;

Talk about minimum syntax, eh? Today, this syntax is not supported, even in most "K&R" style C compilers.

So what is the value (to you) of this syntax? Nothing. Absolutely nothing. It's just an evolutionary artifact, an accident if you will, of C's heritage and its continued evolution away from BCPL.

I hope this helps establish the historical context for this quirky syntax.