V Lecture 14 — make and gdb
V compilation
* .c file -> preprocessor -> preprocessed C file -> compiler -> .o file -> linker -> executable
V preprocessor handles preprocessor directives like #include and #define
* outputs pure C code (i.e., C code without any preprocessor directives)
* one file at a time
V compiler parses source code and compiles it into binary form (object file)
* one file at a time
* object files can refer to symbols that are not defined
* stage at which syntax and type errors are produced
V linker produces final executable by combining object files
* handles multiple files at once
* replaces references to undefined symbols with correct address
* common errors are missing definitions and duplicate definitions
V make
V one way to think about compiling C programs is in terms of dependencies (i.e., files depend on other files)
* a .o file depends on its .c file and the .h files that .c file includes
* the executable depends on the .o files
* to compile something, its dependencies need to be available
V we can represent dependencies as a directed acyclic graph (DAG)
* a graph is a structure consisting of nodes and edges (a tree is a type of graph)
* directed means the edges have a direction (they are one-way arrows)
V acyclic means there are no cycles (loops) in the graph
V exercise: why would the representation of dependencies need to be acyclic?
* a depends on b depends on c depends on a means nothing could ever be compiled
V exercise: draw the DAG for the following code (include .o files and final executable talk)
speak.c: #include “speak.h”
shout.c: #include “speak.h”
#include “shout.h”
main.c: #include “speak.h”
#include “shout.h”
* talk -> speak.o shout.o main.o
speak.o -> speak.c speak.h
shout.o -> shout.c speak.h shout.h
main.o -> main.c speak.h shout.h
V managing all the dependencies for a large program could get very complicated and time consuming
* write a program to take care of the compilation
* Bash script would work…
V …but what if we don’t want to recompile the entire codebase each time?
V exercise: when do we need to recompile?
* when the compilation result (.o file, executable) is older than the corresponding source files
* could be pretty tricky to do in Bash
V answer: make
V a Unix program with its own specialized scripting language
* consists of rules of the form
target … : prerequisites …
recipe

* target … is a list of the outputs
* prerequisites … is a list of the dependencies
* recipe … is a series of shell commands to run when a target doesn’t exist or a target is older than a prerequisite
* if any prerequisites appear as targets in other rules, those rules are checked first
V make takes in a program in this language
* running make by itself will look for a file named makefile or Makefile
V the first rule listed in the file is checked
* rules for prerequisites of the first rules are checked, and so on down the chain of prerequisites
V let’s write a makefile for out linked list program
* see posted file
V gdb
* no debugging symbols found means you forget to compile with -g
* help COMMAND to get information about that command
V break FILENAME:LINE_NUMBER to set a breakpoint
* gdb will pause program execution whenever it reaches this line
* break FUNCTION_NAME to pause whenever that function is called
* info locals to see all local variables
* info args to see all arguments to current function
V step to move execution on step forward
* next line or into function call
* entering a blank line will repeat the previous command
* finish to return from the current function
* continue to resume execution
* display EXPR to print EXPR after each command
* x EXPR examine memory at address EXPR