📄 chapter3.html
字号:
<html><head><title>Writing Bug-Free C Code: Rock-Solid Base</title></head><body><center><font size="+3">Chapter 3: Rock-Solid Base</font><br><a href="index.html">Writing Bug-Free C Code</a><br></center><br><center><table><tr><td valign=top><small><a href="#systembugs">3.1 System Functions Contain Bugs</a><br><a href="#usemacros">3.2 Using Macros to Aid Porting</a><br><a href="#winassert">3.3 Using WinAssert() Statements</a><br><small></td><td width=30> </td><td valign=top><small><a href="#naming">3.4 Naming Conventions</a><br><a href="#summary">3.5 Chapter Summary</a><br></small></td></tr></table></center><br><br>Would you build a skyscraper without a proper, solid foundation? Of course not. Would you build a large application without a rock-solid base system? I wouldn't, and yet I get the feeling that this is happening every day. Do you consider the standard C library to be a rock-solid base? Before you answer this, I need to clarify what I consider to be rock-solid. <br><br>A rock-solid function must first of all be bug-free itself. The function must provide a clean, intuitive interface. What hope would you ever have if you were constantly making mistakes in how a function is called? Function names must clearly state what the function does. What good is a function named DoIt()? The function must detect and report invalid function arguments. The function should be fault-tolerant. The program you are working on should not crash simply because you called a function incorrectly. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Before you can write bug-free code, you must have a bug-free, rock-solid base. </tr></td></table></blockquote>Do you now consider the standard C library to be a rock-solid base? Many functions are, but many functions are not. Consider the heap management routines in the C library, specifically, the free() function. The free() function deallocates a block of memory that was previously allocated through the malloc() function call. <br><br>What happens when you pass free() a completely random value, or a value that you have already previously passed to free()? Your program may bomb immediately. If it doesn't, the heap may be corrupted. If it isn't, some memory may have been overwritten. The point is that not all C library functions are rock-solid. Why not first code a layer on top of the system base that is rock-solid? <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Not all C library functions are rock-solid. A layer that is rock-solid needs to be coded. </tr></td></table></blockquote>As you code your program, you need to consider the current program as it stands as the base for whatever new features you are putting in. Once done, this is the new base for the next set of features. As you code, make sure that the current base is rock-solid; that it is fault-tolerant and that it catches incorrect usage of functions. If the base is not rock-solid, you need to make it rock-solid.<br><a name="systembugs"><br></a><big><b>3.1 System Functions Contain Bugs</b></big> <br><br>Your underlying operating system or development environment has bugs in it. Since there is no such thing as a completely bug-free system, try to find out as much as you can about the environment you are working on. Try to obtain bug lists if they are available. <br><br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> Microsoft has recently started the Microsoft Developer Network. It is a program that is intended to get as much information and technical resources as possible into the hands of developers. The program distributes information in the form of a CD and a Windows-based browser. I highly recommend this program to developers. The Microsoft Developer Network can be reached through the Internet at <a href="http://msdn.microsoft.com">http://msdn.microsoft.com</a> </td></tr></table> <br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> <b>3.1.1 A MS-DOS Bug</b> <br><br>An example of a bug that was found in the MS-DOS operating system involves lost clusters. It was detected by an <a href="chapter7.html#automatedtesting">automated testing procedure §7.24</a>. As the automated testing function would run, invariably it would eventually run out of disk space, but the function never created any file large enough to even come close to running out of disk space. As it turned out, disk clusters were being lost by the operating system on a regular basis and eventually the disk would run out of free clusters. The only way to recover the lost clusters was to run the CHKDSK program provided by the operating system. <br><br>This bug is a confirmed bug in MS-DOS 3.2 and MS-DOS 3.3, the only versions available when the bug was discovered. The bug does not exist in MS-DOS 5.0 and later versions. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>C program that shows MS-DOS version 3.3 lost cluster bug</b>#include <fcntl.h>#include <sys\types.h>#include <sys\stat.h>#include <io.h>#include <stdio.h>int main(void){ char c; int fh = creat( "it.tmp", S_IREAD|S_IWRITE ); lseek( fh, 81920, SEEK_SET ); write( fh, &c, 1 ); chsize( fh, 81920 ); lseek( fh, 122880, SEEK_SET ); write( fh, &c, 1 ); close( fh ); return 0;} /* main */</pre></tr></td></table> <br>Running this program repeatedly under MS-DOS 3.2 or MS-DOS 3.3 will cause the lost cluster bug. Run CHKDSK to recover the lost clusters.</td></tr></table><br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> <b>3.1.2 Windows Bugs</b> <br><br>As any early Windows developer will tell you, Windows was a buggy operating system. Over the course of five years, I accumulated over six three-ring binders full of correspondence with Microsoft support concerning bugs in the Windows system. <br><br>With the introduction of Windows 3.0, I noticed a significant drop in the number of problems that I was reporting. I attribute this reduction to the fact that Windows 3.0 was a protected-mode operating system (previously it was not). Many types of errors cause a CPU fault and the error can be pinpointed immediately. However, the Windows 3.0 system was still a little shaky when it came to DOS boxes, the emulation of a MS-DOS PC in a Windows window. <br><br>With the introduction of Windows 3.1, the number of bugs that I report has dropped dramatically. I consider Windows 3.1 to be a stable operating environment. In fact, on my development machine, I stay in Windows all day and use DOS boxes to run my MS-DOS applications. My machine may crash once a week. Not too bad, but still room for improvement. <br><br>However, there is a particularly nasty bug I have found that still exists in Windows 3.1. Luckily, the likelihood that you will ever encounter the bug is almost nil. The bug is that sometimes the Windows multitasker does not switch to the correct PSP when a Windows application performs disk I/O. What this means is that a file handle that should be correct is not, because the program is in the open file context of another application! <br><br>A little background may be needed to explain why this can even occur. When Windows multitasks applications, it has to keep a tremendous amount of information around for each application. One piece of that information is the open files context. In order to significantly speed up task switching in Windows, the open files context is not switched until the task actually performs an operation that requires the open files context. Reading and writing a disk file is an example of an operation that would cause a true open files context switch. Since most applications rarely perform disk I/O relative to the amount of CPU time they need, this delayed context switch of the open files context speeds up multitasking. <br><br>The bug occurs when, in the course of multitasking applications, the Windows kernel incorrectly thinks that it is truly in the open files context of the currently running application, when, in fact, it is in the open files context of another application. Microsoft has been slow to fix this problem since the circumstances that cause the bug to occur are extreme. <br><br>The only reason I found this bug in the first place is because of an automated testing procedure I used to test the correctness of a new module. This procedure caused almost continuous I/O to occur and every once in a great while, an I/O would fail under low memory conditions. <br><br>A Windows 3.1 bug. There is another bug that I found that anyone can reproduce easily on any Windows 3.1 machine. First, run the standard Notepad application. Go into the file open dialog and click with the mouse on the OK button. Now press and hold down the space bar. While continuing to hold down the space bar, press the ESC key. This causes a General Protection Fault. The problem has to do with the dialog manager inside the Windows kernel. The fault occurs because both an IDOK and an IDCANCEL are being sent to the dialog callback procedure when in reality, only one message should be sent. This is a problem with all dialogs in all applications. However, the application may or may not fault. It just depends upon how the application was written to respond to the IDOK and IDCANCEL messages. This bug is fixed in Windows 3.11.</td></tr></table><br><b>3.1.3 What to Do</b> <br><br>The point in demonstrating to you that bugs do exist in MS-DOS and Windows is to emphasize that sometimes even system level functions fail. Code that you think never fails is bound to fail sometime. <br><br>My reaction to having system level functions fail me has been to provide another layer of code between my application code and the system level functions that checks for assumptions that I am making. At some point, you write code that makes an assumption. Consider the following code. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Code with an assumption</b>int fh = open(....);if (fh!=-1) { close(fh); }</pre></tr></td></table> <br>Do you see what the assumption is? The assumption being made in this code is that the close() function never fails. Well then, why not assert this? The close() returns zero for success, otherwise non-zero for failure. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Provide a code wrapper around all system calls. </tr></td></table></blockquote>The best solution is to provide a wrapper function around each and every system call. Assert any assumptions that are being made within this wrapper function. Placing the assert in the wrapper function once instead of every place it is being called is a lot less error prone.<br><a name="usemacros"><br></a><big><b>3.2 Using Macros to Aid Porting</b></big> <br><br>With all the different machine architectures that are in use today, how in the world do you write code so that it can be ported easily? C provides an excellent mechanism for conditional compilation, but this is only a small part of the solution. <br><br>How do you handle segmented versus non-segmented architectures? What about C and C++? There are slight differences between the two languages. <br><br>One solution that works really well is to abstract out the interdependencies between the environments into a set of macros so that the code base does not have to change. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Use macros as an aid to porting so that your code base does not change at all. </tr></td></table></blockquote><a name="segmentedflat"></a><b>3.2.1 Segmented/Flat Architecture Macros</b> <br><br>A number of #defines that provide a basis for further development are as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -