I'm a relative beginner to C and I need to learn how makefiles work and I'm a bit confused on how the combination of C files work. Say we have a main.c, a foo.c, and a bar.c. How should the code be written so that main.c recognizes functions in the other files? Also, in foo.c and bar.c, is all of the code written in the main function there or do we need to write other functions for what we need them to do? I've read tutorials on how makefiles are written, and it makes sense for the most part, but I'm still a bit confused on the basic logistics of it.
-
1Just a note, not a full-fledged answer: You actually don’t want to have a `main` function in more than one file that gets compiled into the same target, as then you’ll have two functions with the same name. – Jeff Kelley Dec 03 '10 at 20:33
6 Answers
Generally what will happen is you will define your functions for the other files in a header file, which can then be included in main.c. For example, consider these snippets:
main.c:
#include "foo.h"
int main(int argc, char *argv[]) {
do_foo();
return 0;
}
foo.h:
void do_foo();
foo.c:
#include <stdio.h>
#include "foo.h"
void do_foo() {
printf("foo was done\n");
}
What will happen is that main.c will be turned into an object file (main.o), and foo.c will be turned into an object file (foo.o). Then the linker will link these two files together and that is where the do_foo()
function in main.c is 'associated' with the function in foo.o.
Example GCC command: gcc -o myprogram main.c foo.c
Example makefile
myprogam: main.o foo.o
gcc -o myprogram main.o foo.o
main.o: main.c foo.h
gcc -c main.c
foo.o: foo.c foo.h
gcc -c foo.c

- 10,274
- 3
- 35
- 42
-
4Indent the Makefile with real tabs as mentioned in here http://stackoverflow.com/a/9580615/1461060 – gihanchanuka Jan 27 '17 at 16:34
About C/C++ compilation
When you have a set of files, you usually do 2 things:
- compile each source file (.c) to an object file (.o)
- link all object files into an executable.
The source files are independent - you need header files to be able to provide the "information" (declarations) about functions in a given module in order to let any other module use them. Header files are not compiled by themselves - they are #include
d as parts of source files.
Have a look below on how the commands for that look like and how they are handled by make
.
About makefiles
Makefile is a set of targets and rules to build them. A target is "something which can be built and results in a given file". (There exist also "phony" targets which don't result in a file and just are there to execute commands - a common one is called clean
to remove the compilation results).
Each target has 2 parts:
- list of dependencies, "sensitivity list" (other files and targets which are needed for this target) (after
:
, comma-separated), - list of shell commands which are executed to build this target (below the above, indented)
Consider this example:
main: main.o module1.o module2.o
g++ main.o module1.o module2.o -o main
This means that: "To build the file main
, I need to first make sure that targets main.o
, module1.o
and module2.o
are up to date; then I need to call the following command...".
This can be also rewritten as:
main: main.o module1.o module2.o
gcc $^ -o $@
The variables (everything starting with $
is a variable) will be expanded to the dependencies list and the target name, as you expect.
You can define your own variables and expand them as follows:
OBJS = main.o module1.o module2.o
main: $(OBJS)
# code goes here
You compile individual translation units as follows:
main.o: main.c
gcc -c $< -o $@
# note the -c option, which means: "compile, but don't link"
# $< will expand to the first source file
You can add header dependencies to rebuild main.o when either main.c or any of its headers change:
main.o: main.c module1.h module2.h
gcc -c $< -o $@
In order not to write the same command over and over again, you can define a general rule and just supply the dependencies (if you want):
%.o: %.c
gcc -c $< -o $@
main.o: main.c module1.h module2.h
module1.o: module1.c module1.h module2.h
There's also some magic to generate the dependencies automatically (see link). One of the downsides of using Make is that it doesn't do it by itself (as some building systems do - like SCons which I prefer for C/C++).

- 70,399
- 25
- 169
- 233
In essence a makefile comprises of rules of the form:
<this file> : <needs these files>
<and is created by this command>
You normally have at least one high level target, if any of its dependencies do not exist, make looks for a rule that has that file as a target. It does this recursively until it has resolved all dependencies of the top-level target, before executing the top-level command (if there is one - both dependencies and command are optional fields in a rule)
A make file can have 'default rules' based on patterns, and there are built-in macros for various file matching scenarios as well as user define macros and inclusion of nested makefiles.
I have simplified the above rule form to the most usual case. In fact the command need not create the target at all, it is simply a command to be executed once all the files in the dependency are present. Moreover the target need not be a file either. Often the top level target is a 'dummy' target called "all" or similar.
There are of course many subtleties and nuances to make, all detailed in the manual (GNU make specifically, there are other make utilities).

- 88,407
- 13
- 85
- 165
Functions in foo.c
that need to be called from outside foo.c
should have prototypes in foo.h
. The outside files that need to call those functions should then #include "foo.h"
. foo.c
and bar.c
shouldn't even have a main()
function if they're part of the same program as main.c
.
Makefiles define targets. For simple programs, you can just have a single target that compiles the whole thing. More complex (read: bigger) programs can have intermediate targets (like foo.o) that will let make
avoid needless recompilation. The way make
determines whether or not a given target needs to be recompiled is it looks at the modification times of all the prerequisites (the stuff after the colon) and if any of them come after the last changed time of the target file itself, it gets rebuilt.
Here's a very simple example:
main.c:
#include "foo.h"
int main()
{
fooprint(12);
return 0;
}
foo.c:
#include "stdio.h"
#include "foo.h"
void fooprint(int val)
{
printf("A value: %d\n", val);
}
foo.h:
void fooprint(int val);
Makefile:
main: main.c foo.o
gcc -o main main.c foo.o
foo.o: foo.c
gcc -c foo.c
Then you can run make main
and it will compile foo.c
into foo.o
then compile main.c and link it with foo.o. If you modify main.c
, it'll just recompile main.c
and link it against the already built foo.o
.

- 49,466
- 12
- 107
- 135
-
Should your first sentence read "Functions in foo.c that need to be called from outside foo.c should have prototypes in foo.h", omitting the "don't" and thereby inverting what you actually say? The corollaries are (1) functions that don't need to be called from outside foo.c should be defined as static functions, and (2) in parallel, functions in bar.c that need to be called from outside bar.c should have prototypes in bar.h (and functions inside bar.c that should not be called from outside bar.c should be defined as static functions). – Jonathan Leffler Dec 04 '10 at 16:37
-
@Jonathan: Whoops, yes. I was going to make that point about `static` but decided against it. – nmichaels Dec 04 '10 at 20:41
-
The OP is at least as much asking about program organization as about makefiles; I believe early education on 'static should be default' is a good idea - everything that can be static should be. The other common beginner mistake is 'all declarations used in foo.c should be in foo.h', rather than stating that 'foo.h tells the outside world what they need to know to use the functions that foo.c defines for them to use'. For example, types used only inside foo.c should not be defined in foo.h - yet so often they are. – Jonathan Leffler Dec 04 '10 at 20:45
-
@Jonathan: Agreed. I was just explaining the error. That's certainly worthwhile information. – nmichaels Dec 05 '10 at 03:04
How Makefile works ?
=> When the make command is executed on terminal, it looks for a file named makefile or Makefile in the current directory and constructs a dependency tree.
If you have several Makefiles, then you can execute specific with the command:
make -f MyMakefile
=> Based on make target specified in makefile, make checks if the dependency files of that target exist. And if they exist, whether they are newer than the target itself, by comparing file timestamps.
Here our first and default target is “all” which looks for main.o and function.o file dependencies. Second and third target is main.o and function.o respectively which have dependencies of main.c and function.c respectively.
=> Before executing the commands of corresponding target, its dependencies must be met, when they are not met, the targets of those dependencies are executed before the given make target, to supply the missing dependencies.
=> When a target is a file-name, make compares the time-stamps of the target file and its dependency files. If the dependency file is newer than target file, target execute otherwise not execute.
In our case, when first target “all” start executing it looks for main.o file dependency, if its not met. Then it goes to second target main.o which check for its dependency main.c and compare time-stamp with it. If target found main.c dependency is updated, then target execute else not. Same process is follow for next target function.o.
=> It thus winds up recursively checking all the way down the dependency tree, to the source code files. By this process, make saves time, by executing only commands that need to be executed, based on which of the source files (listed as dependencies) have been updated, and have a newer time-stamp than their target.
=> Now, when a target is not a file-name(which we called “special targets”), make obviously cannot compare time-stamps to check whether the target’s dependencies are newer. Therefore, such a target is always executed.
In our Makefile, special targets are “all” and “clean”. As we discussed target “all” earlier, but we not discuss target clean. Target clean removes the all object files created during compilation and binary executable files according to command.
For the execution of each target, make prints the actions while executing them. Note that each of the command are executed in a separate sub-shell environment because of secure execution so that they can not change current shell environment which may affect other target execution. For example, if one command contains cd newdir, the current directory will be changed only for that line command, for the next line command, the current directory will be unchanged.
Source : - http://www.firmcodes.com/linux/write-first-makefile-c-source-code-linux-tutorial/

- 101
- 1
- 1
Make has little to do with the structure of a C program. All make does is define a dependency tree and execute commands when it finds the dependencies are out of whack. My saying, in a makefile:
foo.exe : foo.c bar.c baz.c
simply sez: foo.exe is dependent on foo.c, bar.c and baz.c. This, sotto vocce, gets expanded, using make's default rule set, to something like:
foo.exe : foo.obj bar.obj baz.obj
foo.obj : foo.c
bar.obj : bar.c
baz.obj : baz.c
Make simply walks the dependency tree starting at its root (in this case, foo.exe). If a target doesn't exist or if one of the objects upon which it depends is newer than the target, the associated commands are executed. to make the dependency correct.
See Managing Projects with Make from O'Reilly for more than you probably want to know.
As far as the second part of your question goes, the answer is just two letters: K and R. Their The C Programming Language is arguably one of the best computer programming books ever written.

- 71,308
- 16
- 93
- 135