Write a function, move the memory, and write a simple test case for it for testing.
Is it simple enough? Some people quickly wrote the answer. For details, see program list 1 and program list 2.
Program list 1 v0.1 programs
Void mymemmove (char * DST, char * SRC, int count)
{
While (count --)
{
* DST ++ = * SRC ++;
}
}
Procedure List 2 Test Cases
Void test ()
{
Char P1 [256] = "Hello, world !";
Char P2 [256] = {0 };
Mymemmove (P2, P1, strlen (P1 ));
Printf ("% s", P2 );
}
Objectively speaking, compared to those who cannot write the code in the White Paper or function declaration, the students who can write this code are already very good, at least the C language course has already achieved the education goal of current colleges and universities, but there is still a certain distance from the enterprise's employment requirements. Let's call the above program v0.1 to see if there is any improvement.
First, let's see if the function declaration is reasonable. The v0.1 program uses char * to represent the source address and destination address. Of course there is no problem, but it is inconvenient for others to use it, if you want to move count consecutive struct objects to another place, if you want to use v0.1 programs, the correct statement is as follows:
Mymemmove (char *) DST, (char *) SRC, sizeof (thestruct) * count)
That is to say, we need to forcibly convert the struct pointer to char * to work normally. In this way, all types except strings are inevitably forced to convert the pointer. Otherwise, the compiler will scream, for example, the following error occurs in VC ++ 2008:
Error c2664: 'mymemmove ': cannot convert parameter 1 from 'thestruct *' to 'Char *'
How can this problem be solved? In fact, it is very simple. We know that there is a special pointer that can be assigned a value to any type of pointer, that is, void *. Therefore, we should use void * to represent the source address and destination address. Of course, the content of the function body should also be changed accordingly, so that we can get the v0.2 version of the program.
Program list 3 v0.2
Void mymemmove (void * DST, void * SRC, int count)
{
While (count --)
{
* (Char *) DST = * (char *) SRC;
DST = (char *) DST + 1;
Src = (char *) SRC + 1;
}
}
Some may ask, isn't there a pointer forced conversion? It's just a place to change. Yes, the forced pointer conversion is indeed transferred from the user's code to the library code, but we can understand mymemmove as the library and test as the user, in fact, the effect after adjustment is quite different. v0.1 is a permanent task, and v0.2 is a permanent task!
Note that the return value should also be changed to void * to implement the chained expression *. In addition, if we accidentally write "* (char *) DST = * (char *) SRC;", write it as "* (char *) src = * (char *) DST; "The compilation is still successful, and it takes a lot of time to identify this error. Note that the content pointed to by Src should not be changed in this function, and all assignments to the content referred to by Src should be disabled. Therefore, this parameter should be modified with Const, if similar errors are found during compilation:
Error c3892: 'src': you cannot assign to a variable that is const
As a programmer, making mistakes is inevitable. However, we can use machines that are relatively difficult to make mistakes, that is, compilers, to reduce the probability of making mistakes. In this way, we get v0.3 programs.
Program list 4 v0.3
Void * mymemmove (void * DST, const void * SRC, int count)
{
Void * ret = DST;
While (count --)
{
* (Char *) DST = * (char *) SRC;
DST = (char *) DST + 1;
Src = (char *) SRC + 1;
}
Return ret;
}
Now let's look at a situation where a user calls the database like this: mymemmove (null, SRC, count), which is entirely possible because these addresses are generally calculated by a program, it is inevitable that an error occurs. It is not surprising that there are zero addresses or other illegal addresses. It is expected that, if this happens, the program will immediately go down. What's worse, you don't know where the error is, so I had to invest a lot of energy in finding bugs in the vast amount of code. The common solution to this type of problem is to check the validity of the input parameters, that is, the v0.4 program.
Program list 5 v0.4
Void * mymemmove (void * DST, const void * SRC, int count)
{
Void * ret = DST;
If (null = DST | null = SRC)
{
Return DST;
}
While (count --)
{
* (Char *) DST = * (char *) SRC;
DST = (char *) DST + 1;
Src = (char *) SRC + 1;
}
Return ret;
}
The above statement is written as "If (null = DST | null = SRC)" instead of "If (DST = NULL | src = NULL )", this is also to reduce the probability of making mistakes. We know that both "=" and "=" are valid operators in the C language. If we accidentally write "If (DST = NULL | src = NULL) "It can still be compiled, but the meaning is completely different, but if it is written as" If (null = DST | null = SRC) ", the compilation will fail, therefore, we need to develop a good program design habit: constants and variables should be written before the condition judgment.
The v0.4 version of code first checks the validity of the parameter and returns the result directly if it is invalid. This reduces the possibility of program dwon loss, but degrades the performance, because each call will make a judgment, especially when frequent calls and high performance requirements are involved, the performance loss of the call cannot be underestimated.
If you pass a long-term rigorous test to ensure that you do not use the zero address as a parameter to call the mymemmove function, you may want to simply disable the parameter validity check. We know that macros have the function of this switch, so the v0.5 program will come out.
Program list 6 v0.5
Void * mymemmove (void * DST, const void * SRC, int count)
{
Void * ret = DST;
# Ifdef debug
If (null = DST | null = SRC)
{
Return DST;
}
# Endif
While (count --)
{
* (Char *) DST = * (char *) SRC;
DST = (char *) DST + 1;
Src = (char *) SRC + 1;
}
Return ret;
}
If we add the "# define debug" statement during debugging to improve program robustness, we will change it to the "# UNDEF debug" statement after the debugging is passed to improve program performance. In fact, a macro with similar functions already exists in the standard library: assert, which is more useful. It can also point out that the Code fails to be checked in that line when defining debug, when debugging is not defined, it can be regarded as non-existent. The use of assert (_ expression) is very simple. When _ expression is 0, a debugging error occurs in the debugger. With this good thing, the code is much easier.
Program list 7 v0.6
Void * mymemmove (void * DST, const void * SRC, int count)
{
Assert (DST );
Assert (SRC );
Void * ret = DST;
While (count --)
{
* (Char *) DST = * (char *) SRC;
DST = (char *) DST + 1;
Src = (char *) SRC + 1;
}
Return ret;
}
Once one of the two pointer parameters of the caller is zero, the error shown in 1 is displayed, and it indicates which row is easy to identify. So far, at the language level, there is basically no problem with our programs, so is it true? This requires programmers to consider the logic. This is also a must for excellent programmers, that is, the rigor of thinking. Otherwise, the program will have very hidden bugs. In this example, if you use the following code to call your program.
Program listing 8 overlapping memory testing
Void test ()
{
Char P [256] = "Hello, world! ";
Mymemmove (p + 1, P, strlen (p) + 1 );
Printf ("% s \ n", P );
}
If you have a computer around you, you can give it a try and you will find that the output is not what we expect "hhello, world !" (In "Hello world !" But "hhhhhhhhhhhhhh". Why? The reason is that there is an overlap between the source address range and the target address range. In v0.6, the program does not intend to modify the content of the source address range! Some students will immediately say that I started copying from the high address. Roughly speaking, it seems that this problem can be solved. Although the intervals overlap, they have been copied before the modification, so the results are not affected. However, after careful consideration, this is actually a mistake that is not rigorous in the above thinking, because the user can still make an error in such a call:
Mymemmove (p, p + 1, strlen (p) + 1 );
Therefore, the most perfect solution is to determine whether the source address and target address are copied from the high address or the low address. Therefore, v0.7 is successfully completed.
Program list 9 v0.7
Void * mymemmove (void * DST, const void * SRC, int count)
{
Assert (DST );
Assert (SRC );
Void * ret = DST;
If (DST <= SRC | (char *) DST> = (char *) SRC + count )){
While (count --){
* (Char *) DST = * (char *) SRC;
DST = (char *) DST + 1;
Src = (char *) SRC + 1;
}
}
Else {
DST = (char *) DST + count-1;
Src = (char *) SRC + count-1;
While (count --){
* (Char *) DST = * (char *) SRC;
DST = (char *) DST-1;
Src = (char *) Src-1;
}
}
Return (RET );
}
After modification of the above seven versions, our program can finally be regarded as an "industrial level. Looking back at the previous test cases, we will find that it is not a test case at all, because it only calls the most normal situation and cannot achieve the purpose of the test. With the above experience, the test case also appears accordingly. We may wish to use a character array to simulate the memory.
Program list 10 Comprehensive Test Cases
Void test ()
{
Char P1 [256] = "Hello, world! ";
Char P2 [256] = {0 };
Mymemmove (P2, P1, strlen (P1) + 1 );
Printf ("% s \ n", P2 );
Mymemmove (null, P1, strlen (P1) + 1 );
Mymemmove (P2, null, strlen (P1) + 1 );
Mymemmove (P1 + 1, P1, strlen (P1) + 1 );
Printf ("% s \ n", P1 );
Mymemmove (P1, P1 + 1, strlen (P1) + 1 );
Printf ("% s \ n", P1 );
}
When writing code at the beginning, we often consider how the program works normally. After you have several years of experience and write tens of thousands of lines of code, you will find that the branch code that handles exceptions is sometimes more than the normal main line code, this is also the gap between high-quality programs and general programs. If software products are regarded as a machine, such small functions and classes are parts. Only when the quality of these components is high can the quality of the entire software product be high, otherwise, like a Chinese car in the past few years, today this part strikes and tomorrow that part is resting. As a test case for testing these parts, we must simulate various harsh environments and expose hidden defects of the parts. In this sense, programmers who write test cases must be more rigorous than programmers who design software.