JKop said:
And then from there it's made into an object file. (BTW, are they just
simply called an object file?). So now we have "boo.o". So now what exactly
is "boo.o"? Is the file platform specific, or compiler specific,
Both.
The file contains the machine code which represents the function.
or is it
totally universal and can it be used with any project in any environment
(eg. MSVisual C++) and with any compiler?
Usually: no
In special cases: yes
But this is dependent on your compiler vendor.
So the file "boo.o" just contains the function "Hello". Now some other
".cpp" file in the same project wants to use "Hello". Let's call this file
"ret.cpp". "Ret.cpp" looks like this:
#include "boo.h"
int main(void);
int main(void)
{
Hello('r');
return 0;
}
So "ret.cpp" is turned into "ret.o" (Is the correct word here "build"?)
We say: it is compiled to ret.o
So
at this stage, "ret.o" still knows nothing about where the "Hello"
function's source code actually is.
right.
So now Mr Linker comes in, takes
"ret.o" and "boo.o" and introduces them. From there, they're compiled into a
".exe".
Yes.
So again, back to libraries, can I just rename "boo.o" to "boo.lib"
Usually: no.
You use another program, a librarian, to collect multiple *.o files
and create a library from them.
and
start e-mailing it to all buddies for them to use in their C++ projects, eg.
making a game for the PS2, the interface for a microwave, WAP on mobile
phone?
You can do the very same with the original boo.o file. Your buddies
can link it into their programs and use the functionality you provide.
Libraries come into play, if you don't have just one single .0 file.
E.g. I have a solid modeller. This solid modeller resides in ~35 *.ccp
files. I can compile them to *.o files and when I need that modeller
in one project I can include all of them into the project. I think you
recognize the problem: In every single project I have to include those
35 files. I must not forget one of them or else the linker will not be able
to link in the functions in it. On the other hand if I add a 36. file
to that modeller, I have to update all of the using projects with that
additional file. So what can I do? I can create a library. This library
contains all 35 files, and I just have to specify the library to the linker
to enable it to link against my modeller. No longer 35 files, instead
just one file - the library. And just in case that the modeller needs
a 36. file, I simply compile it and put it also in the library. The
projects using that library don't need to be updated, since they link
against the library, and the library contains everything needed for
linking in the modeller.
---
So at heart, is a C++ program just a load of ".cpp" files?
Then these ".cpp" files get a load of "extern" statements via header files.
Then each ".cpp" file is built seperately into an "object file", and then
the linker links the object files together and analyzes all the "extern"
statements to share stuff between the files.
Basically: correct.
Maybe the following is of some use to you. It turns around the
question of: "Why header files", but is also a short introduction of
how the process of compilation and linking works:
*******************************************************************************************
First of all let me introduce a few terms and clearify
their meaning:
source code file The files which contains C or C++
code in the form of functions and/or
class definitions
header file Another form of source file. Header files
usually are used to seperate the 'interface'
description from the actual implementation
which resides in the source code files.
object code file The result of feeding a source code file through
the compiler. Object code files already contain
machine code, the one and only language your computer
understands. Nevertheless object code at this stage
is not executable. One object code file is the direct
translation of one source code file und thus usually
lacks external references, eg. the actual implementation
of functions which are defined in other source code files.
library file a collection of object code files. It happens frequently that
a set of object code files is always used together. Instead
of always listing all those object code files during the
link process it is often possible to build a library from
them and use the library instead. But there is no magic
with a library. A library can be seen as some repository
where one can deposit object code files such that the library
forms a collection of them.
compiling the process of transforming the source code files into
object code file. C and C++ define the concept of 'translation
unit'. Each translation unit (normally: one single source code
file) is translated independently of all other translation units.
linking the process of combining multiple object code files and libraries
into an executable. During the linking process all external references
of one object code file are examined and the linker tries to find
modules which satisfy those external references.
In practice the whole process works as follows:
Say you have 2 source files (with errors, we will return to them later)
main.c
******
int main()
{
foo();
}
test.c
******
void foo()
{
printf( "test\n" );
}
and you want to create an executable. The steps are
as in the graphics:
main.c test.c
+----------------+ +-----------------------+
| | | |
| int main() | | void foo() |
| { | | { |
| foo(); | | printf( "test\n" ); |
| } | | } |
+----------------+ +-----------------------+
| |
| |
v v
********** **********
* Compiler * * Compiler *
********** **********
| |
| |
| |
main.obj v test.obj v
+--------------+ +--------------+
| machine code | | machine code |
+--------------+ +--------------+
| |
| |
+------------------+ +--------------------+
| |
v v
************* Standard Library
* Linker *<----------+--------------------+
************* | eg. implementation |
| | of printf or the |
| | math functions |
| | |
| +--------------------+
main.exe v
+-------------------------+
| Executable which can |
| be run on a particluar |
| operating system |
+-------------------------+
So the steps are: compile each translation unit (each source file) independently
and then link the resulting object code files to form the executable. To do that
misssing functions (like printf or sqrt) are added by linking in a prebuilt library
which contains the object modules for them.
The important part is:
Each translation unit is compiled independently! So when the compiler compiles
test.c it has no knowledge about what happend in main.c and vice versa. When the
compiler tries to compile main.c it eventually reaches the line
foo();
where main.c tries to call function foo(). But the compiler has never heared about
a function foo! Even if you have compiled test.c prior to it, when main.c is
compiled this knowledge is already lost. Thus you have to inform the compiler
thar foo() is not a typing error and that there indeed is somewhere a function
called foo. You do this with an function prototype:
main.c
+----------------+
| void foo(); |
| |
| int main() |
| { |
| foo(); |
| } |
+----------------+
|
|
v
**********
* Compiler *
**********
|
Now the compiler knows about this function and can do its job. In very much the same way
the compiler has never heared about a function called printf(). printf is not part of
the 'core' language. In a conforming C implementation it has to exist somewhere, but
printf() is not on the same level as 'int' is. The compiler knows about 'int' and
what it means, but printf is just a function call and the compiler has to know its
parameters and return type in order to compile a call to it. Thus you have to inform
the compiler of its existence. You could do this in very much the same way as you
did it in main.c, by writing a prototype. But since this is needed so often and
there are so many other functions available, this very fast gets boring and error prone.
Thus somebody else has already provided all those protoypes in a seperate file, called
a header file, and instead of writing the protoypes by yourself, you simply 'pull in'
this header file and have them all available:
test.c
+-----------------------+
| #include <stdio.h> |<-+
| | |
| void foo() | |
| { | |
| printf( "test\n" ); | |
| } | |
+-----------------------+ |
| |
| |
v |
********** stdio.h v
* Compiler * +-------------------------------------+
********** | ... |
| | int printf( const char* fmt, ... ); |
| ... |
+-------------------------------------+
And now the compiler has everything it needs to know to compile test.c
Since main.c and test.c could have been compiled successfully they can be linked
to the final executable which can be run. During the process of linking the linked
figures out that there is a call to foo() in main.obj. Thus the linker tries to find
a function called foo. It finds this function by searching through the object
module test.obj. The linker thus inserts the correct memory address for foo
into main.obj and also includes foo from test.obj into the final executable. But
in doing so, the linker also figures out, that in function foo() there is a call
to printf. The linker thus searches for a function printf. It finds it in the
standard library, which is always searched when linking a C program. There the
linker finds a function printf and this function thus is included into the
final executable too. printf() by itself may use other functions to do its
work but the linker will find all of them in the standard library and include
them into the final executable.
There is one thing left to talk about. While main.c is correct from a technical
point of view it is still unsatisfying. Imagine that our functoni foo() has
a much more complicated argument list. Also imagine that your program does not
consist of just those 2 translation units but instead has 100-dreds of them and
that foo() needs to be called in 87 of them. Thus you would have write a prototype
in every single one of them. I think I don't have to tell you what that means: All those
prototypes need to be correct and just in case function foo() changes (things like
that happen), all those 87 prototypes need to be updated. So how can you do that?
You already know the solution, you have used it already. You do pretty much
the same as you did in the case of stdio.h. You write a header file and
include this instead of the prototype:
main.c
+-------------------+ test.h
| #include "test.h" |<---------+-------------+
| | | void foo(); |
| int main() | | |
| { | +-------------+
| foo(); |
| } |
+-------------------+
|
|
v
**********
* Compiler *
**********
|
Now you can include that header file in all the 87 translation units which
need to know about foo(). And if the prototype for foo() needs some update
you do it in one central place: by editing file test.h. All 87 translation
units will pull in this updated protype when they are recompiled.