Makefile

Though everyone codes in C/C++ in their day-to-day life, not everyone (including me) clearly understands the simplest C compilation technique called Makefile. Hence, I prepared this post which contains the simple makefile to complex makefile concepts and mistakes we do while coding.

make command

Disadvantages before Makefile :

  1. Compilation command needs to be typed every time. 
  2. Recompilation of all files is required even if one file changes. 

1. Instead of running or typing the "gcc <arguments>" or "g++ <arguments>" compilation command every time, we can use the "make" command which has the list of files to be compiled from the Makefile. This eliminates the downfall of typing the command completely every time.

Example 1

main-file.c
#include<stdio.h>
void print_function()
{
    printf("%s %d\n",__func__,__LINE__);
}
int main()
{
    print_function();
}

Makefile
main: main-file.c      
        gcc -o main main-file.c -I.

Here, the first line in the Makefile "main: main-file.c" is a rule of "make" command, so the rule will be executed if the file/any of those files changes. The second line has a command "gcc -o main main-file.c -I.". Now, I executed the make from the command prompt, but it threw the below error.

makefile:2: *** missing separator (did you mean TAB instead of 8 spaces?).  Stop.





There should be tab at the beginning of any command, otherwise "make" command will not recognize.
 This can be debugged by using vi editor command "set list".


I removed the spaces and entered tab (^I) before the command line. In set list mode, the tab will be visible as ^I




Execute "make" command followed by "./main" if make is successful. 



Output:
1
print_function 4

Let's keep enhance the makefile. 

Example 2

We can add multiple .c files of same directory in the Makefile also instead of typing each time.

main-file.c
#include<stdio.h>
void print_function()
{
    printf("%s %d\n",__func__,__LINE__);
}
int main()
{
    print_function();
    file2_function();
}

docs-file2.c
#include<stdio.h>
void file2_function()
{
    printf("%s %d\n",__func__,__LINE__);
}

Makefile
main: main-file.c docs-file2.c
        gcc -o main main-file.c docs-file2.c -I.

When I executed the make main, it threw the following errors and warnings. 

Shirleys-MacBook-Air:make_learning shirley$ make
gcc -o main main-file.c docs-file2.c -I.
main-file.c:9:6: error: implicit declaration of function 'file2_function' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
     file2_function();
     ^
1 error generated.
make: *** [main] Error 1
Shirleys-MacBook-Air:make_learning shirley$ 

I modified the code as follows and then it compiled successfully. 

#include<stdio.h>

void file2_function();

void print_function()
{
    printf("%s %d\n",__func__,__LINE__);
}

int main()
{
    print_function();
     file2_function();
}


Shirleys-MacBook-Air:make_learning shirley$ make main
gcc -o main main-file.c docs-file2.c -I.
Shirleys-MacBook-Air:make_learning shirley$ ./main
print_function 7
file2_function 4
Shirleys-MacBook-Air:make_learning shirley$

Example 3

Let's be a bit more efficient. 

main-file.c
#include<stdio.h>

void file2_function();

void print_function()
{
    printf("%s %d\n",__func__,__LINE__);
}

int main()
{
    print_function();
     file2_function();
}

docs-file2.c
#include<stdio.h>
void file2_function()
{
    printf("%s %d\n",__func__,__LINE__);
}

Makefile
CC=gcc
CFLAGS=-I.

main: main-file.o docs-file2.o
        $(CC) -o main main-file.o docs-file2.o

CC & CFLAGS are special constants, CFLAGS is the list of flags to pass to the compilation command. If .o's are specified in the rule, "make" knows it must first compile the .c files individually and builds the executable "main". 

make output 
1
2
3
gcc -I.   -c -o main-file.o main-file.c
gcc -I.   -c -o docs-file2.o docs-file2.c
gcc -o main main-file.o docs-file2.o



Example 4

If .h file is modified, the .c files will not be recompiled, so "make" should be aware that all .c files depend on the .h files. Rule needs to be created for all .o files which tells the .o files depend on the .c and .h files included in the DEPS macro. In this way, we can eliminate the recompilation of all source files. To learn more about .h files click here.

In the rule "%.o: %.c $(DEPS)", the -c flag says to generate the object file, the -o $@ says to put the output of the compilation in the file named on the left side of the :, the $< is the first item in the dependencies list, and the CFLAGS macro is defined as above.

As a final simplification, let's use the special macros $@ and $^, which are the left and right sides of the :.

main.h
#ifndef _MAIN_H
#define _MAIN_H
void print_function();
#endif

main-file.c
#include<stdio.h>
#include "main.h"
void file2_function();

void print_function()
{
    printf("%s %d\n",__func__,__LINE__);
}

int main()
{
    print_function();
     file2_function();
}


docs-file2.c

#include<stdio.h>
#include "main.h"
void file2_function()
{
    printf("%s %d\n",__func__,__LINE__);
}


Makefile

CC=gcc
CFLAGS=-I.
DEPS=main.h

%.o: %.c $(DEPS)
        $(CC) -c -o $@ $< $(CFLAGS) 

main: main-file.o docs-file2.o
        $(CC) -o main main-file.o docs-file2.o

make output
gcc -c -o main-file.o main-file.c -I.
gcc -c -o docs-file2.o docs-file2.c -I.
gcc -o main main-file.o docs-file2.o

Example 5 

All of the object files can also be included as part of macro OBJ.

main.h
#ifndef _MAIN_H
#define _MAIN_H
void print_function();
#endif

main-file.c
#include<stdio.h>
#include "main.h"
int main()
{
    print_function();
    file2_function();
}
void print_function()
{
    printf("%s %d\n",__func__,__LINE__);
}
docs-file2.c
#include<stdio.h>
#include "main.h"
void file2_function()
{
    printf("%s %d\n",__func__,__LINE__);
}

Makefile
CC=gcc
CFLAGS=-I.
DEPS= main.h
OBJ= main-file.o docs-file2.o

%.o: %.c $(DEPS)
        $(CC) -c -o $@ $< $(CFLAGS) 

main: $(OBJ)
        $(CC) -o $@ $^ $(CFLAGS)


make output 
1
2
3
gcc -c -o main-file.o main-file.c -I.
gcc -c -o docs-file2.o docs-file2.c -I.
gcc -o main main-file.o docs-file2.o

gcc command options 

-I.                          gcc will look the include header files in the current directory.
-o <executable> 
:                             It specifies rule
<tab>                     Command
$@                         left side of :
$^                           right side of :


<to be updated>



References 

https://stackoverflow.com/questions/20514587/text0x20-undefined-reference-to-main-and-undefined-reference-to-function

https://askubuntu.com/questions/1225764/cannot-execute-binary-file-exec-format-error-ubuntu-18-04-x86-64-target

Comments