Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced C 1992

.pdf
Скачиваний:
93
Добавлен:
17.08.2013
Размер:
4.28 Mб
Скачать

Part IV • Documenting the Differences

Table 16.1. Operator precedence in C.

Operator

Function

Group

Associativity

( ) [ ]

.

->

++

—-

:>

!

~

-

+

&

*

sizeof

++

—-

(type)

*

/

%

+

-

Function call

1

Left to right

Array element

1

Left to right

Structure or

1

Left to right

union member

 

 

Pointer to

1

Left to right

structure member

 

 

Postfix increment

1

Left to right

Postfix decrement

1

Left to right

Base operator

1

Left to right

Logical NOT

2

Right to left

Bitwise complement

2

Right to left

Arithmetic negation

2

Right to left

Unary plus

2

Right to left

Address

2

Right to left

Indirection

2

Right to left

Size in bytes

2

Right to left

Prefix increment

2

Right to left

Prefix decrement

2

Right to left

Type cast

3

Right to left

Multiplication

4

Left to right

Division

4

Left to right

Remainder

4

Left to right

Addition

5

Left to right

Subtraction

5

Left to right

646

 

 

 

Debugging and Efficiency

 

C C C

 

 

 

 

 

 

16C

 

 

 

 

 

 

C C C

 

 

 

 

 

 

C C

 

 

 

 

 

 

 

 

 

Operator

Function

Group

Associativity

 

 

 

 

 

 

 

 

 

 

 

<<

Left shift

6

Left to right

 

 

 

>>

Right shift

6

Left to right

 

 

 

<

Less than

7

Left to right

 

 

 

<=

Less than or equal to

7

Left to right

 

 

 

>

Greater than

7

Left to right

 

 

 

>=

Greater than or equal to

7

Left to right

 

 

 

==

Equality

8

Left to right

 

 

 

!=

Inequality

8

Left to right

 

 

 

&

Bitwise AND

9

Left to right

 

 

 

^

Bitwise exclusive OR

10

Left to right

 

 

 

|

Bitwise inclusive OR

11

Left to right

 

 

 

&&

Logical AND*

12

Left to right

 

 

 

||

Logical OR*

13

Left to right

 

 

 

 

e1?e2:e3

Conditional*

14

Right to left

 

 

 

=

Simple assignment

15

Right to left

 

 

 

*=

Multiplication

 

 

 

 

 

 

 

assignment

15

Right to left

 

 

 

/=

Division assignment

15

Right to left

 

 

 

%=

Modulus assignment

15

Right to left

 

 

 

+=

Addition assignment

15

Right to left

 

 

 

-=

Subtraction assignment

15

Right to left

 

 

 

<<=

Left-shift assignment

15

Right to left

 

 

 

>>=

Right-shift assignment

15

Right to left

 

 

 

&=

Bitwise-AND assignment

15

Right to left

 

 

 

 

 

 

 

 

continues

647

Part IV • Documenting the Differences

Table 16.1. continued

Operator

Function

Group

Associativity

^=

Bitwise-exclusive-OR

15

Right to left

 

assignment

 

 

|=

Bitwise-inclusive-OR

15

Right to left

 

assignment

 

 

,

Comma*

16

Left to right

 

 

 

 

* Everything preceding the operator is evaluated before the operator is processed

Not Using Proper Array Bounds

Arrays always start at 0. For example, if iArray is defined as

int iArray[25];

the first element is iArray[0], and the last element is iArray[24].

The next example goes a step further:

for (i = 1; i <= 25; i++)

{

iArray[i] = i;

}

This example fails, perhaps causing one of those difficult problems that take so much time to find and correct. The example assigned something to the 26th element of an array that is defined with only 25 elements. Also, the first element is skipped because the for() loop starts at 1, not 0.

A loop that correctly assigns all the elements of iArray follows:

for (i = 0; i < 25; i++)

{

iArray[i] = i;

}

648

Debugging and Efficiency

C C C

 

16C

 

C C C

 

C C

Misused Pointers

When you understand pointers, arrays, and indirection, you know you are a real C programmer. Until that day, you will sometimes use them incorrectly. Pointers and indirection can cause major problems when functions are involved.

If you do not feel comfortable with the concepts of pointers, this is a good time to review Chapter 3, “Pointers and Indirection.”

Order of Evaluation

The order of evaluation for function parameters cannot be guaranteed. The following code is unacceptable because you do not know when YourFunc() will be evaluated:

OurFunct(x = YourFunc(), x);

The second parameter, x, could be either the new value from YourFunc() or what x contained before the call to YourFunc().

The order of evaluation for some other operations is also undefined. For example, in the following code:

int i = 0;

int nArray[10];

nArray[i] = i++;

you cannot determine whether nArray[0] or nArray[1] is assigned; it could be either one. To correct this, ANSI C defines sequence operators, which guarantee that the compiler has evaluated everything that must be evaluated at that point.

Rules for Debugging

When you debug a program, the following rules will help you and perhaps save a bit of time:

1.Assume nothing. Do not assume that a variable has the correct value, even if you see that the variable was assigned a value. Print the variable’s value at the time of the failure. Some other part of the program may have trashed it.

2.When the value of a variable changes to something unexpected, check the logical tests to see whether you have inadvertently assigned a value rather than tested it.

649

Part IV • Documenting the Differences

3.Take a break. By working on something else for a while, more often than not you will suddenly realize the cause of the original problem.

4.If the bug is not in the section of code you originally suspected, it is somewhere else. Do not fall into the trap of thinking, “The bug can’t be in this part of the code because I checked it.”

5.Correct all the compiler’s warnings. Set the warning level as high as possible. If you are writing Windows programs, use STRICT to assure the maximum checking.

6.Pointers cause the most problems. Array-bound overwriting causes almost as many problems. Strings are arrays. In most C compilers, nothing detects when a string’s bounds are exceeded. If the string is on the stack when its bounds are exceeded (because it is an auto variable), the program will probably do some strange things.

7.Back up your disk. One of my first C programs promptly trashed my disk. The only blessing was that I did not have a backup of the program that did the dirty work—it did itself in!

8.From time to time there are compiler bugs, but they are not usually the reason your program fails.

9.Have another C programmer look at your code.

10. When all else fails, rewrite the code so that it works differently.

You are not guaranteed a perfect program just because you avoid these common errors. The most common bug is the simple program logic bug. The program doesn’t work the way you think it does. The program is syntactically correct and does not have any programming bugs, but its logic is incorrect. With a lot of work, a debugger, and patience, you can trace the program’s execution. Eventually, you can find the spot where the results are not what you expected, and perhaps the bug.

Using the assert() Macro

The C language provides the programmer with an important feature, the assert() macro. This macro enables you to make a conditional test, then prints an error message and ends the program if the test fails.

650

Debugging and Efficiency

C C C

 

16C

 

C C C

 

C C

Listing 16.1, ASSERT.C, shows how the assert() macro is used. You must be careful and read the message correctly. Note that assert() activates when the test is FALSE (fails).

Listing 16.1. ASSERT.C.

/* ASSERT, written 1992 by Peter D. Hipson */ /* This program shows the assert() macro. */

#include <limits.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <assert.h>

#define

TRUE

1

#define

FALSE

(!TRUE)

void

main()

 

{

 

 

int

nValue = 1;

 

char

szBuffer[256];

 

while (TRUE)

{

printf(“Enter anything but 25 to test the assert() macro: “); gets(szBuffer);

sscanf(szBuffer, “%d”, &nValue);

assert(nValue == 25);

}

}

651

Part IV • Documenting the Differences

Typical output from the assert() macro follows:

assert

Enter anything but 25 to test the assert() macro: 345 Assertion failed: nValue == 25, file assert.c, line 27

abnormal program termination

The filename and the line number are provided; both of these can be changed with the #line directive.

When you have finished debugging a program, it is not necessary to comment out the assert() calls. Instead, you should define the NDEBUG identifier and simply recompile the program. The preprocessor strips out the assert() calls for you. Later, if you discover that the program still has bugs, a quick recompile without defining the NDEBUG identifier will restore all your assert() calls.

Debug Strings and Messages

Writing lines to the screen, a communications port, the printer, or a file while a program is executing can be a powerful debugging tool. Because this technique does not require a debugger, it can be easy to implement.

You can trace the entire program’s execution using such a technique, but you must carefully plan your debugging session. Listing 16.2, DBGSTRNG.C, shows the use of a debugging output function.

Listing 16.2. DBGSTRNG.C.

/* DBGSTRNG, written 1992 by Peter D. Hipson */ /* This program has a debug output function */

#include <limits.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <assert.h>

#define

TRUE

1

#define

FALSE

(!TRUE)

652

Debugging and Efficiency

C C C

 

16C

 

C C C

 

C C

#undef DebugString

#ifdef NDEBUG

#define DebugString(exp, file, msg)

((void)0)

#else

void _ _cdecl _DebugString(void *, FILE *, void *, void *, unsigned);

#define DebugString(exp, file, msg) \

( (exp) ? _DebugString(#exp, file, msg, _ _FILE_ _, _ _LINE_ _) : (void) 0)

#endif /* NDEBUG */

void

main()

{

 

int

nValue = 1;

char

szBuffer[256];

while (nValue)

{

DebugString(nValue >= 15 && nValue <= 20, stderr, szBuffer);

printf(“Enter 10 to 25 for message, 0 to end loop: “); gets(szBuffer);

sscanf(szBuffer, “%d”, &nValue);

DebugString(nValue >= 10 && nValue <= 25, stderr, szBuffer);

}

}

void _ _cdecl _DebugString( void * szTest,

FILE * File,

653

Part IV • Documenting the Differences

void * szMessage, void * szFile, unsigned nLine)

{

fprintf(File,

“\n’%s’ MSG ‘%s’ IN ‘%s’ LINE ‘%d’\n”, szTest,

szMessage,

szFile,

nLine);

}

For example, using the _DebugString() function to debug a program run produces the following typical run of DBGSTRNG.C:

dbgstrng

Enter 10 to 25 for message, 0 to end loop: 10

‘nValue >= 10 && nValue <= 25’ MSG ‘10’ IN ‘dbgstrng.c’ LINE ‘48’ Enter 10 to 25 for message, 0 to end loop: 1

Enter 10 to 25 for message, 0 to end loop: 14

‘nValue >= 10 && nValue <= 25’ MSG ‘14’ IN ‘dbgstrng.c’ LINE ‘48’ Enter 10 to 25 for message, 0 to end loop: 15

‘nValue >= 10 && nValue <= 25’ MSG ‘15’ IN ‘dbgstrng.c’ LINE ‘48’

‘nValue >= 15 && nValue <= 20’ MSG ‘15’ IN ‘dbgstrng.c’ LINE ‘41’ Enter 10 to 25 for message, 0 to end loop: 19

‘nValue >= 10 && nValue <= 25’ MSG ‘19’ IN ‘dbgstrng.c’ LINE ‘48’

‘nValue >= 15 && nValue <= 20’ MSG ‘19’ IN ‘dbgstrng.c’ LINE ‘41’ Enter 10 to 25 for message, 0 to end loop: 0

When using a function such as DebugString(), you are not restricted to using stderr. Instead, you might open a communications port or the printer as the debugging file or write the output to a file. When writing to a file, be sure you flush

654

Debugging and Efficiency

C C C

 

16C

 

C C C

 

C C

the file’s buffer after each write. Otherwise, you could lose the most important part of the program’s output if the program fails without closing the file or flushing the buffer.

When programming under Windows, you could call OutputDebugString() rather than fprintf(). The OutputDebugString() Windows function writes a string to the debugging terminal (or whatever destination you choose if you use the DBWIN program), without halting the program or destroying the contents of the Window’s screen.

Debuggers

Every compiler comes with a debugger. Regardless of the environment (DOS, Windows, OS/2, or UNIX), the concept behind the use of a debugger is the same: to allow access to various commands while the program runs so that you can monitor its execution.

Typical services offered by most debuggers follow:

Execution in an environment similar to the typical operating environment. This involves using as little memory as possible (a requirement that was difficult with DOS on a PC, but is easier with the rapid acceptance of protected-mode operating systems such as Windows and OS/2) and not interfering with the output device (the screen). To avoid interfering with the screen, the debugger generally uses a serial terminal, a second monitor (often a monochrome adapter, or MDA), or two workstations (with network-based debugging).

Memory examination. This includes simple memory dumps, examination of external variables (by name), and examination of local variables.

Memory modification. This generally is limited to changing variables, both global and local.

Program breakpoints. At a program breakpoint (a specified point of interruption), the debugger is given control before the line or instruction is executed. At a breakpoint, you might examine variables, registers, or memory.

Memory breakpoints. These are similar to program breakpoints, but the memory specified need not be an instruction. For example, the breakpoint

655