c Programming

21
C, C++ programming tips Vishal Patil Summer 2003 Contents 1 Introduction 3 2 Managing multi-file C/C++ project 4 2.1 Good software engineering practices .............. 4 3 Using Automake and Autoconf 7 3.1 Project directory structure ................... 7 3.2 Enabling Portablity ....................... 7 3.3 Configuration files in brief .................... 8 3.4 A brief example .......................... 8 3.5 Understanding the configure.ac file ............... 8 3.6 Understanding the Makefile.am file ............... 9 3.6.1 For each program ..................... 10 3.6.2 For each library ...................... 11 4 Generating build scripts 12 5 Build options 12 6 Avoiding common errors in C and C++ 13 6.1 Identifier clashes between source files .............. 13 6.2 Multiply defined symbols .................... 13 6.3 Redefinitions, redelarations, conflicting types ......... 14 7 Effective C++ programming 15 7.1 Put the constant on the left in a conditional .......... 15 7.2 Handle errors and not bugs ................... 15 7.3 Use asserts in debug builds ................... 16 1

description

c ebook

Transcript of c Programming

  • C, C++ programming tips

    Vishal Patil

    Summer 2003

    Contents

    1 Introduction 3

    2 Managing multi-file C/C++ project 42.1 Good software engineering practices . . . . . . . . . . . . . . 4

    3 Using Automake and Autoconf 73.1 Project directory structure . . . . . . . . . . . . . . . . . . . 73.2 Enabling Portablity . . . . . . . . . . . . . . . . . . . . . . . 73.3 Configuration files in brief . . . . . . . . . . . . . . . . . . . . 83.4 A brief example . . . . . . . . . . . . . . . . . . . . . . . . . . 83.5 Understanding the configure.ac file . . . . . . . . . . . . . . . 83.6 Understanding the Makefile.am file . . . . . . . . . . . . . . . 9

    3.6.1 For each program . . . . . . . . . . . . . . . . . . . . . 103.6.2 For each library . . . . . . . . . . . . . . . . . . . . . . 11

    4 Generating build scripts 12

    5 Build options 12

    6 Avoiding common errors in C and C++ 136.1 Identifier clashes between source files . . . . . . . . . . . . . . 136.2 Multiply defined symbols . . . . . . . . . . . . . . . . . . . . 136.3 Redefinitions, redelarations, conflicting types . . . . . . . . . 14

    7 Effective C++ programming 157.1 Put the constant on the left in a conditional . . . . . . . . . . 157.2 Handle errors and not bugs . . . . . . . . . . . . . . . . . . . 157.3 Use asserts in debug builds . . . . . . . . . . . . . . . . . . . 16

    1

  • 7.4 Use exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . 177.5 Virtual functions . . . . . . . . . . . . . . . . . . . . . . . . . 187.6 Dont ignore API function return values . . . . . . . . . . . . 187.7 Be consistent . . . . . . . . . . . . . . . . . . . . . . . . . . . 187.8 Make your code const correct . . . . . . . . . . . . . . . . . . 18

    7.8.1 The many faces of const . . . . . . . . . . . . . . . . . 187.8.2 Understanding the const cast operator . . . . . . . . . 197.8.3 const and data hiding . . . . . . . . . . . . . . . . . . 20

    8 References 21

    2

  • 1 Introduction

    This document gives a quick idea about the various aspects of software de-velopment using C and C++. The document is in an adhoc form and lacksa proper structure and flow of information. However I shall be revising thestructure from time to time and as I add new information to it.

    Note: I hold no responsibility for any damage or failure causedby using this document.It is very likely that the contents ofthe document might change from time to time and might evenbe mistaken at some places. Use this document as per your owndiscretion.

    3

  • 2 Managing multi-file C/C++ project

    2.1 Good software engineering practices

    The key to better software engineering is to focus away from developingmonolithic applications that do only one job, and focus on developing li-braries. One way to think of libraries is as a program with multiple entrypoints. Every library you write becomes a legacy that you can pass on toother developers. Just like in mathematics you develop little theorems anduse the little theorems to hide the complexity in proving bigger theorems,in software engineering you develop libraries to take care of low-level de-tails once and for all so that they are out of the way everytime you make adifferent implementation for a variation of the problem.

    On a higher level you still dont create just one application. You createmany little applications that work together. The centralized all-in-one ap-proach in my experience is far less flexible than the decentralized approachin which a set of applications work together as a team to accomplish thegoal. In fact this is the fundamental principle behind the design of the Unixoperating system. Of course, it is still important to glue together the variouscomponents to do the job. This you can do either with scripting or withactually building a suite of specialized monolithic applications derived fromthe underlying tools.

    The name of the game is like this: Break down the program to parts.And the parts to smaller parts, until you get down to simple subproblemsthat can be easily tested, and from which you can construct variations of theoriginal problem. Implement each one of these as a library, write test codefor each library and make sure that the library works. It is very importantfor your library to have a complete test suite, a collection of programs thatare supposed to run silently and return normally (exit(0);) if they executesuccessfully, and return abnormally (assert(false); exit(1);) if they fail. Thepurpose of the test suite is to detect bugs in the library, and to convinceyou, the developer, that the library works. The best time to write a testprogram is as soon as it is possible! Dont be lazy. Dont just keep throwingin code after code after code. The minute there is enough new code in thereto put together some kind of test program, just do it! I can not emphasizethat enough. When you write new code you have the illusion that you areproducing work, only to find out tomorrow that you need an entire week todebug it. As a rule, internalize the reality that you know you have producednew work everytime you write a working test program for the new features,and not a minute before. Another time when you should definetly write a

    4

  • test suite is when you find a bug while ordinarily using the library. Then,before you even fix the bug, write a test program that detects the bug.Then go fix it. This way, as you add new features to your libraries you haveinsurance that they wont reawaken old bugs.

    Please keep documentation up to date as you go. The best time to writedocumentation is right after you get a few new test programs working. Youmight feel that you are too busy to write documentation, but the truth ofthe matter is that you will always be too busy. After long hours debuggingthese seg faults, think of it as a celebration of triumph to fire up the editorand document your brand-spanking new cool features.

    Please make sure that computational code is completely seperated fromI/O code so that someone else can reuse your computational code withoutbeing forced to also follow your I/O model. Then write programs thatinvoke your collection of libraries to solve various problems. By dividingand conquering the problem library by library with a test suite for eachstep along the way, you can write good and robust code. Also, if you aredeveloping numerical software, please dont expect that other users of yourcode will be getting a high while entering data for your input files. Insteadwrite an interactive utility that will allow users to configure input files in auser friendly way. Granted, this is too much work in Fortran. Then again,you do know more powerful languages, dont you?

    Examples of useful libraries are things like linear algebra libraries, gen-eral ODE solvers, interpolation algorithms, and so on. As a result you endup with two packages. A package of libraries complete with a test suite, anda package of applications that invoke the libraries. The package of librariesis well-tested code that can be passed down to future developers. It is codethat wont have to be rewritten if its treated with respect. The packageof applications is something that each developer will probably rewrite sincedifferent people will probably want to solve different problems. The effect ofhaving a package of libraries is that C++ is elevated to a Very High LevelLanguage thats closer to the problems you are solving. In fact a good ruleof thumb is to make the libraries sufficiently sophisticated so that each ex-ecutable that you produce can be expressed in one source file. All this maysound like common sense, but you will be surprised at how many scientificdevelopers maintain just one does-everything-program that they perpetuallyhack until it becomes impossible to maintain. And then you will be evenmore surprised when you find that some professors dont understand why asimple mathematical modification of someone elses code is taking you solong.

    Every library must have its own directory and Makefile. So a library

    5

  • package will have many subdirectories, each directory being one library.And perhaps if you have too many of them, you might want to group themeven further down. Then, theres the applications. If youve done everythingright, there should be enough stuff in your libraries to enable you to haveone source file per application. Which means that all the source files canprobably go down under the same directory.

    Very often you will come to a situation where theres something thatyour libraries to-date cant do, so you implement it and stick it along inyour source file for the application. If you find yourself cut and pasting thatimplementation to other source files, then this means that you have to putthis in a library somewhere. And if it doesnt belong to any library youvewritten so far, maybe to a new library. When you are in a deadline crunch,theres a tendency not to do this since its easier to cut and paste. Theproblem is that if you dont take action right then, eventually your code willdegenerate to a hard-to-use mess. Keeping the entropy down is somethingthat must be done on a daily basis.

    6

  • 3 Using Automake and Autoconf

    In this section I shall explain managing multi file C and C++ projects usingautomake and autoconf tools. These tools enable the developer to get ridof the tedium of writing complicated Makefiles for large projects and alsoavail portability across various platforms. These tools have been specificallydesigned for managing GNU projects. Software developed using automakeand autoconf needs to adhere to the GNU software engineering principles.

    3.1 Project directory structure

    The project directory is recommeded to have the following subdirectoriesand files.

    src : Contains the actual source code that gets compiled. Every libraryshud have its over subdirectory. Every executable shud have its ownsubdirectory as well. If the each executable needs only one or twosource files its sensible to keep all the source files in the same directory.

    lib : An optional directory in which you place portablity code likeimplementations of system calls that are not avaliable of certian plat-forms.

    doc : Directory containing documentation for your package. m4 : A directory containing m4 files that you package may need toinstall. These files define new autoconf macros that you should makeavailable to other developers who want to use your libraries.

    intl : Portability source code which allows your program to talk invarious languages.

    po: Directory containing message catalogs for your software package.Automake makes it really easy to manage multidirectory source code pack-ages so you shudnt be shy taking advantage of it.

    3.2 Enabling Portablity

    Inorder to make your C/C++ project protable across different platformsyou need to add the following lines of code to all of the source files beforeany include statements

    7

  • #ifdef HAVE_CONFIG_H#include #endif

    The config.h file is generated by the tools (or the configure script not quitesure!!) and the HAVE CONFIG H flag is passed along with the -D option of thecompiler by the generated scripts at the time of building the project. Usethe autoheader utility to generate the config.h file for the project.

    3.3 Configuration files in brief

    There are two main configuration files used by these tools.

    configure.ac : Used by the autoconf tool to generate plaform specificconfigure script.

    Makefile.am : Used by the automake tool to generate the Makefilefor the sources present in the directory containing the Makefile.amfile.

    NOTE : There exists a single configure.ac for a project however you need tocreate a distinct Makefile.am for each sub directory in the project

    3.4 A brief example

    I shall explain the use of these tools with the help of a small sample project.The project will involve creation of an executable file called main (sourcemain.c) which will call functions from a user generated library geom (sourcecircle.c) and stats (source mean.c). The code for the main executableis present in the src directorry while that for the geom and stats librariesis present inside subdirectories geom and stats respectively ,inside the srcdirectory. Thus this project will cover compiling of source files, creatingstatic libraries and linking the libraries to create the final executable.

    3.5 Understanding the configure.ac file

    This file is used by the autoconf tool and used to generate the plaformspecific configure script.

    The configure.ac file for the example is shown below.

    AC_INIT(reconf)AM_CONFIG_HEADER(config.h)

    8

  • AM_INIT_AUTOMAKE(test,0.1)AC_PROG_CCAC_PROG_RANLIBAC_PROG_INSTALLAC_CONFIG_FILES([Makefile

    doc/Makefilem4/Makefilesrc/Makefilesrc/geom/Makefilesrc/stats/Makefilelib/Makefile

    ])AC_OUTPUT

    In the above sample configure.ac the parameters passed to the AM INIT AUTOMAKEfunction namely test and 0.1 represent the package name and versionnumber respectively.

    The AC CONFIG FILES function needs to be passed the paths ofthe various Makefiles which need to be generated for the varioussubdirectories. Please note that Makefile.am must be present in eachsub directory under the project directory

    3.6 Understanding the Makefile.am file

    A Makefile.am is a set of assignments. These assignments imply the Make-file, a set of targets, dependencies and rules, and the Makefile implies theexecution of building. The first set of assignments look like this

    INCLUDES = -I/geom -I/stats ....LDFLAGS = -L/geom -L/stats ....LDADD = -lgeom -lgeom ...

    The INCLUDES assignment is where you insert the -I flags that you needto pass to your compiler. If the stuff in this directory is dependent ona library in another directory of the same package, then the -I flagmust point to that directory.

    The LDFLAGS assignment is where you insert the -L flags that areneeded by the compiler when it links all the object files to an exe-cutable.

    9

  • The LDADD assignment is where you list a long set of installed librariesthat you want to link in with all of your executables. Use the -l flagonly for installed libraries. You can list libraries that have been builtbut not installed yet as well, but do this only be providing the fullpath to these libraries.

    If your package contains subdirectories with libraries and you want to linkthese libraries in another subdirectory you need to put -I and -L flags inthe two variables above. To express the path to these other subdirectories,use the $(top srcdir) variable. For example if you want to access a libraryunder src/libfoo you can put something like:

    INCLUDES = ... -I$(top_srcdir)/src/geom ...LDFLAGS = ... -L$(top_srcdir)/src/geom ...

    on the Makefile.am of every directory level that wants access to these li-braries. Also, you must make sure that the libraries are built before thedirectory level is built. To guarantee that, list the library directories inSUBDIRS before the directory levels that depend on it. One way to dothis is to put all the library directories under a lib directory and all theexecutable directories under a bin directory and on the Makefile.am forthe directory level that contains lib and bin list them as:

    SUBDIRS = lib bin

    3.6.1 For each program

    You need to declare the set of files that are sources of the program, the setof libraries that must be linked with the program and (optionally) a set ofdependencies that need to be built before the program is built. These aredeclared in assignments that look like this:

    main_SOURCES = main.cmain_LDADD = -lgeom -lstatsmain_LDFLAGS = -L$(top_srcdir)/src/geom -L$(top_srcdir)/src/statsmain_DEPENDENCIES = geom stats

    main SOURCES : Here you list all the *.cc and *.h files that com-pose the source code of the program. The presense of a header filehere doesnt cause the file to be installed at /prefix/include but itdoes cause it to be added to the distribution when you do make dist.To cause header files to be installed you must also put them in in-clude HEADERS.

    10

  • main LADD : Here you add primarily the -l flags for linking whateverlibraries are needed by your code. You may also list object files, whichhave been compiled in an exotic way, as well as paths to uninstalledyet libraries.

    main LDFLAGS : Here you add the -L flags that are needed to resolvethe libraries you passed in main LDADD. Certain flags that need tobe passed on every program can be expressed on a global basis byassigning them at LDFLAGS.

    main DEPENDENCIES : If for any reason you want certain othertargets to be built before building this program, you can list themhere.

    3.6.2 For each library

    Theres a total of four assignments that are relevant to building libraries.Eg for the lib geom these assignments can be specified as follows.

    lib_LIBRARIES = libgeom.alibgeom_a_SOURCES = circle.c circle.hlibgeom_a_LIBADD = circle.olibgeom_a_DEPENDENCIES =

    lib LIBRARIES : The library name libgeom a SOURCES : Just like with programs, here you list all the*.cc files as well as all the private header files that compose the library.By private header file we mean a header file that is used internally bythe library and the maintainers of the library, but is not exported tothe end-user. You can list public header files also if you like, andperhaps you should for documentation purposes, but if you mentionthem in include HEADERS it is not required to repeat them a secondtime here.

    libgeom a LIBADD : If there are any other object files that you wantto include in the library list them here. You might be tempted tolist them as dependencies in libgeom a DEPENDENCIES, but thatwill not work. If you do that, the object files will be built before thelibrary is built but they will not be included in the library! By listingan object file here, you are stating that you want it to be built andyou want it to be included in the library.

    11

  • libgeom a DEPENDENCIES : If there are any other targets that needto be built before this library is built, list them here.

    4 Generating build scripts

    The following commands have to executed in order to generate the buildscripts for the project.

    1. libtoolize

    2. aclocal

    3. autoheader

    4. autoconf

    5. touch README AUTHORS NEWS ChangeLog (Required for GNUsoftware adherence)

    6. automake -a

    The execution of the above four commands generates the configure in thetop directory and Makefile scripts in the top directory as well as each ofthe sub directories.

    5 Build options

    You need to run the configure script before building the project usingmake. After successfully running the configure script the following optionsas avaliable for make

    make Builds the project and creates the executables and libraries. make clean Cleans the project i.e removes all the executables. make install Builds and installs the project i.e the executable iscopied in the /prefix/bin,headers in /prefix/include and libraries in/prefix/lib where prefix is usually /usr/local.

    make uninstall Uninstalls the project i.e removes the files added to/prefix/bin, /prefix/include and /prefix/lib directories.

    make dist Creates a distribution of the project (-.tar.gz file) of the project.

    12

  • 6 Avoiding common errors in C and C++

    6.1 Identifier clashes between source files

    In C, variables and functions are by default public, so that any C source filemay refer to global variables and functions from another C source file. Thisis true even if the file in question does not have a declaration or prototypefor the variable or function. You must, therefore, ensure that the same sym-bol name is not used in two different files. If you dont do this you will getlinker errors and possibly warnings during compilation.

    One way of doing this is to prefix public symbols with some string whichdepends on the source file they appear in. For example, all the routines ingfx.c might begin with the prefix gfx . If you are careful with the way yousplit up your program, use sensible function names, and dont go overboardwith global variables, this shouldnt be a problem anyway.

    To prevent a symbol from being visible from outside the source file it isdefined in, prefix its definition with the keyword static. This is useful forsmall functions which are used internally by a file, and wont be needed byany other file.

    6.2 Multiply defined symbols

    A header file is literally substituted into your C code in place of the #in-clude statement. Consequently, if the header file is included in more thanone source file all the definitions in the header file will occur in both sourcefiles. This causes them to be defined more than once, which gives a linkererror (see above).

    Solution: dont define variables in header files. You only want to declarethem in the header file, and define them (once only) in the appropriate Csource file, which should #include the header file of course for type checking.The distinction between a declaration and a definition is easy to miss forbeginners; a declaration tells the compiler that the named symbol shouldexist and should have the specified type, but it does not cause the compilerto allocate storage space for it, while a definition does allocate the space. Tomake a declaration rather than a definition, put the keyword extern beforethe definition.

    So, if we have an integer called counter which we want to be publicly

    13

  • available, we would define it in a source file (one only) as int counter; attop level, and declare it in a header file as extern int counter;.

    6.3 Redefinitions, redelarations, conflicting types

    Consider what happens if a C source file includes both a.h and b.h, and alsoa.h includes b.h (which is perfectly sensible; b.h might define some typesthat a.h needs). Now, the C source file includes b.h twice. So every #definein b.h occurs twice, every declaration occurs twice (not actually a problem),every typedef occurs twice, etc. In theory, since they are exact duplicates itshouldnt matter, but in practice it is not valid C and you will probably getcompiler errors or at least warnings.

    The solution to this problem is to ensure that the body of each headerfile is included only once per source file. This is generally achieved usingpreprocessor directives. We will define a macro for each header file, as weenter the header file, and only use the body of the file if the macro is notalready defined. In practice it is as simple as putting this at the start ofeach header file:

    #ifndef FILENAME_H#define FILENAME_H

    and then putting this at the end of it:

    #endif

    replacing FILENAME H with the (capitalised) filename of the header file,using an underline instead of a dot. Some people like to put a commentafter the #endif to remind them what it is referring to, e.g.

    #endif /* #ifndef FILENAME_H */

    Personally I dont do that since its usually pretty obvious, but it is a matterof style.

    You only need to do this trick to header files that generate the compilererrors, but it doesnt hurt to do it to all header files.

    14

  • 7 Effective C++ programming

    7.1 Put the constant on the left in a conditional

    Weve all experienced bugs like this:

    while (continue = TRUE){

    // ...this loops forever!}

    This type of problem can be solved by putting the constant on the left, soif you leave out an = in a conditional, you will get a compiler error insteadof a program bug (because constants are non lvalues, of course):

    while (TRUE = continue){

    // compile error!}

    7.2 Handle errors and not bugs

    There are lots of error conditions that happen in the normal life of a program.For instance, file not found, out of memory, or invalid user input. You shouldalways handle these conditions gracefully (by re-prompting for a filename,by freeing memory or telling the user to quit other applications, or by tellingthe user there is an error in his input, respectively). However, there are otherconditions which are not real error conditions, but are the result of bugs.For example, say you have a routine which copies a string into a buffer, andno one is supposed to pass in a NULL pointer to the routine. You do notwant to do something like this:

    void CopyString(char* szBuffer, int nBufSize){

    if (NULL == szBuffer)return; // quietly fail if NULL pointer

    else{

    strncpy(szBuffer, "Hello", nBufSize);}

    }

    15

  • Also, dont do something like this (some extremely fault-tolerant systemsmay be able to justify this, but 99% of applications cant):

    void CopyString(char* szBuffer, int nBufSize){

    if (NULL == szBuffer){

    cerr

  • 7.4 Use exceptions

    Use the try/throw/catch mechanisms in C++ - they are very powerful.Many people implement an exception class, which they use for general errorreporting throughout their program.

    class ProgramException{

    // pass in a pointer to string, make sure string still exists when// the PrintError() method is calledProgramException(const char* const szErrorMsg = NULL){

    if (NULL == szErrorMsg)m_szMsg = "Unspecified error";

    elsem_szMsg = szErrorMsg;

    };

    void PrintError(){

    cerr

  • return EXIT_FAILURE;}return EXIT_SUCCESS;

    }

    7.5 Virtual functions

    In C++ virtual functions are used for enabling Polymorphism. Followingtwo important points need to noted while using virual functions:

    A function declared as virtual in the base class shud to be declared asvirtual in the derived class as well.

    A class which has a member function declared as virtual needs to haveits destructor to be defined as virtual as well. This is required forproper calls to the destructor up the class hierarchy.

    7.6 Dont ignore API function return values

    Most API functions will return a particular value which represents an error.You should test for these values every time you call the API function. If youdont want want to clutter your code with error-testing, then wrap the APIcall in another function (do this when you are thinking about portability,too) which tests the return value and either asserts, handles the problem,or throws an exception. The above example of OpenDataFile is a primitiveway of wrapping fopen with error-checking code which throws an exceptionif fopen fails.

    7.7 Be consistent

    Be consistent in the way you write your code. Use the same indentation andbracketing style everywhere. If you put the constant on the left in a condi-tional, do it everywhere. If you assert on your pointers, do it everywhere.Use the same kind of comment style for the same kind of comments. If youare the type to go in for a naming convention (like Hungarian notation),then you have to stick to it everywhere. Dont do int iCount in one placeand int nCount in another.

    7.8 Make your code const correct

    7.8.1 The many faces of const

    const int x; // constant int

    18

  • x = 2; // illegal - cant modify x

    const int* pX; // changeable pointer to constant int*pX = 3; // illegal - cant use pX to modify an intpX = &someOtherIntVar; // legal - pX can point somewhere else

    int* const pY; // constant pointer to changeable int*pY = 4; // legal - can use pY to modify an intpY = &someOtherIntVar; // illegal - cant make pY point anywhere else

    const int* const pZ; // const pointer to const int*pZ = 5; // illegal - cant use pZ to modify an intpZ = &someOtherIntVar; // illegal - cant make pZ point anywhere else

    The const keyword is more involved when used with pointers. A pointeris itself a variable which holds a memory address of another variable - itcan be used as a handle to the variable whose address it holds. Note thatthere is a difference between a read-only handle to a changeable variableand a changeable handle to a read-only variable.

    7.8.2 Understanding the const cast operator

    const int x = 4; // x is const, it cant be modifiedconst int* pX = &x; // you cant modify x through the pX pointer

    cout

  • the code would certainly be confusing. Also, if you were using user-definedclasses instead of numeric types, the code would still compile, but it wouldalmost certainly crash your program. If you use const cast, you can be surethat the compiler will only let you change the const-ness of a variable, andnever its type.

    7.8.3 const and data hiding

    In C++ when a member function returns a pointer which points to itsmember variable, there exists a possibility of the pointer address or thepointer value getting modified. This problem can be overcome by using theconst keyword. An example illustrating this idea is given below

    class Person{

    public:Person(char* szNewName){

    // make a copy of the stringm_szName = _strdup(szNewName);

    };

    ~Person() { delete[] m_szName; };

    const char* const GetName() const{

    return m_szName;};

    private:

    char* m_szName;};

    In the above class the GetName() member function returns a pointer to themember variable m szName. To prevent this member variable from gettingaccidently modified the GetName() has been prototyped to return a constantpointer pointing to a constant value. Also the const keyword at the end ofthe function prototype states that the function does not modify any of themember variables.

    20

  • 8 References

    http://www.gmonline.demon.co.uk/cscene/CS2/CS2-01.html http://autotoolset.sourceforge.net/tutorial.html

    21