Makefiles

Below is a simple makefile:
foo: foo.cpp
	g++ -o foo foo.cpp

debugfoo: foo.cpp
	g++ -g -o debugfoo foo.cpp
Tasks:
  1. Explain what makefiles and the make command are for.
    Makefiles automate repetitive tasks, such as
    compiling C++ source files. Makefiles are
    collections of rules, dependences and actions.
    The user activates a rule, which activates other
    necessary rules (depending on the dependences)
    until a particular set of actions is performed.
    
  2. Given the simple makefile above, explain what would happen during:
    make
    	activates the first rule: foo
    
    	if foo.cpp has changed
    	then it is re-compiled into foo
    
    make foo
    	activates the given rule: foo
    
    	if foo.cpp has changed
    	then it is re-compiled into foo
    
    make debugfoo
    	activates the given rule: debugfoo
    
    	if debugfoo.cpp has changed
    	then it is re-compiled into foo
    
  3. Create a makefile for the square program and the file IO program you created from Week 4 Tutorial Tasks (see the process and resource control and program IO tutorial questions).

    Include four rules:

    1. the standard make option (for each program)
    2. a debugging make option as shown in the example above,
    3. a rule to compile all programs (normal and debug versions), and
    4. a rule that allows you to clean up the current directory (delete the executable files).

     

    Note: make sure you use the g++ compiler.

    all: square fileio squaregdb fileiogdb
    
    square: square.cpp
            g++ -lm square.cpp -o square
    
    squaregdb: square.cpp
            g++ -g -lm square.cpp -o squaregdb
    
    fileio: fileio.cpp
            g++ fileio.cpp -o fileio
    
    fileiogdb: fileio.cpp
            g++ -g fileio.cpp -o fileiogdb
    
    clean:
            rm square fileio squaregdb fileiogdb
    

Debuggers

This section of the tutorial is based on the square program. You will need to compile the square program for debugging. Tasks:
  1. Explain why debuggers are necessary.
    Even after getting a program to compile
    successfully, it is fairly rare to have
    a large or medium-sized program run perfectly
    the first time it is executed.  Usually the
    programmer must spend some further time
    running and debugging the code.
    
  2. Explain how the gdb debugger helps you debug a program.
    It allow us to run a program part way, then
    interrupt it and check the value of certain
    variables, possibly watching what happens to
    the value of those variables over the next
    several instructions, etc.  This will help
    reveal what is going wrong with the program.
    
  3. Given the makefile you created earlier in this tutorial (for the square and file IO programs):
    1. issue the command which compiles the square program for debugging
    2. start the debugger using gdb
    3. load the square program (debugging version)
    4. set the arguments to be "1 2 3 4 5"
    5. set a breakpoint at the main function
    6. list the contents of the program source and set a breakpoint inside the loop over the elements of argv
    7. run the program
    8. when the breakpoint is activated, step through the code one source line at a time (output statements using cout can't be stepped through, you have to use the continue command)
    9. print the values of argc, and argv
    10. print the values of the important program variables
    11. continue stepping through the program until it terminates normally
    12. experiment by running the program with no arguments

      Note: You can get online help from inside gdb by entering the command help from the (gdb) prompt.

% gdb
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.16 (alpha-dec-osf4.0), Copyright 1996 Free Software Foundation, Inc.
(gdb) file squaregdb
Reading symbols from squaregdb...done.
(gdb) set args 1 2 3 4 5
(gdb) break main
Breakpoint 1 at 0x12000a358: file square.cpp, line 5.
(gdb) list
1	#include 

2	   #include 

3	   #include 

4	   void main(int argc, char* argv[]) {
5	     if (argc == 1)
6	       cout << "No args. Terminating." << endl;
7	     else {
8	       for (int index=1; index < argc; index++) {
9		  cout << "arg[" << index << "]: square = "
10		  << pow(atoi(argv[index]),2) << endl;
(gdb) list
11	       }
12	     }
13	   }
14
(gdb) break 9
Breakpoint 2 at 0x12000a3c0: file square.cpp, line 9.
(gdb) r
Starting program: /postgrad/postgrad/59502/sci-jjh/cp1300/w7/squaregdb 1 2 3 4 5

Breakpoint 1, main (argc=6, argv=0x11ffffb08) at square.cpp:5
5	     if (argc == 1)
(gdb) print argc
$1 = 6
(gdb) print argv [0]
$2 = 0x11ffffc48 "/postgrad/postgrad/59502/sci-jjh/cp1300/w7/squaregdb"
(gdb) s
8	       for (int index=1; index < argc; index++) {
(gdb) print index
$3 = 1
(gdb) s
10		  << pow(atoi(argv[index]),2) << endl;
(gdb) s
ostream::operator<< (this=0x140000038, s=0x1400007d0 "arg[") at iostream.cc:756
iostream.cc:756: No such file or directory.
(gdb) c
Continuing.
arg[1]: square = 1

Breakpoint 2, main (argc=6, argv=0x11ffffb08) at square.cc:10
10		  << pow(atoi(argv[index]),2) << endl;
(gdb) print index
$4 = 2
(gdb) c
Continuing.
arg[2]: square = 4

Breakpoint 2, main (argc=6, argv=0x11ffffb08) at square.cpp:10
10		  << pow(atoi(argv[index]),2) << endl;
(gdb) print index
$5 = 3
(gdb) c
Continuing.
arg[3]: square = 9

Breakpoint 2, main (argc=6, argv=0x11ffffb08) at square.cpp:10
10		  << pow(atoi(argv[index]),2) << endl;
(gdb) print index
$6 = 4
(gdb) c
Continuing.
arg[4]: square = 16

Breakpoint 2, main (argc=6, argv=0x11ffffb08) at square.cpp:10
10		  << pow(atoi(argv[index]),2) << endl;
(gdb) print index
$7 = 5
(gdb) c
Continuing.
arg[5]: square = 25

Program exited normally.
(gdb) set args
(gdb) show args
Arguments to give program being debugged when it is started is "".
(gdb) r
Starting program: /postgrad/postgrad/59502/sci-jjh/cp1300/w7/squaregdb

Breakpoint 1, main (argc=1, argv=0x11ffffb28) at square.cpp:5
5	     if (argc == 1)
(gdb) print argc
$9 = 1
(gdb) s
6	       cout << "No args. Terminating." << endl;
(gdb) s
ostream::operator<< (this=0x140000038, s=0x1400007b8 "No args. Terminating.")
    at iostream.cc:756
iostream.cc:756: No such file or directory.
(gdb) c
Continuing.
No args. Terminating.

Program exited normally.
(gdb) q

Profiling

Take the following code called "fishy.cpp" and compile it for profiling:

// The three Fishermen problem
/* How many fish overall do three fishermen have if:
  * one leaves and takes his share (3 equal shares with 1 remaining fish)
  * another leaves and takes his share (of the remaining fish, computed as above)
  * the last one leaves and takes his share (of the leftover fish, computed as above)
*/

#include <iostream.h>
#include <stdlib.h>

bool calcShare(int, int&);
int calcFish(int);

int main(int argc, char** argv) {

   int maxValue;
   if (argc == 2 && (maxValue = atoi(argv[1])) != 0) {

      int smallestShare,
          middleShare,
          overallShare;
      for (smallestShare = 1;
           smallestShare < maxValue;
           smallestShare++) {
         cout << "Trying smallest share = " << smallestShare;
         if (calcShare(smallestShare,middleShare)) {
            cout << '.';
            if (calcShare(middleShare,overallShare)) {
               cout << " Overall Number of Fish = "
                    << calcFish(overallShare);
            }
         }
         cout << endl;
      }
   }
   else
      cout << "Usage: fishy <n>" << endl
           << "<n> is largest number to try" << endl;
   return 0;
}

bool calcShare(int smaller, int& larger) {

   larger = calcFish(smaller);

   if (larger % 2 == 0) {
      larger /= 2;
      return true;
   }
   else return false;
}

int calcFish(int smaller) {
   return (3*smaller + 1);
}
 
% g++ -pg fishy.cpp -o fishy
 

Run the executable and generate a profiling report (execute it as: "fishy 1000").

Note that the profiling wont have much time to sample; larger
more complex programs will provide more numeric profile information.
% gprof -b fishy
granularity: each sample hit covers 0 byte(s) no time propagated

                                  called/total       parents
index  %time    self descendents  called+self    name           index
                                  called/total       children

                0.00        0.00     250/1749        main [3]
                0.00        0.00    1499/1749        calcShare__FiRi [2]
[1]      0.0    0.00        0.00    1749         calcFish__Fi [1]

-----------------------------------------------

                0.00        0.00    1499/1499        main [3]
[2]      0.0    0.00        0.00    1499         calcShare__FiRi [2]
                0.00        0.00    1499/1749        calcFish__Fi [1]

-----------------------------------------------

                0.00        0.00       1/1           __start [49]
[3]      0.0    0.00        0.00       1         main [3]
                0.00        0.00    1499/1499        calcShare__FiRi [2]
                0.00        0.00     250/1749        calcFish__Fi [1]
-----------------------------------------------

                0.00        0.00       1/1           __start [49]
[3]      0.0    0.00        0.00       1         main [3]
                0.00        0.00    1499/1499        calcShare__FiRi [2]
                0.00        0.00     250/1749        calcFish__Fi [1]

-----------------------------------------------

^L

granularity: each sample hit covers 0 byte(s) no time accumulated

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
  0.0       0.00     0.00     1749     0.00     0.00  calcFish__Fi [1]
  0.0       0.00     0.00     1499     0.00     0.00  calcShare__FiRi [2]
  0.0       0.00     0.00        1     0.00     0.00  main [3]
^L
Index by function name

   [1] calcFish__Fi          [2] calcShare__FiRi       [3] main
Use "gprof -b fishy | less" for a page-at-atime output, or:
	gprof -b fishy > results.txt
	less results.txt
 

Assembly Code

  1. What command will compile fishy.cpp to assembly code?

g++ -S fishy.cpp

produces fishy.s

  1. (Optional!) Discover command(s) that will assemble the assembly code of fishy.cpp to an object file "fishy.o", and then link and create the final executable "fishy" (Hint: check out the man pages on gcc and g++, what you require is information about what gcc and g++ actually do! Note that in a way, g++ extents the capabilities of gcc... also, when you find out what g++ does, the "mips-tfile" command is optional, and "ld" can be used in place of  "collect2" minus some parameters...)

In "man gcc" there is an option "-v" that lists the stages of compilation and the commands used; this also applies to g++. Using "g++ -v fishy.cpp" will list the compilation commands:

Reading specs from /usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.2/specs
gcc version 2.95.2 19991024 (release)
 /usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.2/cpp -lang-c++ -v -D__GNUC__=
2 -D__GNUG__=2 -D__GNUC_MINOR__=95 -D__cplusplus -Dunix -D__osf__ -D_LONGLONG -D
SYSTYPE_BSD -D_SYSTYPE_BSD -D__unix__ -D__osf__ -D_LONGLONG -D__SYSTYPE_BSD__ -D
_SYSTYPE_BSD -D__unix -D__SYSTYPE_BSD -Asystem(unix) -Asystem(xpg4) -D__EXCEPTIO
NS -D__LANGUAGE_C__ -D__LANGUAGE_C -DLANGUAGE_C -Acpu(alpha) -Amachine(alpha) -D
__alpha -D__alpha__ -D__alpha_ev6__ -Acpu(ev6) -D__alpha_bwx__ -Acpu(bwx) -D__al
pha_max__ -Acpu(max) -D__alpha_fix__ -Acpu(fix) fishy.cpp /tmp/cc6ftETm.ii
GNU CPP version 2.95.2 19991024 (release)
pha_max__ -Acpu(max) -D__alpha_fix__ -Acpu(fix) fishy.cpp /tmp/cc6ftETm.ii
GNU CPP version 2.95.2 19991024 (release)
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.2/../../../../include/g++-3
 /usr/local/include
 /usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.2/../../../../alphaev6-dec-osf
4.0f/include
 /usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.2/include
 /usr/include
End of search list.
The following default directories have been omitted from the search path:
End of omitted list.
GNU C++ version 2.95.2 19991024 (release) (alphaev6-dec-osf4.0f) compiled by GNU
 C version 2.95.2 19991024 (release).ase) (alphaev6-dec-osf4.0f) compiled by GNU
 as -g -nocpp -O0 -o /tmp/ccpdZIhN.o /tmp/ccATKnrO.s
 /usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.2/mips-tfile -v -o /tmp/ccpdZI
hN.o /tmp/ccATKnrO.s
mips-tfile version 2.95.2 19991024 (release)
 /usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.2/collect2 -G 8 -O1 -call_shar
ed /usr/lib/cmplrs/cc/crt0.o -L/usr/local/lib/gcc-lib/alphaev6-dec-osf4.0f/2.95.
2 -L/usr/lib/cmplrs/cc -L/usr/local/lib /tmp/ccpdZIhN.o -lstdc++ -lm -lgcc -lc -
lgcc

g++ uses a series of temporary files in /tmp for the intermediate assembly code, object code, etc.

We can use the "as" command as above to compile "fishy.s" into the object file "fishy.o"
(include all the same parameters...)

There is no need to apply the  the "mips-tfile" command (this is optional, it setups debugging info)

Then use ld  (which, is the same sort of thing as the "collect2" linker command) to produce the final
executable!

Something like (long lines are wrapped around):

% g++ -S fishy.cpp
% as -g -nocpp -O0 -o fishy.o fishy.s
% ld -G 8 -call_shared /usr/lib/cmplrs/cc/crt0.o -L/usr/local/lib/gcc-lib/alphae
v6-dec-osf4.0f/2.95.2 -L/usr/lib/cmplrs/cc -L/usr/local/lib fishy.o -lstdc++ -lm
 -lgcc -lc -o fishy
% fishy
Usage: fishy <n>
<n> is largest number to try
%