41

Recently I dug a little bit into old graphics libraries and found libxmi. The site was last updated on 08/09/2000. And in the source code I found the following style of function definitions which I have never seen before:

#include <stdio.h>

int add(a, b)
    int a, b;
{
    return a + b;
}

int main()
{
    printf("%d\n", add(5, 6));
}

Believe it or not but you can compile this with a recent gcc and MSVC (in C mode). When was this type of definitions invented? Is it even standardized ISO C? What is this?

16
  • 42
    That's K&R C. Commented Feb 1, 2022 at 20:54
  • 47
    Yet again, I feel old now...
    – Jon Custer
    Commented Feb 1, 2022 at 21:08
  • 34
    That's what C was like before ISO standardization. Commented Feb 1, 2022 at 21:08
  • 11
    Without the type declaration, int would haver been assumed, so that was redundant.
    – Erik Eidt
    Commented Feb 1, 2022 at 21:10
  • 14
    The key sentence in the Wikipedia link above is "Even after the publication of the 1989 ANSI standard, for many years K&R C was still considered the "lowest common denominator" to which C programmers restricted themselves when maximum portability was desired" - an old graphics library would want that maximum portability.
    – Jon Custer
    Commented Feb 1, 2022 at 21:30

4 Answers 4

73

It's the original C syntax as designed by Dennis Ritchie, so bite your tongue :-)

As mentioned in another answer, the style was not unusual at the time. For an example of early art: FORTRAN II (1958) did not have variable declarations at all. A variable existed by being mentioned. The type of an identifier was determined by its initial letter (I to N INTEGER, otherwise REAL). There was a DIMENSION statement for specification of array identifiers.

Subroutine declarations would therefore have no type information in the SUBROUTINE statement, though may be followed by a DIMENSION.

      SUBROUTINE FOO(VALUES, KOUNT)
      DIMENSION VALUES(KOUNT)

VALUES is a REAL array of KOUNT elements. (We all learned the art of bad spelling)

FORTRAN IV (~1962) added type declarations, but they were optional in general. The implicit declaration and "initial letter" rules still applied. The dummy arguments of subroutines could have type specifiers in subsequent statements, but were not required to.

      SUBROUTINE BAR(VALUES, KOUNT)
      DOUBLE PRECISION VALUES
      DIMENSION VALUES(KOUNT)

In Algol 60, the argument types are specified after the procedure heading, and before the body of the procedure - here just a single statement. Assignment to the procedure name was how a return value was indicated.

integer procedure square(n);  
    value n; integer n;  
    square := n × n;  

By Algol 68, you might find the formal-parameter style a little more familiar. The returned value is the value of the last expression to be executed. The type of 'square' is read as something like 'proc of int returning int'.

proc square = (int n) int :  
    (n × n);

In short, there was nothing unusual at the time about that aspect of C. The style that C initially adopted for function definitions was quite common at the time, so it's not so strange a choice for Ritchie to have made. The style you are more familiar with did not fully emerge in C until ANSI C, in 1989 - 17 years after C was created.

Note too that there was not necessarily a way to state all of the attributes of a formal parameter in a single statement: Algol needed integer for type, and (optionally) value for mechanism.

Incidentally, the reason for the ANSI C change was not so much in the definition of a function -- both forms of function definitions convey the argument types -- but in the declaration of functions, for example in header files. The latter carried no argument information at all - for example all you could say about an external function in K&R style was what it returned: double sin().

This is error-prone; you don't find out (with the compiler technology of the day) until execution time that you've got a mismatch.

While ANSI C added the new syntax, they did not outlaw the old K&R syntax, which remains valid, in the successive ISO standards as well as the original ANSI.

Link: paper from Ritchie on the history of C

18
  • 6
    Even the most modern Fortran (2018) still uses this style, so it's not just a thing of the 60's.
    – TooTea
    Commented Feb 2, 2022 at 15:37
  • 9
    The most modern Fortran is a thing of the 60s being beat into standing up.
    – Kaz
    Commented Feb 2, 2022 at 20:14
  • 4
    This answer could be read to indicate that K&R style does not conform to ISO C, but to answer "Is it even standardized ISO C?": yes, it is. Every revision of ISO C, up to and including the current one (C17), includes specifications for K&R function definition style alongside its specifications for ANSI definition style. Commented Feb 3, 2022 at 3:17
  • 1
    Does KOUNT have to be spelled wrong to avoid conflict with the built-in COUNT function, or is it just a way to avoid declaring the variable as an integer by exploiting the implicit leading I-N rule?
    – dan04
    Commented Feb 4, 2022 at 1:10
  • 8
    And so, as they say, GOD is REAL - unless declared INTEGER...
    – Zeus
    Commented Feb 4, 2022 at 5:53
30

As others have explained, it's the original K&R C style. But why is it this way?

C is the successor to B (Wikipedia). It was typeless, so there was no need to specify which type a function argument had:

/* The following function will print a non-negative number, n, to
   the base b, where 2<=b<=10.  This routine uses the fact that
   in the ASCII character set, the digits 0 to 9 have sequential
   code values.  */

printn(n, b) {
        extrn putchar;
        auto a;
        /* Wikipedia note: the auto keyword declares a variable with
           automatic storage (lifetime is function scope), not
           "automatic typing" as in C++11. */

        if (a = n / b)        /* assignment, not test for equality */
                printn(a, b); /* recursive */
        putchar(n % b + '0');
}

(The original source of this example is from the User's Reference to B by Ken Thompson.)

So when Dennis and Ken evolved the language and added types, they needed a way to specify which type an argument had. I don't know the exact reasons for adding this info between ) and { but it does make sense.

A point I remember but don't have a reference to right now: The types default to int if left out, and this was important and intentional. Due to this quirk, some B code can be compiled with C with no or minor changes. This eased porting existing code.

13

It’s an old style where type declarations were separate from the naming (and positioning) of the arguments. The style dates from at least the 1960s in languages like FORTRAN, Algol 60, and PL/I.

7

As others have mentioned, this was the style of function declarations before ANSI C. It’s often called K&R C, after Brian Kernighan and Dennis Ritchie. Back in the early ’90s, some books about the then-recent changes to the language complained that the new style of declaring functions was more like C++ than C.

Why was that chosen originally? The calling convention on the computers for which C was originally developed was to pass arguments on the stack. That syntax made it simple to call a function like printf() with any number of arguments of arbitrary type, and have the called function figure out how to interpret them based on the format string. (It was less simple to write the variadic function in K&R C, but that would have been done in assembler back then.) Dennis Ritchie wanted C to be more strongly-typed than B, but not too strictly to write a generic I/O library.

ANSI C provides more complex ways to switch back to the older syntax, to be able to link to existing libraries, or partly back to it for variadic functions. This lets printf() and scanf() be supported, but enables type-checking of function arguments wherever possible. It would later get a simple form of generic function for use in the math library, and later still have that mechanism become available as a building block. Originally, though, there was only one kind of function, and it supported arbitrary arguments.

9
  • 4
    I think this slightly misses the point of the question. Having int foo(x); int x; does not make variadic functions any easier than int foo(int x). What allows variadic functions is making the provision of any formal argument list (whether as x or int x) optional -- which is possible in both declaration conventions, or at least was until foo(void) was invented.
    – dave
    Commented Feb 2, 2022 at 15:52
  • 1
    @another-dave The strict prototyping actually does preclude variadic functions, by forcing the caller to pass only that number of arguments with those types. This is why C89 needed to add the more complex ,(const char* format, ...) syntax to enable it. But, before ANSI C, that was how all C functions worked.
    – Davislor
    Commented Feb 2, 2022 at 16:36
  • Of course. As I said, it's not where the 'int' goes that makes the difference.
    – dave
    Commented Feb 2, 2022 at 17:08
  • @another-dave The context in which it does is, if the prototype specifies that the function takes one parameter of type int, it statically type-checks every call to the function. If there is no prototype, the compiler still allows the function to be called with any number and type of arguments. (It also changes how the argument types are implicitly converted.) If there’s no prototype preceding the call, the compiler has no way of knowing what the K&R-style type signature in the other module says.
    – Davislor
    Commented Feb 3, 2022 at 1:54
  • 3
    @another-dave: On most systems, a function of the form int foo(x,y,z) int x,y,z; {...} could safely be invoked with 1, 2, or 3 arguments if it never made use of any arguments that weren't passed to it. One could, for example, have a function int sendPacket(dat, length) char *dat; int length;, that would send an empty packet, ignoring the length argument, if dat was null. Call-side code which passed null without specifying a length could then be smaller than would be possible if both arguments had to be passed. Not all platforms could efficiently support support semantics, however.
    – supercat
    Commented Feb 3, 2022 at 17:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .