Predefined Macros

Many compilers predefine a number of useful macros, including __DATE__, __TIME__, __LINE__, and __FILE__. Each of these names is surrounded by two underscore characters to reduce the likelihood that the names will conflict with names you've used in your program.

When the precompiler sees one of these macros, it makes the appropriate substitutes. For __DATE__, the current date is substituted. For __TIME__, the current time is substituted. __LINE__ and __FILE__ are replaced with the source code line number and filename, respectively. You should note that this substitution is made when the source is precompiled, not when the program is run. If you asked the program to print __DATE__, you would not get the current date; you would get the date the program was compiled. These defined macros are very useful in debugging.

assert()

Many compilers offer an assert() macro. The assert() macro returns true if its parameter evaluates true, and takes some kind of action if it evaluates false. Many compilers will abort the program on an assert() that fails, others will throw an exception. (See Hour 20, “Special Classes, Functions, and Pointers.”)

One powerful feature of the assert() macro is that the preprocessor collapses it into no code at all if DEBUG is not defined. It is a great help during development, and when the final product ships there is neither a performance penalty nor an increase in the size of the executable version of the program.

Rather than depending on the compiler-provided assert(), you are free to write your own assert() macro. Listing 21.3 provides a simple assert() macro and shows its use.

Listing 21.3. A Simple assert() Macro
 0:  // Listing 21.3 ASSERTS
 1:  #define DEBUG
 2:  #include <iostream>
 3:
 4:  #ifndef DEBUG
 5:      #define ASSERT(x)
 6:  #else
 7:      #define ASSERT(x) 
 8:          if (! (x)) 
 9:          { 
10:              std::cout << "ERROR!! Assert " << #x << " failed
"; 
11:              std::cout << " on line " << __LINE__  << "
"; 
12:              std::cout << " in file " << __FILE__ << "
";  
13:          }
14:  #endif
15:
16:  int main()
17:  {
18:      int x = 5;
19:      std::cout << "First assert: 
";
20:      ASSERT(x==5);
21:      std::cout << "
Second assert: 
";
22:      ASSERT(x != 5);
23:      std::cout << "
Done.
";
24:      return 0;
25:  }


First assert:

Second assert:
ERROR!! Assert x!=5 failed
 on line 24
 in file test2103.cpp
						

On line 1, the term DEBUG is defined. Typically, this would be done from the command line (or the IDE) at compile time, so that you could turn it on and off at will. On lines 7–13, the assert() macro is defined. Typically, this would be done in a header file, and that header (ASSERT.HPP) would be included in all your implementation files.


On line 4, the term DEBUG is tested. If it is not defined, assert() is defined to create no code at all. If DEBUG is defined, the functionality defined on lines 7–13 is applied.

The assert() itself is one long statement, split across seven source code lines, as far as the precompiler is concerned. On line 8, the value passed in as a parameter is tested; if it evaluates false, the statements on lines 10–12 are invoked, printing an error message. If the value passed in evaluates true, no action is taken.

Debugging with assert()

When writing your program, you will often know deep down in your soul that something is true: A function has a certain value, a pointer is valid, and so forth. It is the nature of bugs that what you know to be true might not be true under some conditions. For example, you know that a pointer is valid, yet the program crashes. assert() can help you find this type of bug, but only if you make it a regular practice to use assert() statements liberally in your code. Every time you assign or are passed a pointer as a parameter or function return value, be sure to assert that the pointer is valid. Any time your code depends on a particular value being in a variable, assert() that it is true.

There is no penalty for frequent use of assert() statements; they are removed from the code when you undefine debugging. They also provide good internal documentation, reminding the reader of what you believe is true at any given moment in the flow of the code.

Side Effects

It is not uncommon to find that a bug appears only after the assert() statements are removed. This is almost always due to the program unintentionally depending on side effects of things done in assert()s and other debug-only code. For example, if you write

ASSERT (x = 5)

when you mean to test whether x == 5, you will create a particularly nasty bug.

Let's say that just prior to this assert(), you called a function that set x equal to 0. With this assert() you think you are testing whether x is equal to 5; in fact, you are setting x equal to 5. The test returns true, because x = 5 not only sets x to 5, but returns the value 5; and because 5 is non-zero, the test evaluates to true.

After you pass the assert() statement, x really is equal to 5 (you just set it!). Your program runs just fine, and you're ready to ship it, so you turn debugging off. Now the assert() disappears and you are no longer setting x to 5. Because x was set to 0 just before this, it remains at 0 and your program breaks.

In frustration, you turn debugging back on, but hey! Presto! The bug is gone. Once again, this is rather funny to watch, but not to live through, so be very careful about side effects in debugging code. If you see a bug that only appears when debugging is turned off, take a look at your debugging code with an eye out for nasty side effects.

Class Invariants

Most classes have some conditions that should always be true whenever you are finished with a class member function. These are called class invariants, and they are the sine qua non of your class. For example, it may be true that your CIRCLE object should never have a radius of 0, or that your ANIMAL should always have an age greater than 0 and less than 100.

It can be very helpful to declare an Invariants() method that returns true only if each of these conditions is still true. You can then insert Assert(Invariants()) at the start and completion of every class method. The exception would be that your Invariants() would not be expected to return true before your constructor runs or after your destructor ends. Listing 21.4 demonstrates the use of the Invariants() method in a trivial class.

Listing 21.4. Using Invariants()
  0:  // Listing 21.4 Invariants
  1:  #define DEBUG
  2:  #define SHOW_INVARIANTS
  3:  #include <iostream>
  4:  #include <string.h>
  5:
  6:  #ifndef DEBUG
  7:  #define ASSERT(x)
  8:  #else
  9:  #define ASSERT(x) 
 10:      if (! (x)) 
 11:      { 
 12:          std::cout << "ERROR!! Assert " << #x << " failed
"; 
 13:          std::cout << " on line " << __LINE__  << "
"; 
 14:          std::cout << " in file " << __FILE__ << "
";  
 15:      }
 16:  #endif
 17:
 18:  class String
 19:  {
 20:  public:
 21:      // constructors
 22:      String();
 23:      String(const char *const);
 24:      String(const String &);
 25:      ~String();
 26:
 27:      char & operator[](int offset);
 28:      char operator[](int offset) const;
 29:
 30:      String & operator= (const String &);
 31:      int GetLen()const { return itsLen; }
 32:      const char * GetString() const { return itsString; }
 33:      bool Invariants() const;
 34:
 35:  private:
 36:      String (int);         // private constructor
 37:      char * itsString;
 38:      unsigned short itsLen;
 39:  };
 40:
 41:  // default constructor creates string of 0 bytes
 42:  String::String()
 43:  {
 44:      itsString = new char[1];
 45:      itsString[0] = '';
 46:      itsLen=0;
 47:      ASSERT(Invariants());
 48:  }
 49:
 50:  // private (helper) constructor, used only by
 51:  // class methods for creating a new string of
 52:  // required size.  Null filled.
 53:  String::String(int len)
 54:  {
 55:      itsString = new char[len+1];
 56:      for (int i = 0; i<=len; i++)
 57:          itsString[i] = '';
 58:      itsLen=len;
 59:      ASSERT(Invariants());
 60:  }
 61:
 62:  // Converts a character array to a String
 63:  String::String(const char * const cString)
 64:  {
 65:      itsLen = strlen(cString);
 66:      itsString = new char[itsLen+1];
 67:      for (int i = 0; i<itsLen; i++)
 68:          itsString[i] = cString[i];
 69:      itsString[itsLen]='';
 70:      ASSERT(Invariants());
 71:  }
 72:
 73:  // copy constructor
 74:  String::String (const String & rhs)
 75:  {
 76:      itsLen=rhs.GetLen();
 77:      itsString = new char[itsLen+1];
 78:      for (int i = 0; i<itsLen;i++)
 79:          itsString[i] = rhs[i];
 80:      itsString[itsLen] = '';
 81:      ASSERT(Invariants());
 82:  }
 83:
 84:  // destructor, frees allocated memory
 85:  String::~String ()
 86:  {
 87:      ASSERT(Invariants());
 88:      delete [] itsString;
 89:      itsLen = 0;
 90:  }
 91:
 92:  // operator equals, frees existing memory
 93:  // then copies string and size
 94:  String& String::operator=(const String & rhs)
 95:  {
 96:      ASSERT(Invariants());
 97:      if (this == &rhs)
 98:          return *this;
 99:      delete [] itsString;
100:      itsLen=rhs.GetLen();
101:      itsString = new char[itsLen+1];
102:      for (int i = 0; i<itsLen;i++)
103:          itsString[i] = rhs[i];
104:      itsString[itsLen] = '';
105:      ASSERT(Invariants());
106:      return *this;
107:  }
108:
109:  //non constant offset operator, returns
110:  // reference to character so it can be
111:  // changed!
112:  char & String::operator[](int offset)
113:  {
114:      ASSERT(Invariants());
115:      if (offset > itsLen)
116:          return itsString[itsLen-1];
117:      else
118:          return itsString[offset];
119:      ASSERT(Invariants());
120:  }
121:
122:  // constant offset operator for use
123:  // on const objects (see copy constructor!)
124:  char String::operator[](int offset) const
125:  {
126:      ASSERT(Invariants());
127:      if (offset > itsLen)
128:          return itsString[itsLen-1];
129:      else
130:          return itsString[offset];
131:      ASSERT(Invariants());
132:  }
133:
134:  // ensure that the string has some length or
135: // the pointer is null and the length is zero
136:  bool String::Invariants() const
137:  {
138:  #ifdef SHOW_INVARIANTS
139:      std::cout << " String OK ";
140:  #endif
141:      return ( (itsLen && itsString) || (!itsLen && !itsString) );
142:  }
143:
144:  class Animal
145:  {
146:  public:
147:      Animal():itsAge(1),itsName("John Q. Animal")
148:          { ASSERT(Invariants());}
149:      Animal(int, const String&);
150:      ~Animal(){}
151:      int GetAge() { ASSERT(Invariants()); return itsAge;}
152:      void SetAge(int Age)
153:          {
154:              ASSERT(Invariants());
155:              itsAge = Age;
156:              ASSERT(Invariants());
157:          }
158:      String& GetName() { ASSERT(Invariants()); return itsName;  }
159:      void SetName(const String& name)
160:          {
161:              ASSERT(Invariants());
162:              itsName = name;
163:              ASSERT(Invariants());
164:          }
165:      bool Invariants();
166:  private:
167:      int itsAge;
168:      String itsName;
169:  };
170:
171:  Animal::Animal(int age, const String& name):
172:  itsAge(age),
173:  itsName(name)
174:  {
175:      ASSERT(Invariants());
176:  }
177:
178:  bool Animal::Invariants()
179:  {
180:  #ifdef SHOW_INVARIANTS
181:      std::cout << " Animal OK ";
182:  #endif
183:      return (itsAge > 0 && itsName.GetLen());
184:  }
185:
186:  int main()
187:  {
188:      Animal sparky(5,"Sparky");
189:      std::cout << "
" << sparky.GetName().GetString() << " is ";
190:      std::cout << sparky.GetAge() << " years old.";
191:      sparky.SetAge(8);
192:      std::cout << "
" << sparky.GetName().GetString() << " is ";
193:      std::cout << sparky.GetAge() << " years old.";
194:     return 0;
195:  }


String OK  String OK  String OK  String OK  String OK String OK×String OK  String OK 
 Animal OK  String OK  Animal OK
Sparky is  Animal OK 5 years old. Animal OK  Animal OK  Animal OK
Sparky is  Animal OK 8 years old. String OK 
							

On lines 6–16, the assert() macro is defined. If DEBUG is defined, this macro will write out an error message when the assert() macro evaluates false.


On line 33, the String class member function Invariants() is declared; it is defined on lines 135–141. The constructor is declared on lines 22–24, and on line 47, after the object is fully constructed, Invariants() is called to confirm proper construction.

This pattern is repeated for the other constructors, and the destructor calls Invariants() only before it sets out to destroy the object. The remaining class functions call Invariants() both before taking any action and then again before returning. This both affirms and validates a fundamental principal of C++: Member functions other than constructors and destructors should work on valid objects and should leave them in a valid state.

On line 164, class Animal declares its own Invariants() method, implemented on lines 177–183. Note on lines 147, 150, 153, 155, 160, and 162 that inline functions can call the Invariants() method.

Printing Interim Values

In addition to asserting that something is true using the assert() macro, you may want to print the current values of pointers, variables, and strings. This can be very helpful in checking your assumptions about the progress of your program and in locating off-by-one bugs in loops. Listing 21.5 illustrates this idea.

Listing 21.5. Printing Values in DEBUG Mode
 0:  // Listing 21.5 - Printing values in DEBUG mode
 1:  #include <iostream>
 2:  #define DEBUG
 3:
 4:  #ifndef DEBUG
 5:  #define PRINT(x)
 6:  #else
 7:  #define PRINT(x) 
 8:      std::cout << #x << ":	" << x << std::endl;
 9:  #endif
10:
11:  int main()
12:  {
13:      int x = 5;
14:      long y = 73898l;
15:      PRINT(x);
16:      for (int i = 0; i < x; i++)
17:      {
18:          PRINT(i);
19:      }
20:
21:      PRINT (y);
22:      PRINT("Hi.");
23:      int *px = &x;
24:      PRINT(px);
25:      PRINT (*px);
26:      return 0;
27:  }


x:            5
i:            0
i:            1
i:            2
i:            3
i:            4
y:            73898
"Hi.":        Hi.
px:           0x2100 (You may recieve a value other than 0x2100)
*px:          5
							

The macro on lines 4–9 provides printing of the current value of the supplied parameter. Note that the first thing fed to cout is the stringized version of the parameter; that is, if you pass in x, cout receives "x".


Next cout receives the quoted string ": ", which prints a colon and then a tab. Third, cout receives the value of the parameter (x) and then finally endl, which writes a new line and flushes the buffer.

Debugging Levels

In large, complex projects, you may want more control than simply turning DEBUG on and off. You can define debug levels, and test for these levels when deciding which macros to use and which to strip out.

To define a level, simply follow the #define DEBUG statement with a number. While you can have any number of levels, a common system is to have four levels: HIGH, MEDIUM, LOW, and NONE. Listing 21.6 illustrates how this might be done, using the String and Animal classes from Listing 21.4. The definitions of the class methods other than Invariants() have been left out to save space, because they are unchanged from Listing 21.4.

Listing 21.6. Levels of Debugging
  0:  // Listing 21.6 Debugging Levels
  1:  #include <iostream>
  2:  #include <string.h>
  3:
  4:  enum LEVEL { NONE, LOW, MEDIUM, HIGH };
  5:
  6:  #define DEBUGLEVEL HIGH
  7:
  8:  #if DEBUGLEVEL < LOW  // must be low, medium or high
  9:  #define ASSERT(x)
 10:  #else
 11:  #define ASSERT(x) 
 12:      if (! (x)) 
 13:      { 
 14:          std::cout << "ERROR!! Assert " << #x << " failed
"; 
 15:          std::cout << " on line " << __LINE__  << "
"; 
 16:          std::cout << " in file " << __FILE__ << "
";  
 17:      }
 18:   #endif
 19:
 20:  #if DEBUGLEVEL < MEDIUM
 21:  #define EVAL(x)
 22:  #else
 23:  #define EVAL(x) 
 24:      std::cout << #x << ":	" << x << endl;
 25:  #endif
 26:
 27:  #if DEBUGLEVEL < HIGH
 28:  #define PRINT(x)
 29:  #else
 30:  #define PRINT(x) 
 31:      std::cout << x << std::endl;
 32:  #endif
 33:
 34:  class String
 35:  {
 36:  public:
 37:      // constructors
 38:      String();
 39:      String(const char *const);
 40:      String(const String &);
 41:      ~String();
 42:
 43:      char & operator[](int offset);
 44:      char operator[](int offset) const;
 45:
 46:      String & operator= (const String &);
 47:      int GetLen()const { return itsLen; }
 48:      const char * GetString() const
 49:          { return itsString; }
 50:      bool Invariants() const;
 51:
 52:  private:
 53:      String (int);         // private constructor
 54:      char * itsString;
 55:      unsigned short itsLen;
 56:  };
 57:
 58:  bool String::Invariants() const
 59:  {
 60:      PRINT("(String Invariants Checked)");
 61:      return ( (bool) (itsLen && itsString) ||
 62:          (!itsLen && !itsString) );
 63:  }
 64:
 65:  class Animal
 66:  {
 67:  public:
 68:      Animal():itsAge(1),itsName("John Q. Animal")
 69:      { ASSERT(Invariants());}
 70:
 71:      Animal(int, const String&);
 72:      ~Animal(){}
 73:
 74:      int GetAge()
 75:      {
 76:          ASSERT(Invariants());
 77:          return itsAge;
 78:      }
 79:
 80:      void SetAge(int Age)
 81:      {
 82:          ASSERT(Invariants());
 83:          itsAge = Age;
 84:          ASSERT(Invariants());
 85:      }
 86:      String& GetName()
 87:      {
 88:          ASSERT(Invariants());
 89:          return itsName;
 90:      }
 91:
 92:      void SetName(const String& name)
 93:      {
 94:          ASSERT(Invariants());
 95:          itsName = name;
 96:          ASSERT(Invariants());
 97:      }
 98:
 99:      bool Invariants();
100:  private:
101:      int itsAge;
102:      String itsName;
103:  };
104:
105:  // default constructor creates string of 0 bytes
106:  String::String()
107:  {
108:      itsString = new char[1];
109:      itsString[0] = '';
110:      itsLen=0;
111:      ASSERT(Invariants());
112:  }
113:
114:  // private (helper) constructor, used only by
115:  // class methods for creating a new string of
116:  // required size.  Null filled.
117:  String::String(int len)
118:  {
119:      itsString = new char[len+1];
120:      for (int i = 0; i<=len; i++)
121:          itsString[i] = '';
122:      itsLen=len;
123:      ASSERT(Invariants());
124:  }
125:
126:  // Converts a character array to a String
127:  String::String(const char * const cString)
128:  {
129:      itsLen = strlen(cString);
130:      itsString = new char[itsLen+1];
131:      for (int i = 0; i<itsLen; i++)
132:          itsString[i] = cString[i];
133:      itsString[itsLen]='';
134:      ASSERT(Invariants());
135:  }
136:
137:  // copy constructor
138:  String::String (const String & rhs)
139:  {
140:      itsLen=rhs.GetLen();
141:      itsString = new char[itsLen+1];
142:      for (int i = 0; i<itsLen;i++)
143:          itsString[i] = rhs[i];
144:      itsString[itsLen] = '';
145:      ASSERT(Invariants());
146:  }
147:
148:  // destructor, frees allocated memory
149:  String::~String ()
150:  {
151:      ASSERT(Invariants());
152:      delete [] itsString;
153:      itsLen = 0;
154:  }
155:
156:  // operator equals, frees existing memory
157:  // then copies string and size
158:  String& String::operator=(const String & rhs)
159:  {
160:      ASSERT(Invariants());
161:      if (this == &rhs)
162:          return *this;
163:      delete [] itsString;
164:      itsLen=rhs.GetLen();
165:      itsString = new char[itsLen+1];
166:      for (int i = 0; i<itsLen;i++)
167:          itsString[i] = rhs[i];
168:      itsString[itsLen] = '';
169:      ASSERT(Invariants());
170:      return *this;
171:  }
172:
173:  //non constant offset operator, returns
174:  // reference to character so it can be
175:  // changed!
176:  char & String::operator[](int offset)
177:  {
178:      ASSERT(Invariants());
179:      if (offset > itsLen)
180:          return itsString[itsLen-1];
181:      else
182:          return itsString[offset];
183:      ASSERT(Invariants());
184:  }
185:
186:  // constant offset operator for use
187:  // on const objects (see copy constructor!)
188:  char String::operator[](int offset) const
189:  {
190:      ASSERT(Invariants());
191:      if (offset > itsLen)
192:          return itsString[itsLen-1];
193:      else
194:          return itsString[offset];
195:      ASSERT(Invariants());
196:  }
197:
198:  Animal::Animal(int age, const String& name):
199:  itsAge(age),
200:  itsName(name)
201:  {
202:      ASSERT(Invariants());
203:  }
204:
205:  bool Animal::Invariants()
206:  {
207:      PRINT("(Animal Invariants Checked)");
208:      return (itsAge > 0 && itsName.GetLen());
209:  }
210:
211:  int main()
212:  {
213:      const int AGE = 5;
214:      EVAL(AGE);
215:      Animal sparky(AGE,"Sparky");
216:      std::cout << "
" << sparky.GetName().GetString();
217:      std::cout << " is ";
218:      std::cout << sparky.GetAge() << " years old.";
219:      sparky.SetAge(8);
220:      std::cout << "
" << sparky.GetName().GetString();
221:      std::cout << " is ";
222:      std::cout << sparky.GetAge() << " years old.";
223:      return 0;
224:  }


AGE:     5
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)
 (String Invariants Checked)

Sparky is (Animal Invariants Checked)
5 Years old. (Animal Invariants Checked)
 (Animal Invariants Checked)
 (Animal Invariants Checked)
Sparky is (Animal Invariants Checked)
8 years old. (String Invariants Checked)
 (String Invariants Checked)

// run again with DEBUG = MEDIUM

AGE:     5
Sparky is 5 years old.
Sparky is 8 years old.
							

On lines 8–18, the assert() macro is defined to be stripped if DEBUGLEVEL is less than LOW (that is DEBUGLEVEL is NONE). If any debugging is enabled, the assert() macro will work. On lines 20–25, EVAL is declared to be stripped if DEBUGLEVEL is less than MEDIUM; if DEBUGLEVEL is NONE or LOW then EVAL is stripped.


Finally, on lines 27–32, the PRINT macro is declared to be stripped if DEBUGLEVEL is less than HIGH. PRINT is used only when DEBUGLEVEL is high, and you can eliminate this macro by setting DEBUGLEVEL to MEDIUM, and still maintain your use of EVAL and of assert().

PRINT is used within the Invariants() methods to print an informative message. EVAL is used on line 214 to evaluate the current value of the constant integer AGE.

DO use uppercase for your macro names. This is a pervasive convention, and other programmers will be confused if you don't.
DON'T allow your macros to have side effects. Don't increment variables or assign values from within a macro.
DO surround all arguments with parentheses in macro functions.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.251.128