Debugging C + + has several powerful features available, regardless of the platform you are using and whether you can access the debugger. The purpose of this article is to enumerate the features that you can use to debug your code and discuss how to use them.
When discovering new features of a programming language, the initial tendency is usually to take no account of its shortcomings and try to replace all its other features. Since there is no design pattern that is perfect for all problems, if everything has to go into the "better" design model, this tendency is often wasteful and leads to poorly designed code.
You cannot choose the most appropriate model when it is useless to consider your environment and the strengths and weaknesses of these methods first. Assertions,exceptions,longging,return values, and so on, they all have specific strengths and weaknesses.
I will list some of my observations on these methods.
Method 0-nothing
Advantages: Easy to write a large number of code, debugging or release when the implementation of the burden
Disadvantage: If this method has no effect, then skip it
This method is more than a method--that's why it's called method 0. I think that in order to be complete I should include it. If you use this method often, give your client a favor and seek professional help.
This also gives me an opportunity to explain the debugging theory I know and some theorems that I will use in the full text. C + + code has two basic versions-the debug and Release editions. The code for both states must have the same functionality. The difference is that debug mode speeds up debugging and release mode speeds up. Of course, you can also define different levels as needed, but in this article we only use these two patterns.
Note that debugging is different from scavenging.
A vulnerability is a poorly designed code that goes wrong under the correct code. Debugging is the process of identifying and mitigating these vulnerabilities. There are a number of reasons for the vulnerability, and here is a short list:
1. Understanding of programming languages, APIs, other code and platform errors.
2. Wrong code design and organization
3, operating system vulnerabilities
4. Miscommunication among team members
5, hurriedly write the code
6, there is absolutely no reason to say
Note that when you write code, your code is rarely completely independent. That is, your code usually relies on the standard library, and further, you will rarely program alone, which means that there is an interdependent relationship between your code and the members of your group and perhaps even your customers.
There are two terms that are commonly used to describe people or code roles: server and client. People who use and rely on your code are called client, and when you rely on someone else's code, you are their client. The server is rarely used here, but it means "the person who wrote the code."
Method 1--assertions
Advantages: Relatively fast, there is no overhead in the published code, and writing is fairly straightforward.
Disadvantages: When debugging compile, a little lower speed, in the release version can not be safely provided, when the clients debugging code to ask them to read the source.
Explain
Assertion is a Boolean expression that must return a really worthwhile order to continue execution. In C + + the next assertion is by using the Assert function, and the expression passed to it must be true:
ASSERT (this);
If this is the execution of the 0,assert function termination program, and displays a message indicating that your assert (this) failed in the line of your source code, and that you can get there. If the value of this is not 0,assert will simply return and your program will continue to perform normally.
Note that the Assert function cannot do anything and that there is no overhead in the release, so do not use the following:
file* p = 0;
ASSERT (P = fopen ("MyFile.txt", "r+"));
Because fopen will not be invoked in the release version. Here's the right way to use it:
file* p = fopen ("MyFile.txt", "r+"));
ASSERT (P);
Example
The best use can be made in newly written code that must often be assumed. Consider the following function:
void sort (int* const myarray)//A overly simple example
{for
(unsigned int x = 0; x < sizeof (myarray)-1;
if (Myarray[x] < myarray[y]) swap (myarray[x], myarray[y]);
Count the number of hypotheses in this function tag. Now, take a look at the best version of the simpler debugging:
void Sort_array (int* const myarray)
{
assert (myarray);
ASSERT (sizeof (myarray) > sizeof (int*));
for (unsigned int x = 0; x < sizeof (myarray)-1 + + +)
if (Myarray[x] < myarray[y]) swap (myarray[x), Myarray[y]) ;
}
As you can see, if the following conditions occur this seemingly correct code will not work:
1, the pointer is null, or
2, sizeof (myarray) cannot be used to determine the number of array elements, or because the array is not allocated on the stack, or because an item (not an array) address is passed.
Although this is a very simple algorithm, many features you will write or encounter in a larger and more complex program. When you see a lot of conditions that will lead to a series of errors, this is very surprising.
void blend (const video::memory& source, video::memory& destination, const F
Loat Colors[3]) {//the algorithm used IS:B = A * Alpha const unsigned int width = source.width ();
const unsigned int height = source.height ();
const unsigned int depth = source.depth ();
const unsigned int pitch = Source.pitch ();
Switch (depth) {case://... break;
Case://... break;
Case: {unsigned int offset = 0;
unsigned int index = 0;
for (unsigned int y = 0; y < height; y++) {offset = y * pitch;
for (unsigned int x = Destination.get_width (); x > 0; x--) {index = (x * 3) + offset;
Destination[index + 0] = source[index + 0] * Colors[0];
Destination[index + 1] = Source[index + 1] * colors[1];
Destination[index + 2] = Source[index + 2] * colors[2];
}} break;
Case://... break; }
}
Do you fully count the assumptions about function optimization? Let's list them together:
ASSERT (Source.locked () and destination.locked ());
ASSERT (source.width () = = Destination.width ());
ASSERT (source.height () = = Destination.height ());
ASSERT (source.depth () = = Destination.depth ());
ASSERT (source.pitch () = = Destination.pitch ());
ASSERT (source.depth () = = or source.depth () = = or source.depth () = = or
source.depth () = 32);
In general, the more you optimize the low-level code at the same level as this, the more assumptions you will have to make. My function requires the source and destination video memory to be locked, and they have the same width, height, and composition. Configuring these assertion functions at the top of the function will prevent some programmers from wondering in the future about why the function is not used or why the access is illegal, and all of these are now resolved at the top of the function.
However, you must pay attention to the version of the assert you are using. If you use the standard C assert, you may end up with a very large debug build process, because it creates all of these static strings. If you find this to be a problem, overwrite assert to trigger an exception or use something else to replace the string constant at build time.
Maybe you don't have to test all the parameters and conditions-it's a painful decision for a good programmer. Sometimes annotations are better because they do not increase the size of the final compiled version.
Good code should not rely on too many assertions at the top of each function. If you find that you are writing a class and you have to insert assertions into each member function to test the state and so on. In this case, you'd better split your class into multiple classes.
Conclusion
The benefit of inserting assertions into your code is as follows:
1, it allows you to think in a specific environment and other code and data in the case of hypothetical results, and so it also gives you a better technology to develop and reduce the chance of bugs
2, by inserting the assertion at the top or right of the function, or before the condition is used, other programmers will be easier to prevent bugs from appearing when using your code
3, it can help others understand the intentions of your code, affect the relevant functions/data, and some possible design constraints.
4. When using assertions to verify that key parameters are bugs, they are easily tracked, because they are discovered when parameters are just passed in, not in obscure algorithms, or when functions are loaded, or even errors, until they cause some errors to be detected.
5, it makes it easy to trace the correct return value.
6. You don't have to think about unexpected situations or return error codes, but it's helpful when you write simple new code and you want to be able to test it quickly.
Method Two--exceptions
Advantages: Automatically clear and gracefully shut down, and if the processing has the opportunity to continue execution, in both debug and release versions can be used.
Disadvantage: relatively slow
Explain
Basically, you can use the Throw keyword to throw the data up to the unknown function caller and the function stack continues to grow until another function captures the data. You can use the TRY keyword to include statements that you want to catch exceptions. Look at the following example:
void Some_function () {throw 5;}//Some function that throws a exception
int main (int argc, char* argv[])
{
try//Just letting the compiler know we want to catch all exceptions from this code
{
some_function ();
}
catch (const int x)//If the type matches the data thrown, we execute this code
{
//do something about the exception ...}}
If you don't insert a try statement block into your code, the stack will continue to grow simply until it returns to the main function and your program exits. You should not put them anywhere-only you can catch an anomaly and restore him. If you can only partially recover it, you can throw back the original exception so that the function stack will continue to grow until it occurs to the next conforming catch statement block.
Example
Exceptions are best used to catch exception conditions in the key areas of the debug and release version. Once they are used correctly, they provide automatic purge and force the program to exit or bring the program back to the correct state. So the exception mechanism is perfect for the release code because it provides everything to end users with good programming habits when they encounter unexpected errors. Properly used, they provide the following benefits:
1, for all resources in the implementation of the automatic removal of any place.
2. It forces the application to either exit or return to the correct state.
3. It forces the recipient of the exception to handle the exception, as a mere alternative to making a choice.
4. It allows the code to be freed to be rewritten, either by writing the person who allocates the code or by hiding the implementation (which, of course, refers to the destructor).
Because of overhead, using it in normal control flow is often a bad idea because other control structures may be quicker and more efficient.
Method 3--retrun Values
Advantages: It is quicker to use constants or built-in types to allow changes to the customer's logic and to be as clean as possible.
Disadvantage: The handling of errors is not mandatory, and the return value may be two semantic.
Explain
Usually we return a useful value or a surface error value to the caller:
{
if (dividend = 0) return (0);//Avoid "integer Divide-by-zero" error return
(divisor/dividend);
}
Returns the best use of the method where the code logic is simple and may be wrong. For example, a function might return a bit identifier for a collection, some of which might be considered by the customer, and the return value is best for conditional logic when other customers do not consider it.
A function believes that the caller of the function will notice the wrong condition like a lifeguard who believes that other swimmers will notice that someone is drowning.
Method 4--logging
Sometimes you may not get a debugger. Logging errors to a file can be very effective in debugging. Declares a global log file (such as using use Std::clog) and logs the output to the top when an error occurs. It may be helpful to output the file name and number of rows, so you can know where the error occurs. __file__ and __line__ tell the compiler to output the current file name and row.
Of course, not only can you log errors in logging files, you can also log information that other debuggers cannot access, such as the maximum number of matrices used or some other data. You can also output data that causes your program to fail. Std::fstream is very good for this purpose. If you are very smart, then you can point out some ways to log information about assertions or abnormal logs into a file. :)
This provides the following benefits:
1, it can easily and you already have the code to dissolve into one, and has the very good portability.
2, it can replace the debugger to a can read output.
3, can be without interruption in the case of the program to give detailed information.
Of course, it also has some overhead, so you need to measure whether the benefits in your situation can counteract its drawbacks.
One more thing ...
I was going to end my article here, but I wanted to show how much value a mixed approach would be for debugging. The easiest way to do this is to create a simple class that works better in the case of c-++ code. A file class would be nice. We use C's fopen () to ensure that functionality is simple and portable.
1. Constructors and destructors must guarantee a lifetime of file pointers. /SPAN>
2, the assertion is used to describe the assumptions made by each function.
3. The exception type is used to require client programs to handle exceptions.
4, the member function is used to read or write data.
5, temporary member functions as a shortcut to the data allocated from the stack.
6, contains a false security and exception security constructor, responsible for this member function.
7, portability.
The code is as follows:
#include <cstdio> #include <cassert> #include <ciso646> #include <string> class file {public:
Exceptions struct Exception {};
struct Not_found:public exception {};
struct End:public exception {};
Constants enum Modes {relative, Absolute};
File (const std::string& filename, const std::string& parameters);
~file ();
void Seek (const unsigned int position, const enum modes = relative);
VOID read (void* const data, const unsigned int size);
void Write (const void* const data, const unsigned int size);
void Flush ();
Stack only!
Template <typename t> void read (t& data) {Read (&data, sizeof (data));}
Template <typename t> void write (const t& data) {write (&data, sizeof (data));
private:file* pointer;
File (const file& other) {} file& operator = (const file& Other) {return (*this);}; File::file (const std::string& filename, const std::string& parameters)
: pointer (0) {assert (not filename.empty ());
ASSERT (not Parameters.empty ());
pointer = fopen (FILENAME.C_STR (), Parameters.c_str ());
if (not pointer) throw Not_found ();
} file::~file () {int n = fclose (pointer);
ASSERT (not n); } void File::seek (const unsigned int position, const enum File::modes mode) {int n = fseek (pointer, position, (mode = = relative)?
Seek_cur:seek_set);
ASSERT (not n);
} void File::read (void* const data, const unsigned int size) {size_t s = fread (data, size, 1, pointer);
if (S!= 1 and feof (pointer)) throw end ();
ASSERT (S = = 1);
} void File::write (const void* const data, const unsigned int size) {size_t s = fwrite (data, size, 1, pointer);
ASSERT (S = = 1);
} void File::flush () {int n = fflush (pointer);
ASSERT (not n);
int main (int argc, char* argv[]) {file myfile ("MyFile.txt", "w+");
int x = 5, y = ten, z = 20;
float f = 1.5f, G = 29.4f, h = 0.0129f;
char c = ' I ';
Myfile.write (x); MyfIle.write (y);
Myfile.write (z);
Myfile.write (f);
Myfile.write (g);
Myfile.write (h);
Myfile.write (c);
return 0; }
If you compile them under Windows to ensure that the project type is set to the Win32 Console application, this class provides those benefits to his client program.
1. Allow the user to manually debug each function in order, and therefore know that one parameter caused the member function error.
2, when debugging compile, it will check all the return values and most of the parameters.
3, it throws two kinds of exceptions, if the condition is abnormal and the customer must decide how to do.
4, for most of the common operation provided shortcuts.
5. You are not allowed to copy constructors and members until you are ready to process them.
Note that the file pointer is thrown when the following exception occurs.
1. When creating files, we must ensure that the creation is successful.
2. When reading a document, we should ensure that it will not end prematurely.
Conclusion
The technology for file classes can be used for most value exposure methods or for legacy code that points to internal function pointers.
The ability to extend file classes is an instructive thing for readers. Try to add a replicated constructor, or an assignment operation, and test the class under different conditions. Note that assertions are available when you modify this class, and existing assertions can help you catch new code errors. For your changes, it is possible to put the file object as a usable state.
Conclusion
Log can be used when the program is running and you cannot or do not want to verify the data.
Fortunately, all of these features are portable in C + +. Unfortunately, not everyone who writes code in C + + has time. It is important for your client program that you should choose which solution you need according to your environment.
Regrets where the return value is usually the only communication between C + + code and non C + + code about the error. It is painful to check for a return value, but the assertion reduces the pain.
C + + debugging (by Null_pointer)