CSP301
Design
of a small math utility library
We consider the design
of a simple math utility library. Such libraries, with more efficient
ones with much enhanced functionalities, already exist and is done here
just for illustration purposes.
Features of the
library
This will be a library of 3 dimensional vectors and
matrices.
- Specifications of the Vector
class:-
- Default initialization.
- Functions :-
- Overloaded operator [ ] for element access. (Check
boundary)
- Overloaded operator + for addition of two
vectors
- Overloaded operator - for subtraction
- Overloaded operator * for scalar into vector.
- Overloaded operator * Dot product.
- Cross Product
- Norm function.
- Print vector
- Specifications of the Matrix class:-
Directory structure
for the code
As discussed in the class. We will
organize the code for the library in the following fashion.
- include: To store the header files (.h).
- src:
To store the cpp files (.cpp).
- doc:
To store the documentation files (html,
latex).
- tests:
To store the test codes (.cpp .h).
- objects: To store the object files (.o).
- lib:
To store the library files (.so, .a).
(Note: - If your project produces
some binary (ELF) executables then you should have a directory called bin to house that.)
A look at the
header files
The code to go in a header usually includes some or all of the
following:
- class and struct definitions
- typedefs
- function prototypes
- global variables
- constants
- #defined macros
- #pragma directives
Additionally, when using C++, templates and inline functions
usually need to be in the header file.
Checkout this place for an implementaion of the
math utils stuff. Note the directory structure. Go to the directory include to see the header
file for the Vector stuff. Also not that the file include/MyVector.h
has the comments written in some specific style. This
will be useful for generating documentation with Doxygen.
More on this later but in the meantime you can have a look at the generated
documentation.
A look at the
source files
The code to
go in a source file usually implements the functions defined in the
corresponding header file.
Checkout this place and go to the
directory src
to see the implementation of the different functions of the Vector
class as specified in its header.
The tests directory
contains a file testMyMath.cpp. Writing such testcode is important as
they give you a confidence that a particular module is entomologically
compliant (bug-free).
Howto get all the
stuff made?
If you have
been ploughing through the the MyMath directory, I assume that you have
been doing so, you must have noticed some files called Makefile.
They are absolutely vital for your survival as a programmer. Makefiles offer
the following advantages for handling projects where the code is
distributed over many files.
- Only files which have changed and the targets which depend
on them are
recompiled, making the whole operation faster;
- A complex directory hierarchy for your source code;
- Multiple programming languages may be used in different
parts of one
project;
- Modifications for different architectures, compilers and
systems may
easily be added;
- Non-compiler tasks can be easily automated (cleaning up .o
files,
creating archives, building documentation, running test suites etc).
For detailed information on Make you should read the the manual
You can start by looking here for some makefiles
with increasing level of complexity. The Makefile-1 is the simplest and does
almost nothing apart from just to compile the programes. The other makefiles,
Makefile-2,Makefile-3,.. etc are increasing in their comlexity and efficiency.
For now I would just describe the different stuff going on in the
Makefiles in MyMath
Consider the top-level Makefile in the
MyMath directory.
It looks like this
TOPDIR =$(PWD)
Here a variable $TOPDIR is defined. All the other directories are
defined relative to this varibale. Generally such a variable would be
defined in a top-level Makefile as is this one. It is not advisable to
`hard-code' the base-directory as it hampers portability. If you
hard-code, the next time you hand over your source code to some one
else you will have to give him a detailed list of the changes to be
made to the different Makefiles. With such a scheme the list is going
to be smaller if not unnecessary.
SUBDIRS =$(TOPDIR)/src/
Here a variable $SUBDIRS is defined. It is a variable referring to
your source files. Note the reference to the source directory is in
reference to the TOPDIR variable.
OBJDIR =$(TOPDIR)/objects
Here a variable $OBJDIR is defined which is the location to house
the object (.o) files produced by the compilation.
INCLUDES =-I$(TOPDIR)/include/
Here a variable $INCLUDES is defined. It points to the location where the header files (.h) files are
to be found. Note that we have prepended a `-I' to the location. Thus this variable itself can be
passed as an argument to the g++ command as g++ $(INCLUDES) when we want to compile. However another way to
do this (perhaps a proper way by some reason or logic) would be to define a separate variable called $INCLUDEDIR
and make it point to the include directory. Then one can define the $INCLUDES variable as -I$(INCLUDEDIR)
LDFLAGS =
Here the variable $LDFLAGS is defined but its r.h.s. is empty in its definition. This is kept here more for completeness
and can be stuffed with options that would like to pass to the linker.
BINDIR =$(TOPDIR)/bin/
Here the variable $BINDIR is defined which is the directory to house the elf executables produced by the compilation.
In this example we donot produce such executables.
LIBDIR =$(TOPDIR)/lib/
Here the variable $LIBDIR is defined which is the directory to house the library files produced by the compilation.
In this example we create the shared object libMyMath.so
TESTDIRS =$(TOPDIR)/tests
Here the variable $TESTDIR is defined where we put all our test codes.
CPP =g++
Here the variable $CPP is defined. This is the name of the compiler to be used for the compilation.
CPPFLAGS =-Wall -Wno-unused -g $(INCLUDES)
Here the variable $CPPFLAGS is defined. This contains all the arguments given to the compiler. Note, try to use
the -Wall options (it dumps all warning) although the output will not be interesting if you code loosely. But anyway
they are helpfull for debugging purposes.
OBJ_EXT = .o
Here the variable $OBJ_EXT defines the extensions to be used for the object files. You will see later on how this is used.
EXE_EXT =
Here the variable $EXE_EXT is defined which defines the extensions to be used for the executables. The r.h.s is kept blank.
OBJ_OPT = -c
Here the variable $OBJ_OPT defines the options to be passed to the compiler for creating objects (.o)
.EXPORT_ALL_VARIABLES
Simply by being mentioned as a target, this tells make
to export all variables to child processes by default.
This is used for comminucating the varibles to the sub-make, in a recursive make. This can also be done by
explicitly setting the environment variables.
all: subdirs
This defines the first rule. The target all depends on the target subdirs.
lib: $(LIBDIR)/libMyMath.so
This defines a rule which says that the target lib depends on $(LIBDIR)/libMyMath.so i.e. ./lib/libMyMath.so
Thus saying make lib from the toplevel directory will create libMyMath.so.
subdirs: $(SUBDIRS)
The target subdirs depends on the target $(SUBDIRS).
$(SUBDIRS):
$(MAKE) -C $@
The rule to 'make' target $(SUBDIRS). Here $@ is the makefile variable for target file name. We also use $(MAKE) variable
instead of explicitly saying make. This ensures several stuffs. It uses the same version of make in all the subdirectories, i.e the
version of make that was used to invoke the top-level Makefile. Also note that this is a multiple target rule as $(SUBDIRS) can contain many
stuffs. In our example it just holds the src directory, however we might have multiple source directories and they can all be put in $(SUBDIRS).
.PHONY: subdirs $(SUBDIRS)
This is a PHONY target. Read about PHONY targets in the Manual. It is not the name of any file that is being produced.
It is some command that we want to be executed when we go about doing explicit invocation. Lets stop and take a close
look at what is going on here. We concentrate on the above part of the Makefile.
subdirs: $(SUBDIRS) $(SUBDIRS): $(MAKE) -C $@ .PHONY: subdirs $(SUBDIRS)
|
Let us consider a situation in which we have a huge collection of source files. Some of them are in
src1, some in src2, and the rest in src3. We want to have a recursive make from a directory above these
three directories. Let us look at the first way, the naive way
SUBDIRS = src1 src2 src3 subdirs: for i in $(SUBDIRS); do \ $(MAKE) -C $$i;\ done
|
Now when we say make subdirs the recursive make happens (courtesy '-C' ) in all the subdirectories.
But wait.
There might be errors in src1 and compilation fails there but Make continues happily with
src2 and src3 unless the shell is explicitly told to bail out upon error. Remember Make keeps
on spawnining shells to do the stuff.
Also you cannot do stuff like make -j2 and run the make parallely on two processors (if that
is your system has one) as there is only one rule.
However in our makefile we have done things differently. In our case we have multiple rules. (Thus
parallelization is just typing that extra -j2 or -jN after make). Since separate shells are spawned to
handle the make on separate directories we have a handle on the error too.
$(LIBDIR)/libMyMath.so: subdirs
$(CPP) -shared $(OBJDIR)/*.o $(LDFLAGS) -o $@
Here we have said that the target $(LIBDIR)/libMyMath.so depends on subdirs. So it has to go and check
how to make subdirs. Having done that it proceeds to make the shared library. The second line in more readable form is
g++ -shared /home/subhajit/MyMath/objects/*.o -o /home/subhajit/MyMath/libs/libMyMath.so
The rest of the Makefile essentially explains how the tests target is to be built and on what does it depend. Also
it goes about defining the purging process, how to clean or remove all the junk.
Let us now go into the src directory to check the Makefile there which is recursively invoked by this
top-level Makefile. It looks like this. There's some sed stuff going on here and incase you are curious
about what or how sed check this classic sed tutorial
SRCS=MyVector.cpp
OBJS=$(SRCS:%.cpp=$(OBJDIR)/%$(OBJ_EXT)) OBJSC=$(SRCS:%.c=$(OBJDIR)/%$(OBJ_EXT))
all: $(OBJS) $(OBJSC)
-include $(SRCS:.cpp=.d) clean: rm -f *~ core *.d
$(OBJDIR)/%.o:%.cpp $(CPP) $(CPPFLAGS) $(INCLUDES) $(OBJ_OPT) $< -o $@
%.d: %.cpp set -e; \ $(CPP) -MM $(CPPFLAGS) $(INCLUDES) $< \ | sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \ [ -s $@ ] || rm -f $@
|