?? chapter2.html
字號:
<html><head><title>Writing Bug-Free C Code: Know Your Environment</title></head><body><center><font size="+3">Chapter 2: Know Your Environment</font><br><a href="index.html">Writing Bug-Free C Code</a><br></center><br><center><table><tr><td valign=top><small><a href="#theclanguage">2.1 The C Language</a><br><a href="#cpreprocessor">2.2 C Preprocessor</a><br><a href="#puzzles">2.3 Programming Puzzles</a><br><small></td><td width=30> </td><td valign=top><small><a href="#windows">2.4 Microsoft Windows</a><br><a href="#summary">2.5 Chapter Summary</a><br></small></td></tr></table></center><br><br>Before you can efficiently institute new programming methodologies that help eliminate bugs, you need to understand the features available to you in your programming environment. <br><br>Quite often, it is beneficial to ask yourself why a particular feature is present in the environment. If you are already using the feature, great, but is there another way you could also be using it? If you are not using the feature, try to think of why the feature was added, because someone needed and requested the feature. Why did they need the feature and how are they using it? <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Try to become an expert in your environment. </tr></td></table></blockquote>In the process of learning about all the features of your environment, you may eventually become an expert on it. And the learning process never stops. With each new version of the tools in your environment, look in the manuals to find out what new features have been added. Sometimes features are even removed!<br><a name="theclanguage"><br></a><big><b>2.1 The C Language</b></big> <br><br><table bgcolor="#F0F0F0"><tr><td><img src="./windows.gif">Microsoft C 8.0 (a.k.a. Microsoft Visual C++ 1.0) comes in two editions: the standard edition and the professional edition. The standard edition is targeted to the part-time programmer, the professional edition to the full-time programmer. The professional edition comes with more manuals and, more important, the Windows 3.1 SDK. </td></tr></table> <br>No matter which C compiler you are using, it is important that you fully understand the C programming environment. <br><br><b>2.1.1 The Array Operator</b> <br><br>You've used the array operator to index an array before, but have you given any thought to how the compiler interprets the array operator? You may not know how it does, especially if you learned C after learning another language first! <br><br>Suppose you have a character array called buffer and an integer <tt>nIndex</tt> used to index into the character array. How do you obtain a character from the character array? Most everyone will tell you <tt>buffer[nIndex]</tt>, namely, the array name followed by the array index in brackets. <br><br>Did you know that <tt>nIndex[buffer]</tt> also works! This syntax is not recommended, but it does work. Do you know why? <br><br>The reason that <tt>buffer[nIndex]</tt> references the same character as <tt>nIndex[buffer]</tt> is clearly stated in §A7.3.1 of <a href="references.html">The C Programming Language (2nd ed.)</a> as follows: <blockquote><i> A postfix expression followed by an expression in square brackets is a postfix expression denoting a subscripted array reference. One of the two expressions must have type "pointer to T", where T is some type, and the other must have integral type; the type of the subscript expression is T. The expression E1[E2] is identical (by definition) to *((E1)+(E2)). </i></blockquote>The key to understanding array references is to understand "The expression E1[E2] is identical (by definition) to *((E1)+(E2))." In terms of the example, buffer[nIndex] is identical to *((buffer)+(nIndex)). Addition is commutative (i.e., 3 + 4 equals 4 + 3), which makes *((buffer)+(nIndex)) identical to *((nIndex)+(buffer)), which is nIndex[buffer]. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Array reference E1[E2] is identical to *((E1)+(E2)). </tr></td></table></blockquote><b>2.1.2 The Structure Pointer Operator</b> <br><br>You all know that if p is a pointer to a structure, that p->structure-member refers to a particular member of the structure. Suppose that you could no longer use the structure pointer operator ->. Could you somehow use the other operators in C and continue coding? <br><br>The answer is yes. Instead of p->structure-member, use (*p).structure-member. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> If p is a pointer to a structure, the structure reference p->member is identical to (*p).member. </tr></td></table></blockquote>Again, this is not a recommended syntax, but it is good to understand that it does work and why it works. It is spelled out in §A7.3.3 of <a href="references.html">The C Programming Language</a>. <br><br><a name="c8warning"></a><table bgcolor="#F0F0F0"><tr><td><img src="./windows.gif"> <b>2.1.3 Use the Highest Compiler Warning Level</b> <br><br>The warning and error messages of the Microsoft C compiler are getting better with each release of the compiler. I strongly recommend that you compile your code at the highest warning level available to you. For Microsoft C8, this is warning level four. The command line option for this is /W4. <br><br>At warning level four, there may be some warning messages that you want to totally ignore. An example in Microsoft C8 are warnings produced by unused declared inline functions. I declare my inline functions in an include file. The source files then use only those inline functions that are needed. Unfortunately, the unreferenced inline functions produce the unreferenced local function has been removed warning message, which is warning number C4505. To disable this warning, you can use the warning #pragma in your include files. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Pragma to disable warning number 4505 in Microsoft C8</b>#pragma warning(disable:4505)</pre></tr></td></table><br>Some of the useful, interesting warning messages that you can get from the Microsoft C8 compiler are as show in Table 2-1.<br><br><ul><table border=1 cellspacing=0><tr><td><b>Error Number</b></td><td><b>Error Message</b></td></tr><tr><td>C4019</td><td>empty statement at global scope</td></tr><tr><td>C4100</td><td>unreferenced formal parameter</td></tr><tr><td>C4101</td><td>unreferenced local variable</td></tr><tr><td>C4701</td><td>local variable "identifier" may be used without having been initialized. This warning is given only when compiling with the C8 /Oe global register allocation command-line option.</td></tr><tr><td>C4702</td><td>unreachable code. This warning is given only when compiling with one of the C8 global optimization options (/Oe, /Og or /Ol)</td></tr><tr><td>C4705</td><td>statement has no effect</td></tr><tr><td>C4706</td><td>assignment within conditional expression</td></tr><tr><td>C4723</td><td>potential divide by 0</td></tr></table>Table 2-1: Some interesting Microsoft C8 warning messages.</ul></td></tr></table><br><a name="compilerassert"></a><b>2.1.4 CompilerAssert()</b> <br><br>CompilerAssert() is designed to provide error checking at compile-time for assumptions made by the programmer at design-time. These assumptions must be documented within comments in the code as well, but why not also have a compile-time check made whenever possible? <br><br>The reasoning behind this is simple. What if a new programmer is working on a section of code in which an assumption is made and the programmer inadvertently changes the code so that the assumption is now invalid? The bug may not show up for a long time. However, if the problem could have been flagged at compile-time, it would have saved a lot of time and effort. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Use CompilerAssert() to verify design-time assumptions at compile-time. </tr></td></table></blockquote>The trick in designing CompilerAssert() is to do it in such a way so that the compiler catches the error at compile-time and yet does not produce any run-time code. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>CompilerAssert() define</b>#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]</pre></tr></td></table> <blockquote><small><i><font color="blue"> UPDATE: (exp)?1:-1 used to be (exp)?1:0 but it appears that there are some UNIX C compilers that do <u>not</u> complain about an array of zero length even though the standard says the array size must be greater than zero. So 0 was changed to -1 because every compiler must complain about an array with negative size. </i></font></small></blockquote>This CompilerAssert() works because it is really just an array declaration and the array bound for an array declaration must be a constant expression that is greater than zero. This is documented in §A8.6.2 of <a href="references.html">The C Programming Language</a>. Using extern makes sure that no code is generated. <br><br>The argument that you pass to CompilerAssert() should be a boolean expression. This means that it evaluates to zero or one. If it is one, the CompilerAssert() is valid and so is the array declaration. If it is zero, the CompilerAssert() is invalid and so is the array declaration. This will cause the compiler to notify you of an error and the compilation will stop. <br><br>An added bonus is that the argument to CompilerAssert() must be able to be evaluated at compile-time because the array bound of the array declaration must be a constant expression. This makes it impossible for you to misuse CompilerAssert() and have it accidentally generate some code. <br><br>Another bonus is that CompilerAssert() is not limited to use only in functions or source files. It can even be used in include files as needed! <br><br>It can also be used more than once in the same scope with no problems. This is due to the usage of extern. Using extern declares the type of _CompilerAssert; it does not define it. Declaring the type of a variable name more than once is OK (as long as the data type is the same). Also, the array size of (exp)?1:-1 changes any non-zero (exp) to one. If this were not done and two different (exp) values were used by two CompilerAssert()'s, the compiler would complain. <br><br>Some linkers may require that a _CompilerAssert variable exist while other linkers will optimize out the declared but unused variable. If the linker that you use requires a _CompilerAssert variable, place char _CompilerAssert[1]; in any one of your source files to fix the linker error. The linker that comes with Microsoft C8 does not require this fix. <br><br>Let's say that you have coded a function that for some reason requires an internal buffer to be a power of two. Assuming that an <a href="#ispower2">ISPOWER2() macro</a> already exists, how would you CompilerAssert() this? <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Sample CompilerAssert() usage</b>...char buffer[BUFFER_SIZE];...CompilerAssert(ISPOWER2(sizeof(buffer)));...</pre></tr></td></table> <br>You should test CompilerAssert() to verify that it is working in your environment. Under Microsoft C8 and UNIX, the following program will produce a 'negative subscript' error message when compiled. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Testing CompilerAssert(), file test.c</b>#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]int main(void){ /*--- A forced CompilerAssert ---*/ long lVal; CompilerAssert(sizeof(lVal)==0); return 0;} /* main */ <br><br><b>Compiling test.c under Microsoft C8, should produce an error C2118</b>cl test.c<font color="blue">test.c(7) : error C2118: negative subscript</font> <br><br><b>Compiling test.c under UNIX cc, should produce an error message</b>cc test.c<font color="blue">test.c: In function `main':test.c:7: size of array `_CompilerAssert' is negative</font></pre></tr></td></table> <br><b>2.1.5 Variable Declarations</b> <br><br>Have you ever wondered why you have to declare c is a pointer to a char as char*c? Do you know if there is a difference between char buffer[80] and char (*buffer)[80]? At first, C's declaration syntax may appear difficult, but there is a structure behind it that I am going to try to explain. I firmly believe that the better you understand it, the better C programmer you will be. <br><br>What is the result of 2 + 3 * 5? It is 17, but why? Why did you multiply before you added? The answer is that there is a precedence relationship among the operators. What is the result of (2 + 3) * 5? It is 25 because the parentheses override the natural precedence relationship. There is a direct analogy to reading C variable declarations. <br><br>You have basically four things to look for: First, parentheses, (...); second, arrays, [...]; third, functions, (); fourth, pointers, *. The parentheses, just as in expressions, can override precedence relationships. Arrays [] and functions () have a higher precedence than pointers *. Since arrays and functions always appear to the right of a variable name in a declaration and pointers always appear to the left of the variable name, this in effect tells you that you always move right before moving left when reading the variable declaration. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Always try to move right before moving left when reading a variable declaration. </tr></td></table></blockquote>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -