CS 361 Lecture-Module #14:
Managing Modules using |make|



Plan for this module:
introduce modularity, project management of C programs in UNIX/LINUX

|make|
UNIX/LINUX utility for managing projects
        code in multiple files
advantage of separate modules:
if change code of one module, recompile 
    and then complete compilation,  with other modules
versus needing to recompile entire (potentially MLOC) program if change only one little element
    prog.c
    ------
     ....
     ....
     ....
     ....  <-- say change one part of this line
     ....
     ....
     ....
     ....
     ....
     ....
     ....
     ....
     ....
     ....
     ....
     ....
e.g. suppose have factorial function in file  factorial.c  separate from  main()  as follows:

/* |main.c|: main file for program demonstrating |factorial()| of prog.-arg. */
 
#include <stdio.h>
#include <stdlib.h>

int factorial(int);

int
main(int argc, char * argv[])
{
  int  n;

  if( argc != 2 ) {
    fprintf(stderr, "%s requires one integer argument\n", argv[0]);
    return  EXIT_FAILURE;
    }
  n = atoi(argv[1]);
  printf("factorial(%i) == %i\n", n, factorial(n));

  return  EXIT_SUCCESS;
}

/* |factorial.c|: module providing |factorial()| function
 * calculating the factorial |n!| for the given integer |n|
 */
 
int factorial(int);
    /* include for consistency: if change function to return |double|
     * to handle larger numbers (or another time change back to |int|s
     * for precision and efficiency) and have this declaration, compiler
     * catches immediately.  Otherwise error not caught until linking.
     */

int
factorial(int n)
{
  int  result = 1;
  for ( int i = 1; i < n; i++ )
    result *= i;
  return  result;
}


could compile all together:
$ 
but ... as above
always compiling all together same as having everything back together in one file
better: prepare  to organize the compilation
$ ls 
Makefile       factorial.c    main.c
$ 
command to use Makefile is
$ make
no argument -- not even "Makefile"
$ 
gcc -std=c99 -Wall -g -c factorial.c
gcc -std=c99 -Wall -g -c main.c
gcc main.o factorial.o -o factorial
$ ls
Makefile       factorial.c    main.c
factorial      factorial.o    main.o
$ 
$ factorial 5
factorial(5) == 120
$ 
if change  factorial()  from using  for  to while  or recursion or ...
want to recompile just  factorial.c
don't bother recompiling main.c  (or in general further .c-files of overall project)
    which is what would happen with    gcc -std=c99 -Wall -g *.c
so suppose do change loop in  factorial.c  as follows:
  while ( n > 1 )
    result *= n--;
then:
$ 
gcc -std=c99 -Wall -g -c factorial.c
gcc main.o factorial.o -o factorial
$ factorial 5
factorial(5) == 120
$ 
putting compilation-commands in Makefile also facilitates avoiding errors typing compilation-command
e.g.:
$ gcc -std=c99 -Wall -g main.c
Undefined                       first referenced
 symbol                             in file
factorial(int)                      /var/tmp/cc92gL8D.o
ld: fatal: Symbol referencing errors. No output written to a.out
collect2: ld returned 1 exit status
$ 
or:
$ gcc -std=c99 -Wall -g factorial.c
Undefined                       first referenced
 symbol                             in file
main                                /fs/net/pkg/GNU/gcc/gcc-2.95.2/i386-SunOS5.7/lib/gcc-lib/i386-pc-solaris2.7/2.95.2/crt1.o
ld: fatal: Symbol referencing errors. No output written to a.out
collect2: ld returned 1 exit status
$ 

contents of Makefile:
: "" to end of line
plus 
* dependency lines:
    * flush against left margin
    * name of  file to be made, then "",
        then names of  separated by ""
* command: in line starting with (!)
    incidentally, "ctrl-T" in  vi  doesn't (necessarily) produce tab-character
    need to really type i.e. press specifically tab-key
separating rules
e.g. here:

# basic |Makefile| for program demonstrating |factorial()| of prog.-arg.

factorial :  main.o factorial.o
        gcc main.o factorial.o -o factorial
 

        gcc -std=c99 -Wall -g -c main.c
 
factorial.o :  factorial.c
        gcc -std=c99 -Wall -g -c factorial.c


that  Makefile  should be clear
but producers of  make  included facilities for abbreviating
I don't have time to thoroughly present
but here's an example:

# |makefile| for program demonstrating |factorial()| of prog.-arg.
# taking advantage of |make|-macros i.e. abbreviations and defaults
# re suffixes, 'Automatic Variables'

OBJS = main.o factorial.o

CC = gcc
CFLAGS = -std=c99 -Wall -g


factorial :  $(OBJS)
        $(CC) $^ -o $@

# that's all that's needed!

see documentation


standard: each project in its own directory




header-files

standard practice:
each  aux.c file defines 
put  of those functions, classes in file aux
then  main.c  has:
aux.h
#include  file-name delimiters    <...>  for standard lib.,
"..."    for material that you create
e.g.:

/* |factorial.h|: header-file for module providing |factorial()| function */
 
int factorial(int);

/* |main.c|: main file for program demonstrating |factorial()| of prog.-arg. */
 
#include <stdio.h>
#include <stdlib.h>



int
main(int argc, char * argv[])
{
  ...
}

/* |factorial.c|: [...] */
 
#include "factorial.h"
    /* include for consistency: if change function to return |double|
     * to handle larger numbers (or another time change back to |int|s for
     * precision and efficiency) and have this |#include|, compiler catches
     * immediately.  Otherwise error not caught until linking.
     */

int
factorial(int n)
{
  ...
}


program's dependencies on  aux.h  should be added to Makefile
e.g.:

# basic |Makefile| for program demonstrating |factorial()| of prog.-arg.

factorial :  main.o factorial.o
        gcc main.o factorial.o -o factorial
 
main.o :  main.c factorial.h
        gcc -std=c99 -Wall -g -c main.c
 
factorial.o :  factorial.c factorial.h
        gcc -std=c99 -Wall -g -c factorial.c


or:

# |Makefile| for program demonstrating |factorial()| of prog.-arg.
# using |make|-macros i.e. abbreviations and defaults re suffixes,
# 'Automatic Variables'

OBJS = main.o factorial.o

CC = gcc
CFLAGS = -std=c99 -Wall -g


factorial:  $(OBJS)
        $(CC) $^ -o $@

main.o: factorial.h

factorial.o: factorial.h


gcc -MM    will write the dependency-lines for you
gcc -MM    will produce for you the lines:
"main.o: ..." and "factorial.o: ..."
$ gcc -MM factorial.c
factorial.o: factorial.c factorial.h

Copy into  Makefile
(pair of lines
    factorial.o: factorial.c
    factorial.o: factorial.h
    equivalent to single line
    factorial.o: factorial.c factorial.h
    if they're not separated with a blank line
    )




a point related to these topics is that
you may sometimes see
code divided into separate  .c  files and then
#include $aux$.c
because equivalent to having code all in one file
i.e. only  modular
so don't do that




need  make  for Java?







summary of this lecture-module

modularization,  make, writing  Makefile


(Copyright © 2008 by Hugh McGuire   — for thoughts about this, see   http://www.cis.gvsu.edu/~mcguire/teaching/copyright_thoughts.html .)