in C, why do variables need to be declared before use but functions dont?

618 Views Asked by At

I'm reading K&R and started wondering, why do variables need to be declared before they are used, but functions dont?

In C, all variables must be declared before they are used, usually at the beginning of the function before any executable statements.

Regarding functions it says:

The declaration int power(int base, int n); just before main says that power is a function that expects two int arguments and returns an int. This declaration, which is called a function prototype, has to agree with the definition and uses of power. It is an error if the definition of a function or any uses of it do not agree with its prototype

Does it have something to do with allocating memory for variables? e.g the compiler needs to know the variable size when it uses it, but couldnt it wait until it found the declaration?

3

There are 3 best solutions below

0
John Bollinger On

I'm reading K&R and started wondering, why do variables need to be declared before they are used, but functions dont?

The second (and last) edition of K&R is way out of date with respect to the current specifications of ISO C. The language it describes is aligned with C90. These days it has more historical value than technical value.

I don't see how the quotations you present support the idea that functions do not need to be declared before use, although that was in fact true in C90. It is not true in any of the versions of the C language since, however: C99, C11, C17, and (soon) C23 all require functions to be declared before use. Modern compilers may accept implicitly-declared functions for backward compatibility reasons, however.

Nevertheless,

Does it have something to do with allocating memory for variables?

Not really. A compiler could support automatic variables without advance declaration -- Fortran compilers did, for example, even before C was a twinkle in Dennis Ritchie's eye.

My best guess at the practical reason is that it makes C compilers easier to write and simpler (and smaller, which was important at the time). It may also be drawn in part from C's predecessor, B, though I'm having trouble determining whether B actually required pre-declaration (it did allow that).

With functions, on the other hand, a linking step was (and is) required whether functions are declared before use or not, so requiring declaration before use does not make compilers any easier, simpler, or smaller.

e.g the compiler needs to know the variable size when it uses it, but couldnt it wait until it found the declaration?

Nope. Implicit data typing was a known thing at the time of C's invention, and C had it. In early C, variable declarations did not need to specify a data type. It was sufficient to provide a storage-class specifier and name, in which case the type defaulted to int. And again, Fortran did not require variable declaration before use, so we can conclude that it would have been feasible for C not to require it, either.

1
0___________ On

You can exactly the same with the variables (functions are extern by definition).

#include <stdio.h>

extern int x;
extern double d;

int main(void)
{
    printf("%d\n", x);
    printf("%f\n", d);
}

int x = 5;
double d = 3.5;

In C there is a difference between declaration and definition.

There is even implicit data typing:

extern x;
extern d;

int main(void)
{
    printf("%d\n", x);
    printf("%d\n", d);
}

x = 5;
d = 3.5;

https://godbolt.org/z/YhbqT4jrh

0
Luis Colorado On

I'm reading K&R and started wondering, why do variables need to be declared before they are used, but functions dont?

In C, all variables must be declared before they are used, usually at the beginning of the function before any executable statements.

Well, In C everything must be declared before use. The problem is that, when you introduce a new function to the compiler (a function that has no declaration) C assumes a default declaration for function my_function like:

int my_function();

This is an unknown parameter list, and an int return value.

This is very very trouble making, as it's most probable that your function doesn't have that prototype. Indeed, this is a legacy of old C language definition (what is commonly called K&R C style, and indeed, the book you are reading describes a very old version of the language, but still accepted by compilers)

It is there due to the necessity of compiling very old programs still.

Let's illustrate it with an example:

Assume you have a program that uses the cos(3) (the mathematical trigonometric function cosine), and you use it as this:

#include <stdio.h>
/* #include <math.h>  // we deliverately exclude the definition of double sin(double); */
int main()
{
    printf("The cosine of 1 radian is: %g\n", cos(1));
}

The compiler will assume that you have used a function cos(), that returns an int (this is a mistake the programmer should be aware of) and receives an undetermined number of parameters, so the compiler will not check the parameters you pass to the compiler and pass them as you do, without any automatic conversion (from integer to floating point) because the function call defines an undetermined parameter list), so it passes the integer as is.

Let's see what happens (need to say that the compiler will notice as a warning, as cos() is an intrinsic function ---the cosine function is a known function to the compiler, that it treates as special, not what happens with the others (and not all compilers do what is shown below)

GCC on Linux

$ make pru$$
cc     pru13204.c   -o pru13204
pru13204.c: In function ‘main’:
pru13204.c:5:43: warning: implicit declaration of function ‘cos’ [-Wimplicit-function-declaration]
    5 |         printf("The cosine of 1 is %g\n", cos(1));
      |                                           ^~~
pru13204.c:2:1: note: include ‘<math.h>’ or provide a declaration of ‘cos’
    1 | #include <stdio.h>
  +++ |+#include <math.h>
    2 | 
pru13204.c:5:43: warning: incompatible implicit declaration of built-in function ‘cos’ [-Wbuiltin-declaration-mismatch]
    5 |         printf("The cosine of 1 is %g\n", cos(1));
      |                                           ^~~
pru13204.c:5:43: note: include ‘<math.h>’ or provide a declaration of ‘cos’
$ pru$$
The cosine of 1 is 0.540302

Let's say we have included <math.h> (which has a declaration of cos() function) Uncomment the second program line and see what happens:

$ make pru$$
cc     pru13204.c   -o pru13204
$ pru$$
The cosine of 1 is 0.540302
$

This time, no warning has occured, and the program behaves correctly, giving the correct answer. It seems that the problem is not such a problem, as we have done things bad with no apparent difference. That assumption is not correct, continue reading.

We have been lucky, as the compiler has recognized the cos() function as one of it's favourites, and has repaired the problem on the fly, providing the correct declaration for it, instead of the default behaviour expected on the compiler. With the sample on CLANG we'll see it clearly.

This time, we are showing a complete example, run on CLANG on FreeBSD, and showing that CLANG (which is different than GCC but tries to emulate all GCC behaviour, including even GCC extensions to the C language)

The error with clang 64 bit is more significative, in the sense that it says that it has implicitly declared the cos library function as having prototype double cos(double), which is the correct prototype, but this should not happen, had we used a non-intrinsic function.

$ make pru$$
cc -O2 -pipe  pru26408.c  -o pru26408
pru26408.c:7:33: warning: implicitly declaring library function 'cos' with type 'double (double)' [-Wimplicit-function-declaration]
        printf("Cos of %d == %g\n", i, cos(i));
                                       ^
pru26408.c:7:33: note: include the header <math.h> or explicitly provide a declaration for 'cos'
1 warning generated.

I have prepared another (it will be the last, I swear :)) example, to show that, on a non-intrinsic function (like the double Cos(double x) implementation I provide in the below example, the behaviour is clearly different:

Cos.h

This file will not be included in main.c so we don't get the proper definition of it.

#ifndef _COS_H /* protection against double inclussion */
#define _COS_H

double Cos(double x);

#endif /* _COS_H */

Cos.c

This file makes a dumb implementation of cosine function, by calling the actual one, but this time, Cos() will not be the intrinsic function the compiler knows about.

#include <math.h>
/* dummy implementation of the function cosine, based on the actual
 * function double cos(double x) */
double Cos(double x)
{
    return cos(x); /* just call the right function and return the proper value */
}

main.c

#include <stdio.h>
/* again, don't #include <math.h> or "Cos.h" (which will be shown below)
 * is provided to show the undefined behaviour in action */
int
main()
{
    int i = 1;
    printf("Cos of %d == %g\n", i, Cos(i));
}

Makefile

targets = test_Cos
toclean = $(targets)

test_Cos_deps =
test_Cos_objs = main.c Cos.c
test_Cos_ldfl =
test_Cos_libs = -lm
toclean += $(test_Cos_objs)

all: $(targets)
clean:
    rm -f $(toclean)

test_Cos: $(test_Cos_deps) $(test_Cos_objs)
    $(CC) $(LDFLAGS) $($@_ldfl) -o $@ $($@_objs) $($@_libs) $(LIBS)

This compilation will generate a different warning (and this time it will not repair the code, as in this case the function is not an intrinsic one, so the compiler doesn't know how to do the repairment), and declares it as int Cos(), instead of double Cos(double):

$ make
cc  -O2 -pipe -c main.c -o main.o
main.c:8:33: warning: implicit declaration of function 'Cos' is invalid in C99 [-Wimplicit-function-declaration]
        printf("Cos of %d == %g\n", i, Cos(i));
                                       ^
main.c:8:33: warning: format specifies type 'double' but the argument has type 'int' [-Wformat]
        printf("Cos of %d == %g\n", i, Cos(i));
                             ~~        ^~~~~~
                             %d
2 warnings generated.

You see, it treates it as a function accepting anything, and returning int (the warning checks that the format specifier corresponds to the type of the parameter passed, because printf() is also specified as an undetermined parameter list function.

Now, when we run it, we get:

freebsd@rpi-xterm:/tmp $ ./test_Cos 
Cos of 1 == 1

which is clearly incorrect.

If we now put the missing line in main.c:

#include "Cos.h"

to provide a correct declaration of the function, we get, on compiling:

freebsd@rpi-xterm:/tmp $ make
cc  -O2 -pipe -c main.c -o main.o
cc   -o test_Cos main.o Cos.o -lm 

(no errors, no warnings) and later, on running:

freebsd@rpi-xterm:/tmp $ ./test_Cos
Cos of 1 == 0.540302

(the correct result)

Now the compiler knows beforehand that the Cos() function returns a double (this eliminates de warning on the printf() format correspondence between the format specifier and the actual parameter passed ---which now is a double---, and the int i parameter is converted to a double before being passed to Cos().