For example, what is the difference between linking two files
gcc -c func.c
gcc -c main.c
gcc func.o main.o -o main
and using a header file
#include func.h
int main{
....
..
}
if they seem to accomplish the same thing?
For example, what is the difference between linking two files
gcc -c func.c
gcc -c main.c
gcc func.o main.o -o main
and using a header file
#include func.h
int main{
....
..
}
if they seem to accomplish the same thing?
They don't "accomplish the same thing". They accomplish complementary parts of the same thing. Typically you have to do both: doing just one, or just the other, is totally insufficient.
Here's an imperfect analogy: Suppose you want an addition on your house. At the start of the project, you sign your name on a contract with the builder, specifying the size and finish of the addition. At the end of the project, you sign your name on a check to pay the builder for his work. What's the difference between signing the contract and signing the check? Why do you have to do both, if they both do the same thing, namely getting you your new addition?
Including a header is (sort of) like signing a contract. When you say #include <math.h>
, you're saying "Hey, compiler, there are some math functions. Like sqrt()
, which accepts a double
and returns a double
. So if I write int x = sqrt(144)
, make sure you do a conversion from int
to double
before passing the number to sqrt
, and make sure you do a conversion from double
back to int
before assigning the result to x
."
Linking against a library is (sort of) like paying your bill. (Actually, if I'm honest, it's almost nothing like paying a bill, but bear with me.) When you put -lm
at the end of the command line, you're saying "Hey, compiler, those math functions we agreed on, the ones whose details are specified in the contract called <math.h>
, the ones I've been calling, now I'm paying up, here's where those functions actually are."
#include
makes all the source code in the included file available to the compiler, effectively including it as part of the file being compiled. This is commonly used to declare names that are defined in other files. That tells the compiler what types of the named objects and functions are, and the compiler can use this to generate code that uses those objects and functions. But the code does not actually contain definitions for those objects and functions.
When files are linked together, the link takes objects and functions from multiple files and builds them together into one executable program (or into a library or object module for further linking later). While putting these objects and functions together, the linker resolves references to objects and functions.
Suppose the main
function file main.c calls function foo
in file function.c. The header function.h will declare foo
, which tells the compiler what arguments the function requires and what type of value it returns. When compiling main.c, the compiler will produce an object module main.o that contains a call to foo
. Inside the object file, foo
is represented only by its name. When compiling function.c, the compiler will produce an object module function.o that contains the actual function foo
.
When the linker builds the executable, it will decide where the function foo
will be placed inside the program. Then, it will go into the code for main
where the call instruction is and modify the address or offset used in the call instruction so that it points to the foo
function. Then, in the executable program file, the call to foo
contains only an address or offset; it is no longer referred to by name. The different calls and definitions in the program have been linked together.
Including headers is used to tell the compiler about types of objects and functions. The objects and functions remain in separate files, each in the object file corresponding to the source file in which it is defined.
Linking is used to combine the different object modules into one executable program file.