Review - Makefiles & Command Line Arguments
Thomas J. Kennedy
All example C++ code (e.g., from recorded lectures) makes use of makefiles. You will use makefiles in each C++ assignment. Makefiles are used to perform compilation and linking steps. For C++, this takes the form of:
- Compiling C++ implementation files (e.g.,
.cpp
,.cc
,.cxx
) to*.o
files. - Linking
.o
files to create one or more executables (binaries).
Writing a makefile is an exercise best left to CS 252 (Introduction to Unix/Linux) or an equivalent course. You are required to use makefiles in this course.
1 A Quick makefile
Most of my examples make use of a quick-and-dirty makefile.
MAINPROG=cmd_line_example
SOURCES:=$(wildcard *.cpp)
OBJECTS=$(SOURCES:.cpp=.o)
FLAGS=-std=c++17 -fsanitize=leak,address -Wall -Wpedantic -fuse-ld=gold
all: $(SOURCES) $(MAINPROG)
$(MAINPROG): $(OBJECTS)
g++ $(FLAGS) $(OBJECTS) -o $@
.cpp.o:
g++ $(FLAGS) -c $< -o $@
clean:
rm *.o $(MAINPROG)
Let us break everything down…
MAINPROG=cmd_line_example
The first line is a variable. It is used to specify the name for the final executable. In this example, the final executable will be named cmd_line_example
.
SOURCES:=$(wildcard *.cpp)
OBJECTS=$(SOURCES:.cpp=.o)
The SOURCES
line uses a wild card (i.e., .cpp
) to grab a list of all cpp
files in the current directory. The OBJECTS
line constructs a list of .o
files that need to be compiled (one per cpp
file).
FLAGS=-std=c++17 -fsanitize=leak,address -Wall -Wpedantic -fuse-ld=gold
The FLAGS
line specifies what compilation flags need to be used:
-
-std=c++17
- use the C++17 standard (this could also bec++0x
,c++11
,c++14
, orc++2a
depending on compiler). -
-fsanitize=leak,address
- add memory leak checks and a few other address checks, generating a report if any are encountered during program execution. -
-Wall
- enable warning for potential issues (e.g., comparing signed and unsigned numbers). -
-Wpedantic
- enable even more warnings (This is essentially… ALL THE WARNINGS). -
-fuse-ld=gold
- this is required due to a linker bug on Ubuntu Linux and certain version ofg++
.
all: $(SOURCES) $(MAINPROG)
When make
or make all
is run, compile everything (all cpp
files) and build the main program.
$(MAINPROG): $(OBJECTS)
g++ $(FLAGS) $(OBJECTS) -o $@
.cpp.o:
g++ $(FLAGS) -c $< -o $@
This is a meta-rule that tells the makefile how to compile each cpp file
.
clean:
rm *.o $(MAINPROG)
When make clean
is run, delete all .o
files and delete the final executable.
2 Command Line Arguments
Most (almost all) examples in will make use of command line arguments. Take a look at…
#include <iostream>
#include <iomanip>
using namespace std;
/**
* Echo all command line arguments, one per line in the form:
*
* idx: arg
*
* where idx is the index (0, 1, ...) and arg is the argument.
*
* @param argc number of arguments
* @param argv 2D array of C-style (null terminated) strings
* @param outs output destination (e.g., cerr or cout)
*/
void echo_arguments(const int argc, char** argv, ostream& outs=std::cerr)
{
// There is always at least one argument, i.e., the program name
outs << right << setw(3) << 0 << ": " << argv[0] << "\n";
outs << "\n";
for (int i = 1; i < argc; i++) {
// Technically "right" does not need to be repeated, but
// we would probably tweak formatting more in the future
outs << right << setw(3) << i << ": " << argv[i] << "\n";
}
}
int main(int argc, char** argv)
{
echo_arguments(argc, argv);
return 0;
}
cmd_line_ex.cpp
is a simple program. All it does is echo any entered command line arguments using the echo_arguments
function.
void echo_arguments(const int argc, char** argv, ostream& outs=std::cerr)
{
// There is always at least one argument, i.e., the program name
outs << right << setw(3) << 0 << ": " << argv[0] << "\n";
outs << "\n";
for (int i = 1; i < argc; i++) {
// Technically "right" does not need to be repeated, but
// we would probably tweak formatting more in the future
outs << right << setw(3) << i << ": " << argv[i] << "\n";
}
}
The first argument (i.e., argv[0]
) is a special case. It is always the program name, and it is supplied automatically.
2.1 Playing with Command Line Arguments
Save a copy of
MAINPROG=cmd_line_example
SOURCES:=$(wildcard *.cpp)
OBJECTS=$(SOURCES:.cpp=.o)
FLAGS=-std=c++17 -fsanitize=leak,address -Wall -Wpedantic -fuse-ld=gold
all: $(SOURCES) $(MAINPROG)
$(MAINPROG): $(OBJECTS)
g++ $(FLAGS) $(OBJECTS) -o $@
.cpp.o:
g++ $(FLAGS) -c $< -o $@
clean:
rm *.o $(MAINPROG)
and
#include <iostream>
#include <iomanip>
using namespace std;
/**
* Echo all command line arguments, one per line in the form:
*
* idx: arg
*
* where idx is the index (0, 1, ...) and arg is the argument.
*
* @param argc number of arguments
* @param argv 2D array of C-style (null terminated) strings
* @param outs output destination (e.g., cerr or cout)
*/
void echo_arguments(const int argc, char** argv, ostream& outs=std::cerr)
{
// There is always at least one argument, i.e., the program name
outs << right << setw(3) << 0 << ": " << argv[0] << "\n";
outs << "\n";
for (int i = 1; i < argc; i++) {
// Technically "right" does not need to be repeated, but
// we would probably tweak formatting more in the future
outs << right << setw(3) << i << ": " << argv[i] << "\n";
}
}
int main(int argc, char** argv)
{
echo_arguments(argc, argv);
return 0;
}
..then open a Linux terminal (e.g., SSH into a CS Linux server or open a local Linux terminal). Run make
to compile cmd_line_ex.cpp
.
-
Run
./cmd_line_example
You should see…
0: ./cmd_line_example
-
Run
./cmd_line_example 1 2 77 The letter Q "Hello, and..."
You should see…
0: ./cmd_line_example 1: 1 2: 2 3: 77 4: The 5: letter 6: Q 7: Hello, and...!
Do not forget… The double quotes are required for any argument that contains spaces.
2.2 Using Command Line Arguments
Most programs will not simply echo command line arguments. Many programs will use command line arguments to specify input files. Consider the following excerpt…
if (argc < 2) {
cout << "No input file provided." << "\n";
cout << "Usage: " << argv[0] << " input_file" << "\n";
return 1;
}
ifstream shapesFile(argv[1]);
if (!shapesFile) {
cout << "Error: " << argv[1] << "could not be opened" << "\n";
return 2;
}
The first conditional block checks that the required number of arguments were supplied by the user.
if (argc < 2) {
In this excerpt, only one argument is required… an input file name. Note that the first true argument (argv[1]
) is the input file name. The first supplied argument (argv[0]
) is the executable name.