Tuesday, 1 October 2013

Writing Large Programs

Introduction and Motivation

  • Small programs, containing only a main( ) function and few if any additional function can be kept all in one file.
  • For programs of any significant size, however, it is better to break the program up into separate files. Some of the advantages of this approach are:
    • When changes are made to part of the program, it is only necessary to recompile the file(s) that have been changed. Any other files can be re-used without having to be recompiled.
    • Certain functions and their special data types may be used by more than one program. This is much easier if these functions and their data types are defined in their own files, separate from main( ).
      • These routines can be inserted into libraries, and/or distributed to others in binary compiled format.

The #include Preprocessor Directive

  • The preprocessor reads C program source code files and performs some preliminary processing on them before passing along the modified versions to the full compiler.
  • In particular, the #include directive tells the pre-processor to go read in the contents of a particular file, and place the contents inside the file being compiled at this point. The effect is as if you copied one file and pasted it into the other. ( Note that the original file is not actually changed. A temporary copy is made, modified by the preprocessor, and then passed along to the compiler. The compiler itself never sees the unmodified original. )
  • Most commonly the #inlcuded files have a ".h" extension, indicating that they are header files.
  • There are two common formats for #includes, as follows:
    • #include < libraryFile.h > // The angle brackets say to look in the standard system directories
    • #include " personalHeaders.h" // The quotation marks say to look in the current directory.
  • Directories and disk drive information is legal, but discouraged since it is not portable:
    • #include <C:\Program Files\Libraries\Includes\somefile.h > // Too specfiic
    • #include <sys/types.h> // Common subdirectory, relative to the standard locations.

Typical Contents of #included Files

Defined Types and Data Structures

  • structs
  • enums
  • typedefs

Defined Constants

  • The #define preprocessor directive can be used to globally replace a word with a number.
  • It acts as if an editor did a global search-and-replace edit of the file.
  • So for example, if you have:
  •     #define MAXARRAYSIZE 100
  • then everywhere that the preprocessor finds MAXARRAYSIZE, it will replace it with 100. So:
  •     int numbers[ MAXARRAYSIZE ];
        for( int i = 0; i < MAXARRAYSIZE; i++ )
  • become:
  •     int numbers[ 100 ];
        for( int i = 0; i < 100; i++ )
  • Beware of two common errors with #defines - Adding extra characters that don't belong. For example:
  •     #define MAXARRAYSIZE  = 100
        #define MAXARRAYSIZE 100;
  • results in :
  •     int numbers[ = 100 ];
        int numbers[ 100; ];
  • Leading to the unexpected error messages:

    • Error: = not expected
    • Error: missing ]

  • Preprocessor defines can incorporate previously defined variables, as in:
  •     #define  PI 3.14159
        #define  PI2 ( PI / 2.0 )

Function Prototypes

  • Adding function prototypes into header files ensures that all functions agree on what the prototype should be, and if it changes, it only needs to be changed in one place.

Declared ( not Defined ) Global Variables

  • The "extern" keyword on a global variable declaration lets the compiler know that the variable exists, but does not allocate any space for it.
  • Exactly one file in the program must define the variable ( allocate space and initialize if appropriate ), by declaring it without the extern keyword.
  • So in the .h file that is #included by everybody:
  •     extern int maxAssignments, maxScores[ ]; // Note no size given for the array
  • and then in exactly one .c file:
  •     int maxAssignments = 7, maxScores[ maxAssignments ]; // Here a size is required and initialization is allowed

Macros

  • Macros are a lot like functions, only different. They will be covered sepaately.

Avoiding Circular Includes

  • #included header files often #include other header files in a daisy-chain like manner.
  • In order to avoid an infinite cycle of circular includes, the pre-processor directives #ifndef, #define, and #endif are often used. Consider this example for the header file named "headerFile.h":
  •   #ifndef HEADERFILE_H
    
           #define HEADERFILE_H
           
           // Normal Header File Contents
           // May #include other files, which may #include this one eventually
           
      #endif

Compiling and Linking Multi-File Projects

  • With IDEs just add ( new ) files to the project. The IDE will normally figure it out from there.
  • On UNIX systems, use "make". See the man page or any good book on make for examples.