Practical debugging skills, a must-have course for programmers, how to find bugs correctly, must-see debugging skills for programmers

opening remarks

The content of this section is relatively general, but it is also a very, very, very important section. After all, a programmer can’t just write bugs. Who will fix a bunch of bugs? Therefore, the ability to troubleshoot errors through debugging is a skill that an excellent programmer must master, and it must not only be mastered, but also proficient. In general, there are only two parts in this section. One is to learn the debugging method, there will be Take an example to actually feel it. The second is some issues that must be considered when writing code, and develop a good habit of writing code to prevent problems before they happen.

What are bugs?

The word bug originally meant bed bugs in English. The first computer failure in the world was caused by a bug. Later, the extended meaning of the word bug was interpreted as affecting errors in computer programs. We often hear The debug is also "deworming", which means to fix bugs.

img

What is debugging? how important

When you mention debugging, you have to think of a sentence, an excellent programmer is also an excellent detective. Of course, it does not refer to the kind of case handling, it is only for code.

Every debugging is a process of reasoning to solve the case.

image-20221127090916885

Look at the interesting picture above, does it look like you? We collectively call this kind of superstitious debugging. Maybe the code we write now is not long, with a few lines of code, you can change it here and there with your naked eyes. It can also be corrected, but this is actually very unreasonable. What if there may be tens of thousands of lines of code in a large project. So we need to master the debugging method, definitely not this superstitious debugging.

What is debugging?

Debugging (Debugging / Debug), also known as debugging, is a process of discovering and reducing program errors in computer programs or electronic equipment.

Basic steps for commissioning

  • find program error
  • Locating errors by isolating and eliminating them
  • determine the cause of the error
  • propose wrong solutions
  • Fix bugs and retest

These are some of our general steps. Of course, we still have to experience it in the program in the end. There will be examples later.

Introduction to debug and release

I don’t know if you have noticed that our IDE will have an option debug or release,

image-20221127092302618

We have just explained that debug means debugging, and the word release means release. So debug is the debug version, usually the version written by our programmers, which contains a lot of debugging information, after continuous bug fixes; the release version is the last version we will send to users, and the release version does not contain debugging information , which cannot be debugged, the release version usually optimizes the code to a certain extent.

Summarize:

Debug is usually called the debug version, it contains debugging information, and does not make any optimization, which is convenient for programmers to debug programs.
Release is called a release version, which is often optimized to make the program optimal in code size and running speed, so that users can use it well.

We can also take a look at the difference between the two versions.

image-20221127092932725

Suppose we have the same piece of code like this, and see the difference between the generated executable programs under the debug and release versions.

image-20221127093605178

It can be seen that there are quite a lot of optimized content in the release version. Of course, the difference here is only from the perspective of size.

How do we see whether the release has optimized the code?

Let's look at a slightly more difficult example:

example one

#include <stdio.h>
int main()
{
    
    
	int i = 0;
	int arr[10] = {
    
     0 };
	for (i = 0; i <= 12; i++)
	{
    
    
		arr[i] = 0;
		printf("hehe\n");
	}
	return 0;
}

This code is actually a very problematic code. We can see at a glance that the array is out of bounds, and not only one point is out of bounds. We run this code in the debug version and the result is actually an endless loop of printing hehe, and In the release version, there will be no infinite loop printing. This is the proof that the release version optimizes the code.

But there is also a knowledge point involved here, that is, although my array is out of bounds, why does the result of an infinite loop occur?

We can debug it and feel the charm of debugging in advance.

image-20221127095055839

First of all, you can see a relatively big error point. When the array crosses the boundary, have you found that this code changes the space that the array crosses the boundary to 0? Where is the space accessed by the cross boundary? I changed it to 0 without knowing what to do. What a dangerous behavior this kind of behavior is. Therefore, when writing code, you must do a good job of checking for array out-of-bounds.

Secondly, you may have discovered that when we access arr[12], one is changed to 0 and i is also changed to 0, so we have doubts, is the space accessed by the out-of-bounds exactly the space of i? Is this a coincidence?

A knowledge point is about to be introduced here, and we can understand it through the following figure.

image-20221127101314701

Of course, the environment of different compilers is different, and there may be differences. The habit of using the 64-bit stack area is to use the low address first. In vs2022 or 2019, the reserved space for the array out of bounds in the 32-bit environment is 2 integers in size These contents should be learned in the principles of computer composition, so I won’t go into details here, the main thing is to understand and figure out this phenomenon.

Introduction to Debugging in Windows Environment

Remarks: Debugging in the Linux environment uses gdb, and it is quite challenging.

Here we only introduce the Windows environment.

Environmental preparation

Environmental preparation is actually to switch our IDE to the debug version, otherwise it will not be possible to debug.

Learn a few common shortcut keys

image-20221127104315938

First of all, we have to remember these few,

F5, start debugging, go directly to the next breakpoint, use it in conjunction with the breakpoint, otherwise press F5 and Ctrl+F5 directly to have the same effect.

Ctrl+F5, execute directly without debugging. We usually run directly with Ctrl+F5.

F10, process by process, is usually used to process a process, a process can be a function call, or a statement.

F11, statement by statement, is to execute a statement each time, but this shortcut key can make our execution logic enter the function (this is the most commonly used).

F9, create breakpoints and cancel breakpoints, the important role of breakpoints, you can set breakpoints anywhere in the program. In this way, the program can be stopped at any desired position, and then executed step by step.

At present, the code we write is not very long, with a high probability of dozens of lines, so it is usually directly debugged, but we still have to learn to get used to using breakpoints, and it is a very natural process to develop good habits and write projects in the future.

View the current information of the program during debugging

Here is the highlight. The purpose of our debugging is to check the information of each part of the code, so as to check for errors and finally make corrections.

View the value of a temporary variable

image-20221127105556845

When we debug, we can open any one in Debug->Window->Monitor. In the monitor window, we can enter the variable you want to view, and check the changes through step-by-step debugging. When the result is not what you want When the problem is found.

Reminder: Note that you must go to the debugging window to find it after debugging. Otherwise there is no option to monitor memory etc.

View memory information

image-20221127110211888

As mentioned above, you only need to open the memory window, and all four are available.

view call stack

image-20221127110435758

The call stack is generally used to view the call relationship between functions. Through the call stack, the call relationship of the function and the location of the current call can be clearly reflected.

image-20221127110550208

For example, this is a layer-by-layer function call that calls our main function. When we call the function below the code, we will add functions to the top. There is also a small knowledge point in the data structure. We can draw a picture to explain the sorting method of the stack :

image-20221127112259271

At this time, have you noticed that when we look at the ordering of these functions through the call stack, our understanding of recursion has deepened? So it proves once again that this underlying logic is very helpful for us to learn programming.

view compilation information

image-20221127112710906

In the same way, you can also view the assembly code.

View register information

image-20221127112831506

View register information.

A lot of hands-on, try to debug, in order to make progress

  • Must be proficient in debugging skills.
  • Beginners may spend 80% of their time writing code and 20% of their time debugging. But a programmer may spend 20% of his time writing programs, but 80% of his time debugging.
  • We're all talking about some simple debugging. In the future, there may be very complex debugging scenarios: debugging of multi-threaded programs, etc.
  • Use more shortcut keys to improve efficiency.

debug example

Topic: Realize the code: Find 1! +2! +3! ... + n! ; overflow is not considered.

First, we write the code according to our normal thinking:

#include<stdio.h>

int main()
{
    
    
	int i = 0;
	int n = 0;
	int ret = 1;//保存每个阶乘的结果
	int sum = 0;//保存最终的和
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
    
    
		int j = 0;
		for (j = 1; j <= i; j++)//计算每个阶乘
		{
    
    
			ret *= j;
		}
		sum += ret;
	}
	printf("%d\n", sum);
	return 0;
}

It’s a normal idea to write it down, it seems quite correct, but let’s verify the result,

image-20221127115349376

1! +2! +3! The result should be 9, but I got 15, what is wrong?

Don't worry, let's debug and take a look:

image-20221127115635136

When we observe every element that may have problems, we find that ret is used to calculate factorial, but the value of ret before each factorial is not 1, but the result after the last multiplication, here we find the problem , we just need to reassign ret to 1 before each factorial calculation. So the modified code:

image-20221127115913038

This is an example where we use debugging to troubleshoot errors. Since blogs can only be conveyed in the form of pictures and text, there are certain limitations. You must do it yourself to feel it, and you must do it! Must do it! Must do it!

How to write good (and easy to debug) code

what is good code

  1. the code works fine
  2. few bugs
  3. efficient
  4. high readability
  5. High maintainability
  6. Note clearly
  7. complete documentation

Common techniques for writing good code

  1. use assert
  2. Try to use const
  3. Develop a good coding style
  4. Add necessary comments
  5. Avoid coding pitfalls.
  6. Add necessary comments

The role and use of assert

The translation of assert is called assertion. With our years of learning Chinese, we can roughly guess what the word is, which is roughly the same as the prediction.

The function of assert is to assert. Usually we use it when we don’t want an element to have a value. If the element has this value, the program will report a warning.

Let's take an example to demonstrate:

#include<stdio.h>
#include<assert.h>

void test(int* pa)
{
    
    
	//断言pa不能为空指针
	assert(pa != NULL);
}

int main()
{
    
    
	int* pa = NULL;
	test(pa);
	return 0;
}

Let's run it to see the effect:

image-20221127121139529

It even tells you what you asserted, which file it is in, and which line of code it is in, so this is still very useful.

The role and use of const

We have already understood the constant variables defined by const before. The variable modified with const is still a variable itself, but with constant attributes, it cannot be modified.

#include<stdio.h>
int main()
{
    
    
	const int i = 10;
	i = 20;
	return 0;
}

image-20221127122929502

It can be seen that it is not possible for us to modify const-modified variables in this way. This is our previous understanding of const.

This time we will take a closer look at the function and usage of const.

What we are going to learn today are two situations when const modifies pointers,

#include<stdio.h>
int main()
{
    
    
	const int* i = 10;
	i = 20;
	printf("%d", i);
	return 0;
}

When you modify it with const, you find that i can be changed, and no error is reported. And when you modify *i, an error will be reported:

image-20221127123621209

Another situation is that const is placed after *:

#include<stdio.h>

int main()
{
    
    
	int* const i = 10;
	*i = 30;
	return 0;
}

You will find that *i can be changed, but i cannot be changed, and an error will be reported:

image-20221127123906647

So combining the above two situations, we can summarize:

1. When const modifies the pointer variable, it is placed on the left side of *, which essentially limits the content pointed to by the pointer *i, so i can be changed, but *i cannot be changed;

2. When const modifies a pointer variable, placing it on the right side of * is essentially a restricted i, so the content pointed to by the pointer *i can be changed, but i cannot be changed;

Here's another fun example to help reinforce the memory:

image-20221127130150900

Of course, here is just a simple example, there is no bad guidance.

Common types of programming errors

compile error

To put it simply, it is a grammatical error, just look at the error message (double-click) and solve the problem. Or it can be done with experience. Relatively simple.

link error

Look at the error message, mainly find the identifier in the error message in the code, and then locate the problem. Usually the identifier name does not exist or is misspelled.

For example: The function name is test and it is written as Test. The compiler prompts that the test function cannot be found.

runtime error

This is the most common mistake we make. Generally, this kind of mistake is caused by logic or various factors when writing code. This kind of mistake is generally not easy to find. We must debug patiently to find the problem and then correct it.

A few examples of places that good code should pay attention to

Topic: Simulate the implementation of the strlen function

#include<stdio.h>
#include<assert.h>

int my_strlen(const char* str)//此处const修饰
{
    
    
	assert(str != NULL);//断言指针不能为空指针
	int count = 0;
	if (*str != '\0')
	{
    
    
		str++;
		count++;
	}
	return count;
}

int main()
{
    
    
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

If we wrote this code before, we might not have written these two places, but I hope everyone can pay more attention to these small places.

Topic: Simulation implementation of strcpy

#include<stdio.h>
#include<assert.h>
//库函数的strcpy返回的是目标空间的起始地址
char* my_strcpy(char* des, const char* source)//const修饰不需要改变的量
{
    
    
	assert(des && source);//断言不是空指针,确定指针有效性
	char* ret = des;
	while (*source != '\0')
	{
    
    
		*des++ = *source++;
	}
	*des++ = *source++;//把最后的\0也要拷贝过去
	return ret;
}

int main()
{
    
    
	char arr1[] = "hello";
	char arr2[] = "*********";
	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

Well, the debugging part is over here. It can only tell you the method if you say a thousand words and ten thousand. You still need to debug more and find more problems. Beginners may spend 80% of their time writing code. 20% of the time is debugging. But a programmer may spend 20% of his time writing programs, but 80% of his time debugging. Again: debugging is important! ! ! Debugging is important! ! ! Debugging is important! ! !

Look at the three realms of code:

  • The first layer: look at the code is the code;
  • The second layer: look at the code is not code, but memory;
  • The third layer: look at the code or the code;

final words

The school is also on holiday because of the epidemic. Although I am bored and bored at home, I also hope that I can be more self-disciplined at home and not be as bad as before. I also hope that you who are here will persevere together.

Final Chicken Soup:

Whether it is four years or three years of college. Like a white horse passing by, it is fleeting. Cherish your time, cherish your love, and face life positively every day, come on!

Guess you like

Origin blog.csdn.net/weixin_73223794/article/details/128063893