📄 chapter7.html
字号:
destroying the last object for this class, the memory associated with the resource is freed. <br><br>Global variables to support individual object instances should not be allowed. Global variables that support a class of objects as a whole are permitted. Because these global variables support a class, their use is limited to the source file that declares the class. This can be enforced by using static on these variables, ensuring that the variables are not visible in other source files.<br><a name="loopvars"><br></a><big><b>7.7 Loop Variables</b></big> <br><br>Variables used to control for loops should not be used outside the loop itself. The LOOP() macro (see <a href="chapter2.html#loopmacros">§2.2.5</a>) was designed to enforce this rule. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Loop variables should not be used outside the loop itself. </tr></td></table></blockquote>This rule makes code easier to maintain. While you are writing a function, you understand it thoroughly and are unlikely to make a mistake in using the loop variable after the loop has exited. However, when you or one of your coworkers modifies the loop next year, the assumptions under which the looping variable is used could easily be invalidated. <br><br>It is better to be safe than sorry.<br><a name="highwarnings"><br></a><big><b>7.8 Use the Highest Compiler Warning Level</b></big> <br><br>Today's compilers are pretty smart, but only if you tell them to be. By default, most compilers use a low warning level and use more advanced error checking only if instructed to do so. This is almost always done to remain compatible with older code. You can imagine the number of support calls a compiler vendor would get if the old code that used to compile fine all of a sudden started producing a lot of warning messages under a new release of the compiler. <br><br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> For more information on setting the compiler warning level in Microsoft C8, see <a href="chapter2.html#c8warning">§2.1.3</a>.</td></tr></table><br><a name="usestatic"><br></a><big><b>7.9 Use "static" to Localize Knowledge</b></big> <br><br>Using static can sometimes be confusing because it appears to mean different things in different contexts. A simple rule that helps clear things up is that using static assigns permanent storage to an object and limits the visibility of the object to the scope in which it is defined. There are three basic ways in which static can be used. <br><br>Static function declaration. You have already seen static used in this case with the LOCAL macro (see <a href="chapter6.html#localfunctions">§6.6.7</a>) Since a function already has permanent storage, this part of the rule is redundant. The scope of a function is file scope, so static limits the visibility of the function to the rest of the source file in which it is defined. <br><br>External static variables. When a variable is defined at file scope, it already has permanent storage and is visible to all source files. Using static makes the variable invisible to other source files. Again, the variable definition already has permanent storage, so this part of the rule is redundant. <br><br>Internal static variables. When a variable is defined within a function, it is called an automatic variable. Automatic variables have no persistence across function call invocations. In other words, if a value is assigned to an automatic variable in a block, that value is lost when the block is exited. In almost all cases, this is the desired behavior. However, using static assigns permanent storage to the variable so that a value assigned to the variable is not lost. The scope of the variable is also limited to the block in which it is defined. An internal static variable is just like an external static variable except that the visibility of the variable is limited to a block. <br><br>It is also important to understand how an initialized static variable behaves. Consider the following code. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>An example in using static</b>void Testing( void ){ LOOP(3) { static int nTest1=100; int nTest2=100; printf( "nTest1=%d, nTest2=%d\n", nTest1, nTest2 ); ++nTest1; ++nTest2; } ENDLOOP/* Testing */<br><b>Output from calling Testing() three times</b>nTest1=100, nTest2=100nTest1=101, nTest2=100nTest1=102, nTest2=100nTest1=103, nTest2=100nTest1=104, nTest2=100nTest1=105, nTest2=100nTest1=106, nTest2=100nTest1=107, nTest2=100nTest1=108, nTest2=100</pre></tr></td></table> <br>As you can see from this example, nTest1 is initialized only once, while nTest2 is initialized on each pass through the block. The rule is that the initialization of any static variables takes place at compile-time and that the initialization of automatic variables takes place at run-time. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Static variables are initialized once at compile-time. Automatic variables are initialized as needed at run-time. </tr></td></table></blockquote><a name="blockvars"><br></a><big><b>7.10 Place Variables in the Block Needed</b></big> <br><br>How many times have you tracked down a bug only to realize that you used an automatic variable before it was properly initialized, or used the variable well after it should have been, when it no longer contained an appropriate value? While this may not happen to you as you write the function, it becomes a lot more likely when you go back to the code at a later date and modify it. <br><br>The solution to this problem is to define variables only in the innermost scope in which they are needed. A new scope is created any time you use a begin brace {. The scope is terminated by an ending brace }. This means that your if statements, while statements, and so on. create new scopes. Variables defined within a scope are visible only within that scope. As soon as the scope ends, so does your access to the variable. Consider the following code fragment. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Limiting the scope of a variable</b>LOOP(strlen(pString)) { char c=pString[loop]; ... } ENDLOOP</pre></tr></td></table> <br>In this example, c is visible only within the loop. As soon as the loop exits, c is longer visible. In fact, it can be defined and reused in another scope. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Define variables in the scope in which they are needed. </tr></td></table></blockquote>In standard C, variables can be defined only at the beginning of a scope before the main body of code. In C++, variables can be defined wherever a statement is valid. <br><br>A useful #define for both C and C++ is the NewScope define. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>NewScope define</b>#define NewScope</pre></tr></td></table> <br>NewScope is a syntactical place holder (defined to be nothing) that allows a new scope to be introduced into a program. It also allows for a natural indentation style. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Using NewScope</b>void APIENTRY Function( args ){ /*--- Comment ---*/ (code block) /*--- Using NewScope ---*/ NewScope { type var; (code block that uses var) }} /* Function */</pre></tr></td></table> <br>NewScope is useful in both C and C++ because it allows variables to be created that are private to a block. As soon as the block exits, the variables declared in the block are no longer visible and cannot be referenced.<br><a name="stackarrays"><br></a><big><b>7.11 Arrays on the Stack</b></big> <br><br>When using arrays that are declared on the stack, you must be careful not to return a pointer to one of these arrays back to the calling function. Consider the following example. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>A program with a subtle bug</b>#include <stdlib.h>#include <stdio.h>#include <string.h>char *myitoa( int nNumber ){ char buffer[80]; sprintf( buffer, "%d", nNumber ); return (buffer);}int main(void){ printf( "Number = %s\n", myitoa(234) ); return 0;}</pre></tr></td></table> <br>This program contains a subtle bug in that myitoa() is returning the address of buffer, an array on the stack. It is subtle because despite this bug, the program still works properly! <br><br>It is a bug to return the address of an array on the stack back to a calling function because after the function returns, the array is now in the part of the stack that no function owns. The problem with this bug is that the code will work unless the array gets overwritten by making another function call, although, making a function call is no guarantee that buffer will be overwritten. <br><br>This bug is exactly like keeping a pointer around a block of memory that was freed. Unless the memory is reused and overwritten, accessing an object through the memory pointer will continue to work. As soon as the block of memory gets overwritten, you have a subtle problem to track down.<br><a name="pointers"><br></a><big><b>7.12 Pointers Contain Valid Addresses or NULL</b></big> <br><br>The reasoning behind this is simple. It helps find bugs. If all pointers contain either a valid address or NULL, then accessing the valid address does not cause the program to fault. However, accessing the NULL pointer under most protected environments causes the program to fault and you have found your bug. <br><br>Consider what would happen if you have a pointer to a memory object, the memory object is freed and the pointer is not set to NULL. The pointer still contains the old address, an address to an invalid object. However, the address itself under most environments is still valid and accessing the memory does not cause your program to fault. This is a problem and the source of a lot of bugs. <br><br>The reason for being able to access the memory of a freed object is that most memory managers simply add the memory back into the pool of free memory. Rarely do they actually mark this memory as being inaccessible to the program. It is time-consuming to communicate with the operating system on every allocation and deallocation request. Instead, a memory pool is maintained. Only when this pool is exhausted does the heap manager ask the operating system for more memory. <br><br>To help enforce this policy, macros that interface with the memory manager should be designed and used exclusively. An example of this is NEWOBJ() or FREE() for class objects (see <a href="chapter4.html#managememory">§4.5</a>).<br><a name="avoidtypecasts"><br></a><big><b>7.13 Avoid Type Casting Whenever Possible</b></big> <br><br>Type casting, by design, bypasses the type checking of the compiler. In essence, type casting tells the compiler that you know what you are doing and not to complain about it. The problem with this is that your environment may change, you may be porting the software to
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -