⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter7.html

📁 Writing Bug-Free C Code
💻 HTML
📖 第 1 页 / 共 5 页
字号:
 filename and line number.  This code was examined thoroughly but no problem could be found.  So, a stack trace of the fault was obtained from the customer, which assisted me in pinpointing the problem immediately.  As it turned out, a newly added feature had caused a reentrancy problem to occur in old code. <br><br>Most development environments today provide sophisticated tools that allow the developer to quickly pinpoint problems in their code.  What do you do when a customer calls up with a fault that you cannot reproduce?  The customer is certainly not running the development environment that you are running. <br><br>My solution to this problem is to add full stack trace capabilities into the application itself.  At every point in the stack trace, a filename and line number are obtained.   <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td>   I build stack trace support into my applications.   </tr></td></table></blockquote>Unfortunately, the solution is specific to the underlying environment, so I cannot give a general solution, but I will do my best to describe the technique that I use. <br><br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> <b>7.18.1 Implementing Stack Trace Support</b> <br><br><i>Obtaining filename and line number information</i>.  The most important piece of information to which access is needed is debugging filename and line number information.  Under a Microsoft development environment, obtaining this information is done in two steps.  The first step is to tell the compiler to generate line number information.  Under Microsoft C8, this is done with the /Zd command line option which results in the .obj files containing the line number information.  The second step is instructing the Microsoft segmented executable linker to produce a .map file.  The /map command line option is used. <br><br><i>Translate the filename and line number information</i>.  The .map file contains the filename and line number information in a human readable form.  A program needs to be written that takes this .map text information and translates it into a form that is easily readable by the stack trace code. <br><br><i>Walking the Stack</i>.  This is the toughest part because it is so specific to the platform that you are using.  If walking the stack is not provided as a service by the operating system, you may want to consider walking the stack yourself.  This is what I do.  Luckily, Windows now provides stack walking support through the TOOLHELP.DLL system library.  For Windows, the functions of interest are StackTraceFirst(), StackTraceCSIPFirst() and StackTraceNext(). <br><br><i>Mapping addresses to filename and line numbers</i>.  As you walk the stack, the only piece of information available to you is an address.  Somehow you need to map this back to the information you stored in the binary file representation of the .map file.  Again, this is specific to the environment you are working on.  Under 16-bit protected-mode Windows, far addresses are really composed of a selector and offset.  The trick is to map the selector back to a segment number because the segment number is what is specified in the .map file.  This is done in two steps.  The first step is to map the selector to a global memory handle by using GlobalHandle().  The second step is then to map this global memory handle to a segment number by calling GlobalEntryHandle().  Both functions are provided by TOOLHELP.DLL.  You can now look up the filename and line number. <br><br>You now have superior stack trace support built right into your application.  It is superior because the stack trace gives filename and line numbers instead of the hex offsets usually given by system level stack traces.</td></tr></table><br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> <b>7.18.2 Enhancements</b> <br><br>If you implement stack trace support in your application, I have some enhancements to suggest to you.  I would highly recommend that you first get the basic stack trace support working before tackling these enhancements. <br><br><i>Hooking CPU faults</i>.  In protected memory environments, if your program accesses memory that does not belong to it, the program faults and the operating system halts the program.  If possible, try to hook into this fault and produce a stack trace!  For Windows, TOOLHELP.DLL provides the InterruptRegister() and InterruptUnRegister() functions that allow programs to hook their own faults.  This requires some assembly language programming. <br><br><i>Hooking parameter errors</i>.  Under Windows, the kernel is performing error checks on the parameters being passed to Windows API calls.  It is possible to hook into the bad parameter notification chain.  This is done by using TOOLHELP.DLL, which provides NotifyRegister() and NotifyUnRegister(). <br><br><i>Displaying function arguments</i>.  As you walk the stack, try to parse what arguments were passed to the function along with the filename and line number.  This is tricky but it is doable and well worth the effort.  Most faults that cause stack traces are caused by an invalid argument in some function call.  Spotting this in the stack trace then becomes easy.</td></tr></table><br><b>7.18.3 The Benefits</b> <br><br>I have implemented full stack trace support along with hooking CPU faults, hooking Windows kernel parameter errors and displaying function arguments.  What are the benefits?  Great customer relations!  In most cases, a stack trace is enough to track down a problem.  In other words, I can track down a problem without first having to reproduce the problem.  Customers begin to trust that a reported problem will get fixed and you end up with a robust product that the customer begins to trust and rely upon.<br><a name="singleexit"><br></a><big><b>7.19 Functions Have a Single Point of Exit</b></big> <br><br>This has more to do with writing functions that are easily maintainable than anything else.  If a function has one entry point, a flow of control and one exit point, the function is easier to understand than a function with multiple exit points. <br><br>It also helps eliminate buggy code because using a return in the middle of a function implies an algorithm that does not have a straightforward flow of control.  The algorithm should be redesigned so that there is only one exit point. <br><br>In a sense, a return in the middle of a function is just like using a goto statement.  Instead of transferring control back to the caller at the end of the function, control is being passed back from the middle of the function.<br><a name="nogoto"><br></a><big><b>7.20 Do Not Use the Goto Statement</b></big> <br><br>I agree with the majority opinion that goto statements should be avoided.  Functions with goto statements are hard to maintain.<br><a name="bulletproff"><br></a><big><b>7.21 Write Bulletproof Functions</b></big> <br><br>Who is responsible for making sure that a function gets called and used properly?  Is it up to the programmer?  Or is it up to the function that gets called?  Let's face it, programmers make mistakes.  So anything that can be done on the part of the function to ensure that it is being used properly aids the programmer in finding problems in the program. <br><br>Consider a GetTextOfMonth() function.  It takes as an argument a month, zero through eleven inclusive, and returns a long pointer to a three-character string description of the month.  A naturally simple solution is as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>GetTextOfMonth() function, no error checking</b>LPSTR APIENTRY GetTextOfMonth( int nMonth ){    CSCHAR TextOfMonths[12][4] = {        "Jan", "Feb", "Mar", "Apr", "May", "Jun",        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"        };    return (TextOfMonths[nMonth]);} /* GetTextOfMonth */</pre></tr></td></table> <br>The only problem with this code is what happens when the input nMonth is not in the proper range of zero to eleven?  The returned pointer points to something, but if treated as a string, it is more than likely much longer than a three-character string.  If this string is being used in a printf() statement, the resultant buffer has a high likelihood of being overwritten, trashing memory beyond the end of the buffer and causing even more problems that need to be tracked down. <br><br>The solution is to make GetTextOfMonth() completely bulletproof so that any value passed into it returns a pointer to a three-character string.  One possible solution is as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>GetTextOfMonth() function, with error checking</b>LPSTR APIENTRY GetTextOfMonth( int nMonth ){    CSCHAR szBADMONTH[]="???";    LPSTR lpMonth=szBADMONTH;    WinAssert((nMonth&gt;=0) && (nMonth&lt;12)) {        CSCHAR TextOfMonths[12][4] = {            "Jan", "Feb", "Mar", "Apr", "May", "Jun",            "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"            };        lpMonth = TextOfMonths[nMonth];        }    return (lpMonth);} /* GetTextOfMonth */</pre></tr></td></table> <br>Notice how a fault-tolerant form of WinAssert() is being used.  This ensures that a full stack trace is logged if the input parameter is invalid. <br><br>Should this fault-tolerant code ever be removed?  Once you get the code working that uses GetTextOfMonth(), you know there are no bugs, right?  No, I do not think so!  Do you know that your entire program is bug-free?  There could be a bug in some totally unrelated part of the program that is causing a memory overwrite.  If it just happens to overwrite a month number that you have stored in memory, you are in big trouble once again.  Or what happens when you go back a year latter to modify the code that uses GetTextOfMonth()?  You may introduce a subtle bug. <br><br>The best way to write a bug-free program is to keep all the defenses up at all times.  At least this way, you will know when there is a problem in your program. <br><br>You may not know what the problem is, but just knowing that there is a bug is important information for maintaining a quality product. <br><br>The best way to write a bug-free program is to keep all the defenses up at all times.<br><a name="portablecode"><br></a><big><b>7.22 Writing Portable Code</b></big> <br><br>C is so successful because it is so flexible, flexible, that is, to the compiler writer because many key issues are left to the compiler writer to specify how they should work.  This was done so that each implementation of C could take advantage of how a particular machine architecture works. <br><br>For example, what is the sign of the remainder upon integer division?  How many bytes are there in an int or long or short?  Are members of a structure padded to an alignment boundary?  Does a zero-length file actually exist?  What is the ordering of bytes within an int, long or short? <br><br>Most compilers provide a chapter or two in their documentation on how they have implemented these and many more implementation-defined behaviors. <br><br>If writing portable code is important to you, I would suggest that you thoroughly read these chapters and adopt programming methodologies that avoid implementation dependent behavior.<br><a name="memorymodels"><br></a><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> <big><b>7.23 Memory Models</b></big> <br><br>Due to the segmented architecture of the Intel CPU, compiler vendors provide the option of creating a program in one of four basic memory models. <br><br><i>The small memory model</i>.  This model allows for less than 64K of data and less than 64K of code.  This model is great for quick and dirty utility programs.  It also produces the fastest program since there is never any need to reload a segment register. <br><br><i>The compact memory model</i>.  This model allows for more than 64K of data and less than 64K of code.  It is ideal for small programs that crunch through a lot of data.  The program is still fast, but there is a slight speed penalty for accessing far data. <br><br><i>The medium memory model</i>.  This memory model allows for less than 64K of data and more than 64K of code.  This is the memory model that Microsoft recommends using for programming in Windows.  It allows for lots of code and a small amount of data.  By using mixed-model programming, you can gain access to more than 64K of data. <br><br><i>The large memory model</i>.  This allows for more than 64K of data and more than 64K of code.  It is the memory model that most closely matches flat memory model architectures. <br><br>These memory models are complicated by the fact that there is something called mixed-model programming.  The four basic memory models essentially provide default code and data pointer attributes.  A code or data pointer is either a 16-bit near pointer or a 32-bit

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -