|
crashrpt
|
This page contains some tips on writing C++ code robust to errors. A program that conforms to these rules is less likely to crash.
This page contains the following topics:
As a program grows in size and dependencies, you start losing trace of its components, get further away from the big picture, and ease the introduction of bugs [How to write robust code]. So it is important to realise that some rules should be followed to write a code more tolerant to errors.
Generally speaking, robust code has such features as: it is well designed, neat and tidy, well named, well commented, well tested, it rarely crashes.
If your program follows consistent coding rules (for example, C++ Programming Style Guidelines, Programming in C++, Rules and Recommendations or Google C++ Style Guide) it is less likely to crash, because it has a consistent structure.
Even if your program currently doesn't follow any coding rules because of its complexity and because of your team structure, following the simple rules listed below may help you to avoid the majority of the crash situations.
Not ininitialized local variables are a common reason of program crashes. For example, see the following code fragment:
The code fragment above can be a potential reason of a crash, because none of local variables is initialized. The correct code would be the following:
Many WinAPI functions receive/return parameters through C structures. Such a structure, if incorrectly initialized, may be the reason of a crash.
It is recommended to use ZeroMemory() or memset() to fill the structure with zeroes (this typically sets structure fields to their default values).
Many WinAPI structures also have the cbSize parameter that must be initialized with the size of the structure before using.
The following code shows how to initialize a WinAPI structure:
But! DO NOT use ZeroMemory() or memset() for your C++ structures that contain objects as structure members, that may corrupt their internal state and be the reason of a crash.
It is even better to use a constructor for your C++ structure that would init its members with default values:
It is recommended to always validate function input parameters.
If you use a pointer, make sure it is not equal to NULL.
If your function creates an object and returns it as a function parameter, it is recommended to initialize the pointer with NULL in the beginning of the function body.
If you do not explicitly initialize the output parameter and further it is not set due to a bug in function logics, the caller may use invalid pointer which would possibly cause a crash.
Example of incorrect code:
The correct code:
Assign NULL to a pointer after freeing (or deleting) it. This will help to ensure noone will try to reuse an invalid pointer.
Assign NULL (or zero, or something else default value) to a handle after freeing it. This will help to ensure noone will try to reuse an invalid handle.
Below is an example of how to clean up a WinAPI file handle.
Below is an example of how to clean up a FILE* handle.
If you allocate a single object with the operator new, you should free it with the operator delete.
But if you allocate an array of objects with the operator new, you should free this array with delete [] .
or
Ensure that 0 (zero) bytes are not allocated using malloc() or new.
For additional tips on how to allocate memory correctly, you can read the Secure Coding Best Practices for Memory Allocation in C and C++ article.
Asserts can be used in debug mode for checking preconditions and postconditions. But when you compile your program in release mode, asserts are removed on the preprocessing stage. So, using asserts is not enough to validate your program's state.
Incorrect code:
As you can see in the code above, usage of asserts can help you to check your program state in Debug mode, but in Release mode these checks will just disappear.
The correct code would be:
It is a common mistake to call the function and assume it will succeed. When you call a function, it is recommended to check its return code and values of output parameters.
The following code calls functions in succession. Whether to proceed or to exit depends on return code and output parameters.
If you intensively use pointers to shared objects (e.g., COM interfaces), it is a good practice to wrap them into smart pointers. The smart pointer will take care of your object's reference counting and will protect you from accessing an object that was already deleted. That is, you don't need to worry about controlling the lifetime of your interface pointer.
For additional info on smart pointers, see the following articles: Smart Pointers - What, Why, Which? and Implementing a Simple Smart Pointer in C++.
Below is an example code (borrowed from MSDN) that uses ATL's CComPtr template class as a smart pointer.
Look at the following code fragment:
The code above is correct and uses pointer validation. But, assume you made a mistyping and used an assignment operator (=) instead of equality operator (==):
As you can see from the code above, such mistyping may be the result of a stupid crash.
Such error can be avoided by slightly modifying the pointer validation code (exchange left side and right side of the equality operator). If you mistype in such modifyed code, such mistyping will be detected on compilation stage.
1.8.12