Re: Question about multiple files

From: Karl Heinz Buchegger (kbuchegg_at_gascad.at)
Date: 07/14/04


Date: Wed, 14 Jul 2004 10:23:32 +0200

Marty wrote:
>
> Hi guys,
>
> Just a quick one about writing code spanning multiple files.
>
> Say I have a few files defining classes or global functions. When I want to
> use one in the other, I include its header file to get the declarations
> available. This I can understand, but say I declare with extern. Does this
> mean I dont have to include a header file of where the function is declared?

No. extern just means: The actual definition is somewhere else. This is what
the whole thing looks like, but it is defined somewhere else.
You need extern for global variables. On function declarations you can
use extern too, but usually it is not done. This is because for functions
the compiler can differentiate between declaration and definition just
by the syntax

   void foo(); // this is a declaration

   void foo()
   {
   } // this is a definition

but with global variables thats no longer the case

   int i; // this is a definition, but what would
                 // a declaration look like ? ...

   extern int i; // ... like this: just precede it with 'extern', discard
                 // all initializations and it becomes a declaration.

So why is that important?
Because there is the so called 'One Definition Rule' (ODR). It says:
for the very same item in a whole program, you can have as many declarations
as you want (as long as all of them match), but you can have only one definition.

>
> When I link all the object files together, will the function resolve to any
> declaration it finds and in this way can I write code expecting it to be
> there in the future. Say by blocking with a #ifdef maybe.

I'm not sure I understand what you are trying to ask in this paragraph.

So just guessing your question right now:
It works like this. Say you write a program which consists of 2 files
containing functions:

   main.c foo.c
  +----------------------------+ +------------------------+
  | | | #include <stdio.h> |
  | int main() | | |
  | { | | void foo() |
  | foo(); | | { |
  | } | | printf( "Hello\n" ); |
  | | | } |
  +----------------------------+ +------------------------+

You try to compile both of those files in order to get object code files
for each of them, which in turn will be linked together (plus adding the
runtime library) to form the complete program. The important thing is:
when you compile main.c the compiler knows nothing about foo.c and
vice versa. Each file is compiled independently of all others.

But there is a problem when compiling main.c
When the compiler reaches the line foo(); it will emit an error. Why? Because
it never heard about something called foo. It could be a typing error or a
non existent function or a variable misused or ... In any case there is
something wrong and the compiler will barf.
So what to do about it?
Well. Simple, just tell the compiler that there is indeed somewhere a thing
called foo. That it is a function and what its return type and the data types
of the arguments are. You could do this eg this way:

   main.c foo.c
  +----------------------------+ +------------------------+
  | void foo( void ); | | #include <stdio.h> |
  | int main() | | |
  | { | | void foo() |
  | foo(); | | { |
  | } | | printf( "Hello\n" ); |
  | | | } |
  +----------------------------+ +------------------------+

Now main.c is compilable and you can create a program.
Say you continue working on this program and introduce a second function
foo2():

   main.c foo.c
  +----------------------------+ +------------------------+
  | void foo( void ); | | #include <stdio.h> |
  | foid foo2( void ); | | |
  | int main() | | void foo() |
  | { | | { |
  | foo(); | | printf( "Hello\n" ); |
  | foo2(); | | } |
  | } | +------------------------+
  +----------------------------+

                                             foo2.c
                                             +------------------------+
                                             | void foo(); |
                                             | |
                                             | void foo2() |
                                             | { |
                                             | foo(); |
                                             | } |
                                             +------------------------+

Compile all 3 files, link them and run the executable. All is well.
*BUT*
During your development you reach a point where you have to change the function
foo(). It now has to take an argument, lets say an int. So you start changing
things. But since the program got to big you make a mistake and miss one
occourence of foo. The whole thing looks like this:

   main.c foo.c
  +----------------------------+ +------------------------+
  | void foo( int ); | | #include <stdio.h> |
  | foid foo2( void ); | | |
  | int main() | | void foo( int a ) |
  | { | | { |
  | foo( 2 ); | | printf( "Hello\n" ); |
  | foo2(); | | printf( "%d\n", a ); |
  | } | | } |
  +----------------------------+ +------------------------+

                                             foo2.c
                                             +------------------------+
                                             | void foo(); |
                                             | |
                                             | void foo2() |
                                             | { |
                                             | foo(); |
                                             | } |
                                             +------------------------+

See the problem?
I changed the declaration of foo in main.c. But i missed the one in foo2.c
Important: When the compiler compiles foo2.c it knows nothing about foo.c
or about the correct declaration of foo(). The compiler trusts you!
So the compiler will misses that glitch too! It will happily compile foo2.c,
the linker misses it too (since this is C and not C++) and builds an executable
which, well, anything may happen: the program crashes, it outputs a weird number,
it reboots your system, ....

So there must be a better way then including the declaration in each translation
unit. And this solution is: a header file.

   main.c foo.c
  +----------------------------+ +------------------------+
  | #include "foo.h" | | #include <stdio.h> |
  | foid foo2( void ); | | |
  | int main() | | void foo( int a ) |
  | { | | { |
  | foo( 2 ); | | printf( "Hello\n" ); |
  | foo2(); | | printf( "%d\n", a ); |
  | } | | } |
  +----------------------------+ +------------------------+

                                             foo2.c
                                             +------------------------+
                                             | #include "foo.h" |
                                             | |
                                             | void foo2() |
                                             | { |
                                             | foo(); |
                                             | } |
                                             +------------------------+

                      foo.h
                      +------------------+
                      | void foo( int ); |
                      | |
                      +------------------+

So why is that better?
Because now the declaration is physically at exactly one place! When you
need to do some changes in the interface of function foo(), you have to edit
exactly 2 files: the implementation in foo.c and the declaration in foo.h
All other files, since they include foo.h, will get this updated declaration
automatically. That eg. means that the compiler now will detect the error
in foo2.c and will tell me that the call to function foo() is in error, since
foo() expects an int, which is not provided.

But also note. In the whole program, that is files main.c, foo.c,
foo2.c there are multiple declarations of function foo(). When main.c is compiled
it sees a declaration of foo(), when foo2.c is compiled it sees a declaration
of function foo(). No problem with that, all of them match. They have to match,
since all the declarations come from including file foo.h. But there is only
one definition (implementation): the one in foo.c. So from the language point
of view, all is well.

-- 
Karl Heinz Buchegger
kbuchegg@gascad.at


Relevant Pages

  • Re: keyword extern
    ... >> definition or declaration until the end of the translation unit being ... >> One never needs to use the extern keyword with a function definition, ... >> ...then here is how a C compiler understands them. ... If any code in the source file access 'x', ...
    (comp.lang.c)
  • Re: a compiler bug?
    ... The offending code is the declaration of function foo(). ... end module tokenize ... G95 is a good, free, portable compiler, and it is a good idea to run it ...
    (comp.lang.fortran)
  • Re: a compiler bug?
    ... The offending code is the declaration of function foo(). ... end module tokenize ... G95 is a good, free, portable compiler, and it is a good idea to run it ...
    (comp.lang.fortran)
  • Re: Question about 6.2.4 of C99
    ... compilers to reuse memory in some cases, ... muddying up the semantics and/or making the programmer work harder. ... than it would be if the declaration created and really ... I understand why the VLA rules are the way they are. ...
    (comp.std.c)
  • Re: Structure
    ... I am having Linux platform.I am declaring the array of ... Using " extern ",to extern the structure but when i ... Unless you have a C99 compiler, you need a return statement here. ... You could repeat the declaration of the type from main.c here. ...
    (comp.lang.c)