An object may contain the code for one or more functions depending on how the
source code was organized. Usually, an object contains the compiled code from
a single source file, along with the #included
files, but can also
contain code from other files as we shall see. Each such grouping of sources
forms a module, and each module is compiled to a single
object. These terms are often used interchangeably.
In this section, we'll take a look at some of the ways sources can be organized, and some of the effects of this organization on the objects created.
#include
int a_global;
int func ( int a, int b )
{
return (a+b);
}
void main (void)
{
int a_local = 7;
a_global = 5;
printf ( "The result is %d\n", func(a_local, a_global);
}
If we were to compile mymain.c, we would normally wind up with
a single executable file containing compiled code from several places:
It is this last point that makes things interesting. Because the entire module gets copied into the executable when any entity in it is used, our executables can become unreasonably large. Let's move on to another example. Suppose we have a file called one2ten.c containing the following code:
int one(void) { return 1; }
int two(void) { return 2; }
.
.
.
int nine(void) { return 9; }
int ten(void) { return 10; }
If we compile this file to an object with cc -c one2ten.c
, the
compiler will create a single object file containing the code for each of these
functions.
Suppose we now write an application called Three.c which calls
the functions three()
:
int main(void)
{
return three();
}
If we compile Three.c into the object Three.o
with cc -c Three.c
, then the code for main()
is
compiled and placed in the object, but the reference to three()
is
left unresolved. In other words, the code for the function
three()
does not get put in the object.
Suppose that we instead compile Three.c into an executable
called Three with cc -o Three Three.c one2ten.o
.
Then, the code for main()
is compiled, the reference to
three()
is resolved, and the code from Three.c is
linked together with the code in the object one2ten.o.
Because this object contains the code for all of the functions in the source
file one2ten.c, the executable will contain code for nine
functions that it will never use.
three()
is contained
in an object with nine similar, but not prerequisite, functions.
three()
does not need any of the other functions in
one2ten.c to operate, but because they are in the same source,
they will all be compiled together.
If we split one2ten.c into smaller pieces, then the final executable will be smaller. Let's divide it into two parts one2five.c:
int one(void) { return 1; }
.
.
.
int five(void) { return 5; }
and six2ten.c:
int six(void) { return 6; }
.
.
.
int ten(void) { return 10; }
Now, we compile each of these to an object using cc -o one2five.c
and cc -o six2ten.c
. This will create the files
one2five.o and six2ten.o. We can now
recompile the application Three.c using the command line
cc -o Three Three.c one2five.o
. Note that we didn't include
six2ten.o because we don't call any of the functions contained
in it.
Now, the executable will contain the code for main()
and for the
functions one(), two(), three(), four(),
and five()
.
Thus, it will be smaller than the previous version which included the rest of
the functions originally found in one2ten.c.
The current version still contains four functions that are never used, so maybe we should break the source file one2ten.c into ten separate pieces, each of which contains one function. This is okay because none of the functions in one2ten.c need to be hidden from the outside world. We won't worry about that for now though.
We now have ten source files, say one.c through
ten.c, which we can compile to ten objects,
one.o through ten.o.
When we link these with our application Three.c, the final
executable will contain the code for main()
and
three()
, but not for any of the other functions that were
originally in one2ten.c. This minimizes the size of the
executable, but it also maximizes the number of files we have laying around
that we have to keep track of. To make this task less painful, the concept of
libraries was developed. We'll look at these in a later
section.