📄 chapter2.html
字号:
Consider the order in which arguments should be pushed onto the stack. Should the first argument be pushed first or should the last argument be pushed first? <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Printf example</b>printf( "Testing %s %d", pString, nNum );</pre></tr></td></table> <br>If the first argument is pushed first (arguments are pushed left to right), the variable argument section is pushed last. In other words, the address of the format string is pushed, then a variable number of arguments is pushed and finally the function call is made, which pushes the return address. The problem is that the relative stack location of the format string address changes depending upon the number of arguments. (See Figure 2-1). <blockquote> <img src="images/fig2-1.gif"><br> Figure 2-1: Pushing function arguments left to right. </blockquote>If the last argument is pushed first (arguments are pushed right to left), the variable argument section is pushed first. In other words, the variable number of arguments are pushed right to left, then the address of the format string is pushed and finally the function call is made, which pushes the return address. The relative stack location of the format string address is now adjacent to the return address. (See Figure 2-2). <blockquote> <img src="images/fig2-2.gif"><br> Figure 2-2: Pushing function arguments right to left. </blockquote>The solution. In order to support passing a variable number of arguments to functions, it appears that the arguments to a function must be pushed right to left and that the caller, not the callee, is responsible for removing the pushed arguments from the stack.</td></tr></table><br><table bgcolor="#F0F0F0"><tr><td><img src="./windows.gif"> <b>2.1.11 Calling Conventions</b>Were you aware there is more than one way for a function call in Microsoft C8 to be implemented? Each method has advantages and disadvantages. <br><br><a name="ccalling"></a><i>C calling convention</i>. When a function call is made using the C calling convention, the arguments are pushed onto the stack right to left and the caller is responsible for removing the pushed arguments. This convention was designed to allow for a variable number of arguments to be passed into a function, which can be an incredibly valuable tool (e.g., the printf() function). The disadvantage is that the instructions needed to clean up the stack are performed after every call to the function. If you are making a lot of calls in your program, this extra code space adds up. <br><br><a name="pascalcalling"></a><i>Pascal calling convention</i>. When a function call is made using the Pascal calling convention, the arguments are pushed onto the stack left to right and the callee is responsible for removing the pushed arguments. This convention does not allow for a variable number of arguments. It does, however, conserve code space since the stack is cleaned up by the callee and not the caller. Therefore the cleanup is performed only once. This calling convention is used by all the Windows API functions, except for DebugOutput() and wsprintf(), which follow the C calling convention. <br><br><a name="fastcallcalling"></a><i>Fastcall calling convention</i>. When a function call is made using the fastcall calling convention, an attempt is made by the compiler to pass as many arguments as possible through the CPU's registers. Those arguments that cannot be passed through registers are passed to the function following the Pascal calling convention. There are restrictions on when this calling convention can be used. Refer to the Microsoft C8 reference manual for more information. This calling convention is used for all functions that are local (private) to a module (see <a href="chapter6.html#localfunctions">§6.6.7</a>).</td></tr></table><br><table bgcolor="#F0F0F0"><tr><td><img src="./windows.gif"> <b>2.1.12 Code Generation</b> <br><br>It is sometimes incredibly valuable to see the code that the Microsoft C8 compiler is producing. To do this, use the /Fc command line option. The resulting .cod file contains the mixed source code and assembly code produced. <br><br>The primary reason to do this is to make sure that the programming methodologies that you institute are not adding an abnormal amount of processing-time overhead to the code. <br><br>In a few rare circumstances, you can use the code generation option to track down compiler bugs. <br><br>I use this option at times because I am just curious to see how good the optimizing compiler is. Sometimes I could swear at the compiler and other times I am amazed at what it is able to do. Microsoft C8 can really do a great job of code optimization. An example that I like to show is the absolute value macro. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Absolute value macro</b>#define ABS(x) (((x)>0)?(x):-(x))</pre></tr></td></table> <br>When the ABS() macro is used on a 16-bit integer, the generated code without optimizations looks like the following. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Code generated by absolute value macro without optimizations</b> cmp variable reference,OFFSET 0 ;; is number negative? jle L1 ;; .yes, handle neg number mov ax, variable reference ;; no, get number jmp L2 ;; .and exitL1: mov ax, variable reference ;; get negative number neg ax ;; .and make positiveL2:</pre></tr></td></table> <br>When the ABS() macro is used on a 16-bit integer, the generated code with optimizations looks like the following.<br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Code generated by absolute value macro with /Os optimizations</b>1. mov ax, variable reference ;; get number2. cwd ;; sign extend3. xor ax,dx ;; if number is positive, do4. sub ax,dx ;; .nothing, otherwise negate</pre></tr></td></table> <br>This optimized assembly code is a work of art. In step 1, the variable is moved into register ax. Step 2 sign extends ax into dx. So, if the number is positive, dx gets set to 0; otherwise the number is negative and dx gets set to all bits turned on (0xFFFF; negative 1). Provided the number is positive, dx contains 0, so steps 3 and 4 leave the number unchanged. However, if the number is negative, dx contains 0xFFFF, so steps 3 and 4 perform a two's complement, which negates the number. Not bad!</td></tr></table><br><a name="danglingelse"></a><b>2.1.13 Dangling else Problem</b><br><br>Consider the following code.<br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Code sample showing dangling else problem</b>if (test1) if (test2) { ... }else { ... }</pre></tr></td></table> <br>The problem with this code is that the else does not belong to the test1 if statement, but instead it belongs to the test2 if statement. An even bigger problem is that the code may have worked at some point in the past before a maintenance programmer included the test2 if statement which previously did not exist. The solution to the immediate problem is simple, however; add a begin and end brace to the test1 if statement as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Code sample rewritten to eliminate dangling else problem</b>if (test1) { if (test2) { ... } }else { ... }</pre></tr></td></table> <br>A programming methodology that I have instituted to completely eliminate any dangling else problems is to say that every if and every else must have begin and end braces. No exceptions. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Every if block of code and else block of code must have begin and end braces. </tr></td></table></blockquote><b>2.1.14 Problems with strncpy()</b> <br><br>A function in the C library that I never liked much is strncpy(). Do you know what this function does? Do you know what is going to happen in the following program? <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>strncpy() problem, but code may still work</b>#include <stdio.h>#include <string.h>int main(void){ char buffer[11]; strncpy( buffer, "hello there", sizeof(buffer) ); printf( "%s", buffer ); return 0;} /* main */</pre></tr></td></table> <br>strncpy(string1, string2, n) works by copying n characters of string2 to string1. However, if there is no room in string1 for the null character at the end of string2, it is not copied into the string1 buffer. In other words, the buffer is not properly null terminated. If n is greater than the length of string2, string1 is padded with null characters up to length n. <br><br>There are two major problems with strncpy. The first is that string1 may not be properly null terminated. The second is the execution overhead of null padding. <br><br>In the above example, there is no terminating null for the string copied into buffer. Who knows what the printf() in the example prints out? <br><br>The problem is even worse. Depending upon your computer architecture, the above example may still work every time! It all depends upon the alignment of data types. In most cases, 12 bytes are allocated for buffer instead of 11 due to data alignment concerns of the underlying CPU. If this extra byte just happens to be the null character, the code works. If not, the code fails by printing out more than it should in the printf() statement. It will just keep on printing characters until it reaches a null character. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Replace questionable and confusing library functions. Strncpy() is an example of a bad function. </tr></td></table></blockquote><table bgcolor="#F0F0F0"><tr><td><img src="./windows.gif">A strncpy() bug existed in the STARTDOC printer escape under Windows 2.x (STARTDOC tells the system that you are starting to print a new document and it also names the document). If you passed in a string larger than the internal buffer, which was 32 bytes, the buffer did not get properly null terminated.</td></tr></table><br><b>2.1.15 Spell Default Correctly</b> <br><br>When you code a switch statement, be careful not to misspell default, because if you do, the compiler will not complain! Your misspelled version of default is considered by the compiler to be a label. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> A misspelled default in a switch is considered a label. </tr></td></table></blockquote>Remember that a label is any text you choose followed by a colon. There is no way for the compiler to know that you misspelled default. <br><br><b>2.1.16 Logical Operators Use Short-Circuit Logic</b> <br><br>Expressions that are connected by the logical and (&&) and logical or (||) are evaluated left to right and the evaluation of the expression stops once the result can be fully determined. <br><br>For example, given ((A) && (B)), expression A is evaluated first and if zero (false), the result of the entire expression is known to be false so B is not evaluated. B is evaluated if and only if A is non-zero (true). <br><br>Given ((A) || (B)), expression A is evaluated first and if non-zero (true), the result of the entire expression is known to be true so B is not evaluated. B is evaluated if and only if A is zero (false). <br><br>Exiting out of a logical expression early because the final expression value is known is called short-circuit logic. Consider the following example. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Short-circuit logic example</b>if ((lpMem) && (*lpMem=='A')) { (code body) }</pre></tr></td></table> <br>In this example, lpMem is checked first. If and only if lpMem is non-null does the second check (*lpMem=='A') take place. This is important because if both checks took place and lpMem was null, the *lpMem indirection would more than likely cause a fault and the operating system would halt the program. <br><br><b>2.1.17 De Morgan's Laws</b> <br><br>Do you know how to simplify '!(!a || !b)'? The answer is '(a && b)'. Being able to simplify a complicated expression can be important when trying to understand code that someone else has written. Sometimes being able to not a complicated expression to understand what the expression is not helps you understand the expression. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>De Morgans laws</b>1. !(a && b) == (!a || !b)2. !(a || b) == (!a && !b)</pre></tr></td></table> <br>One simple rule applies to De Morgans Laws. To not a logical expression, not both terms and change the logical operator. This
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -