Team LiB
Previous Section Next Section

6.14. Using the Preprocessor for Debugging

6.14. 使用预处理器进行调试

In Section 2.9.2 (p. 71) we learned how to use preprocessor variables to prevent header files being included more than once. C++ programmers sometimes use a technique similar to header guards to conditionally execute debugging code. The idea is that the program will contain debugging code that is executed only while the program is being developed. When the application is completed and ready to ship, the debugging code is turned off. We can write conditional debugging code using the NDEBUG preprocessor variable:

第 2.9.2 节介绍了如何使用预处理变量来避免重复包含头文件。C++ 程序员有时也会使用类似的技术有条件地执行用于调试的代码。这种想法是:程序所包含的调试代码仅在开发过程中执行。当应用程序已经完成,并且准备提交时,就会将调试代码关闭。可使用 NDEBUG 预处理变量实现有条件的调试代码:

     int main()
     {
     #ifndef NDEBUG
     cerr << "starting main" << endl;
     #endif
     // ...

If NDEBUG is not defined, then the program writes the message to cerr. If NDEBUG is defined, then the program executes without ever passing through the code between the #ifndef and the #endif.

如果 NDEBUG 未定义,那么程序就会将信息写到 cerr 中。如果 NDEBUG 已经定义了,那么程序执行时将会跳过 #ifndef#endif 之间的代码。

By default, NDEBUG is not defined, meaning that by default, the code inside the #ifndef and #endif is processed. When the program is being developed, we leave NDEBUG undefined so that the debugging statements are executed. When the program is built for delivery to customers, these debugging statements can be (effectively) removed by defining the NDEBUG preprocessor variable. Most compilers provide a command line option that defines NDEBUG:

默认情况下,NDEBUG 未定义,这也就意味着必须执行 #ifndef#endif 之间的代码。在开发程序的过程中,只要保持 NDEBUG 未定义就会执行其中的调试语句。开发完成后,要将程序交付给客户时,可通过定义 NDEBUG 预处理变量,(有效地)删除这些调试语句。大多数的编译器都提供定义 NDEBUG 命令行选项:

     $ CC -DNDEBUG main.C

has the same effect as writing #define NDEBUG at the beginning of main.C.

这样的命令行行将于在 main.c 的开头提供 #define NDEBUG 预处理命令。

The preprocessor defines four other constants that can be useful in debugging:

预处理器还定义了其余四种在调试时非常有用的常量:

__FILE__ name of the file.

__FILE__ 文件名

__LINE__ current line number.

__LINE__ 当前行号

__TIME__ time the file was compiled.

__TIME__ 文件被编译的时间

__DATE__ date the file was compiled.

__DATE__ 文件被编译的日期

We might use these constants to report additional information in error messages:

可使用这些常量在错误消息中提供更多的信息:

     if (word.size() < threshold)
         cerr << "Error: " << _ _FILE_ _
              << " : line " << _ _LINE_ _ << endl
              << "       Compiled on " << _ _DATE_ _
              << " at " << _ _TIME_ _ << endl
              << "      Word read was " << word
              << ": Length too short" << endl;

If we give this program a string that is shorter than the threshold, then the following error message will be generated:

如果给这个程序提供一个比 threshold 短的 string 对象,则会产生下面的错误信息:

     Error: wdebug.cc : line 21
               Compiled on Jan 12 2005 at 19:44:40
               Word read was "foo": Length too short

Another common debugging technique uses the NDEBUG preprocessor variable and the assert preprocessor macro. The assert macro is defined in the cassert header, which we must include in any file that uses assert.

另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。

A preprocessor macro acts something like a function call. The assert macro takes a single expression, which it uses as a condition:

预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:

     assert(expr)

As long as NDEBUG is not defined, the assert macro evaluates the condtion and if the result is false, then assert writes a message and terminates the program. If the expression has a nonzero (e.g., true) value, then assert does nothing.

只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为 falseassert 输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true)值,则 assert 不做任何操作。

Unlike exceptions, which deal with errors that a program expects might happen in production, programmers use assert to test conditions that "cannot happen." For example, a program that does some manipulation of input text might know that all words it is given are always longer than a threshold. That program might contain a statement such as:

与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用 assert 来测试“不可能发生”的条件。例如,对于处理输入文本的程序,可以预测全部给出的单词都比指定的阈值长。那么程序可以包含这样一个语句:

     assert(word.size() > threshold);

During testing the assert has the effect of verifying that the data are always of the expected size. Once development and test are complete, the program is built and NDEBUG is defined. In production code, assert does nothing, so there is no run-time cost. Of course, there is also no run-time check. assert should be used only to verify things that truly should not be possible. It can be useful as an aid in getting a program debugged but should not be used to substitute for run-time logic checks or error checking that the program should be doing.

在测试过程中,assert 等效于检验数据是否总是具有预期的大小。一旦开发和测试工作完成,程序就已经建立好,并且定义了 NDEBUG。在成品代码中,assert 语句不做任何工作,因此也没有任何运行时代价。当然,也不会引起任何运行时检查。assert 仅用于检查确实不可能的条件,这只对程序的调试有帮助,但不能用来代替运行时的逻辑检查,也不能代替对程序可能产生的错误的检测。

Exercises Section 6.14

Exercise 6.25:

Revise the program you wrote for the exercise in Section 6.11 (p. 214) to conditionally print information about its execution. For example, you might print each word as it is read to let you determine whether the loop correctly finds the first duplicated word that begins with an uppercase letter. Compile and run the program with debugging turned on and again with it turned off.

修改第 6.11 节习题所编写的程序,使其可以有条件地输出运行时的信息。例如,可以输出每一个读入的单词,用来判断循环是否正确地找到第一个连续出现的以大写字母开头的单词。分别在打开和关闭调试器的情况下编译和运行这个程序。

Exercise 6.26:

What happens in the following loop:

下面循环会导致什么现象的发生:

     string s;
     while (cin >> s) {
        assert(cin);
        // process s
     }

Explain whether this usage seems like a good application of the assert macro.

解释这种用法是否是 assert 宏的一种恰当应用。

Exercise 6.27:

Explain this loop:

解释下面的循环:

     string s;
     while (cin >> s && s != sought) { } // empty body
     assert(cin);
     // process s


Team LiB
Previous Section Next Section