"C language is strange and strange, with many traps, but it has achieved great success !" -- Dennis M. Ritchie, father of C language. This statement by Master Ritchie reflects the flexibility and wide application of the C language, but also reveals that C is a language that always pays attention to its own behavior during application. C's design philosophy is still the same: programmers who use C should know what they are doing. Sometimes a program written in C may produce some inexplicable errors, which may seem hard to find the root cause. However, if you look into the cause of many errors, you may find that the concept is unclear. Among these traps we often fall into, the number of pointers is the most. This lecture will analyze some problems encountered when using pointers to avoid falling into these "traps" in the future.
1. pointer and String constant
In the second lecture, the pointer initialization mentioned that a String constant can be assigned to a character pointer. But have you ever wondered why it can be initialized like this? Before answering this question, let's first figure out what is a String constant. A String constant is a character sequence (which can be null) inside a pair of double quotes ).
When a String constant appears in an expression, the following conditions apply:
<! -- [If! SupportLists] --> 1. <! -- [Endif] --> not the operand of the & operator;
<! -- [If! SupportLists] --> 2. <! -- [Endif] --> not the operand of the sizeof operator;
<! -- [If! SupportLists] --> 3. <! -- [Endif] --> not used as the initialization value of the character array
A String constant is converted to a character array pointed to by a pointer. For example, char * cp = "abcdefg"; if the preceding three conditions are not met, "abcdefg" is converted to a character array without a name, this array is initialized by abcdefg and an empty character '\ 0', and a pointer constant is obtained. Its value is the address of the first character, but these are all completed by the compiler. Now we can explain the reason for initializing a character pointer with a String constant. The value of a String constant is a pointer constant. Friends should not be confused about the following statements:
Printf ("% c \ n", * "abcdefg ");
Printf ("% c \ n", * ("abcdefg" + 1 ));
Printf ("% c \ n", "abcdefg" [5]);
* "Abcdefg": the value of a String constant is a pointer constant that points to the first character of a string. a can be obtained by referencing it;
* ("Abcdefg" + 1): perform arithmetic operations on the pointer to point to the next character, and then dereference it to obtain B;
"Abcdefg" [5]: Since "abcdefg" is a pointer, "abcdefg" [5] can be written as * ("abcdefg" + 5), so f is obtained.
Recall the method used to initialize the array: char ca [] = {'A', 'B', 'C', 'D', 'E', 'E', 'F ', 'G', '\ 0'}; this method is too clumsy, so the standard provides a fast method for initializing character Arrays: char ca [] = "abcdefg "; this string constant satisfies the above 3rd: used to initialize character arrays, so it is not converted to a character array pointed to by a pointer. It is just a simple way to initialize character arrays with a single character. Compare the following two statements:
Char ca [] = "abcdefg ";
Char * cp = "abcdefg ";
They have different meanings. The former is the element that initializes a character array, and the latter is a real String constant, as shown in:
Char ca [] = "abcdefg ";
Figure 1
Char * cp = "abcdefg ";
<! -- [If! Vml] -->
Figure 2
Note: It is used to initialize the String constant of the character array. The Compiler allocates space for the character array in the stack and copies all the characters in the string to the array; the character string constant used to initialize character pointers will be arranged by the compiler in the read-only data storage area, but it is also stored in the form of character arrays, 2. We can use a character pointer to read a String constant but cannot modify it. Otherwise, a runtime error may occur. As shown in the following example:
1. char ca [] = "abcdefg ";
2. char * cp = "abcdefg ";
3. ca [0] = 'B ';
4. printf ("% s \ n", ca );
5. cp [0] = 'B ';
6. printf ("% s", cp );
Row 3 of this program does not modify the String constant in the read-only data zone, but an element of the character Array ca in the stack copied from the String constant. However, Row 3 modifies the String constant in the read-only data zone used to initialize character pointers, so a runtime error occurs. Do not think that all string constants are stored in different addresses. Standard C allows the compiler to use the same storage address for two string constants containing the same characters, in reality, most compilers do the same. Let's look at the following program:
Char str1 [] = "abc ";
Char str2 [] = "abc ";
Char * str3 = "abc ";
Char * str4 = "abc ";
Printf ("% d", str1 = str2 );
Printf ("% d \ n", str3 = str4 );
The output result is: 0 1.
Str1 and str2 are two different character arrays initialized as "abc", which have their own space in the stack, str4 is a String constant whose two character pointers are initialized to contain the same character. They point to the same region.
2. strlen () and sizeof
See the following program:
Char a [1000];
Printf ("% d,", sizeof ());
Printf ("% d \ n", strlen ());
The output of this Code may be 1000 or 0. The result of sizeof (a) must be 1000, but the result of strlen (a) cannot be determined. The root cause is that strlen () is a function, and sizeof is an operator, which leads to their differences:
1. sizeof can use a type (enclosed in parentheses) or a variable as the operand, while strlen () only accepts char * character pointers as parameters, the pointer must end with '\ 0;
2. sizeof is an operator. When sizeof is used for Array names, the memory occupied by the entire array is obtained, and the array name is passed to strlen () as a parameter () the array name is converted to the pointer pointing to the first element of the array;
3. the sizeof result is determined during the compilation period, and strlen () is called at runtime.
In the preceding example, array a [1000] is not initialized, so the number of elements and elements in the array are uncertain and may be random values. Therefore, strlen (a) is used to obtain different values, this depends on the random number, but the sizeof result must be 1000, because sizeof obtains the char and 1000 information in char a [1000] during compilation to calculate the space.
3. const pointer and pointer to const
For the const pointer and pointer constants, you should be able to clearly understand them. Constant pointer: the value of the pointer itself cannot be changed. const can be understood as read-only, for example, int * const c_p; pointer constant: a constant of the pointer type, such as: (int *) 0x123456ff. Now we introduce a new concept: pointer to const, that is, a pointer pointing to a const object, such as: const int * p_to_const; it indicates that p_to_const is a pointer to the const int type variable. The value of p_to_const itself can be changed, but it cannot be changed by resolving the reference of p_to_const, the following example is clearer:
Int * p = NULL; // define an integer pointer and initialize it to NULL.
Int I = 0; // define an integer variable and initialize it to 0.
Const int ci = 0; // define a read-only integer variable and initialize it.
Const int * p_to_const = NULL; // defines a pointer to a read-only integer variable. The Initialization is NULL.
P = & I; // OK, let p point to the integer variable I
P_to_const = & ci; // OK, point p_to_const to ci
* P = 5; // OK, use the pointer p to modify the I value
* P_to_const = 5;/* error, p_to_const points to a read-only variable.
Modify ci */
P_to_const = & I; // OK, let the pointer to the const object point to the normal object
P_to_const = p; // OK. Assign the pointer to the Common Object to the pointer to the const object.
P = (int *) & ic; // OK, Which is forcibly converted to the (int *) type. The operand types on both sides of the value assignment operator are the same
P = (int *) p_to_const; // OK, same as above
P = & ic; // error. error causes:
P = p_to_const; // error, same as above
To assign values to the last two rows, you must describe them. In the C language, the pointer assignment operation (including passing between real-time input parameters) should satisfy the following requirements: both operands point to a qualified character or both pointer to a non-qualified type; or the type pointed to by the Left pointer has all the qualifiers of the type pointed to by the right pointer. For example, const int * indicates "pointing to a pointer of the int type with the const qualifier", that is, const modifies the type pointed to by the pointer rather than the pointer. Therefore, in p = & ic;, & ic gets a pointer to the const int type variable, which has the same type as p_to_const. P_to_const points to the const int type, p points to the int type, p is on the left of the value assignment operator, p_to_const is on the right of the value assignment operator, the Type pointed to by the Left pointer does not have all the qualifiers of the type pointed to by the right pointer, so an error occurs.
Small Extension: {Let's take a deeper look. If there is a pointer int ** bp and a pointer const int ** cbp, the value assignment will also be incorrect: cbp = bp; because the const int ** indicates "pointer to the int type pointer with the const qualifier ". Int ** and const int ** are pointer types without delimiters. They point to different types (int ** points to int *, const int ** points to const int *), so they are incompatible. Based on the pointer assignment condition, the two pointers cannot be assigned to each other.
In fact, the compatible type with const int ** is const int ** const, so the following code is valid:
Const int ** const const_p_to_const = & p_to_const;
/* Define a constant pointer to an int type pointer with a const qualifier. It must be initialized during definition and cannot be assigned a value in the program. Because neither the pointer value nor the pointer value can be changed, in practice, this pointer is not widely used */
Const int ** cpp;
Cpp = const_p_to_const;
The left operand cpp points to the const int * type, and the right operand const_p_to_const points to the const int * type, meeting the pointer assignment condition: the Type pointed to by the Left pointer has all the delimiters of the type pointed to by the right pointer, except that const_p_to_const is a const pointer and cannot be assigned any more. Therefore, in turn, values cannot be assigned. Note that objects restricted by const can only be initialized at the time of declaration .}
4. Value Transfer in C Language
As mentioned in section 3rd, C language only provides a value passing call mechanism for function parameters. When a function is called, a copy of the real parameter is copied and assigned to the form parameter, since then, the input parameters are irrelevant, and the changes in the input parameters in the function do not affect the input parameters. As I mentioned earlier, all data parameters (including pointers) in the C language in non-array form are called by passing values. This is not in conflict with the C language's function of only providing value transfer, the array parameters are converted to pointers to the first element of the array. When we use the array name as the real parameter, the actual value is passed. See the program:
# Include <stdio. h>
Void pass_by_value (char parameter [])
{
Printf ("parameter value: % p \ n", parameter );
Printf ("parameter address: % p \ n", & parameter );
Printf ("% s \ n", parameter );
}
Int main ()
{
Char argument [100] = "C language only supports value transfer calling mechanism! ";
Printf ("real parameter value: % p \ n", argument );
Pass_by_value (argument );
Return 0;
}
The output result on my machine is: real parameter value: 0022FF00
Parameter Value: 0022FF00
Parameter address: 0022FED0
C language only supports value transfer calling!
When pass_by_value (argument); is executed, the real parameter array name argument is converted to a pointer to the first element of the array. The value of this pointer is (void *) 0022FF00, then copy this value and assign it to the form parameter. Although the parameter is declared as a character array, it is converted into a pointer, it is an independent object created on the stack (it has its own independent address) and receives the copy of the real parameter value. As a result, we can see that the input parameter has the same value, and the input parameter has an independent address. Let's look at a simple example:
# Include <stdio. h>
Void pointer_plus (char * p)
{
P + = 3;
}
Int main ()
{
Char * a = "abcd ";
Pointer_plus ();
Printf ("% c \ n", * );
Return 0;
}
If a friend thinks that the output is d, you still haven't figured out the concept of value passing. In this program, copy a and assign it to p. From then on, a and p have no relationship, adding p to function pointer_plus actually increases the copy value of a, which does not affect a at all. In the main function, a still points to the first character of the string, therefore, the output is. If you want pointer_plus to change the object pointed to by a, use the second-level pointer. The program is as follows:
# Include <stdio. h>
Void pointer_plus (char ** p)
{
* P + = 3;
}
Int main ()
{
Char * a = "abcd ";
Pointer_plus (& );
Printf ("% c \ n", * );
Return 0;
}
5. Hanging pointer (Dangling pointer)
Hanging pointers are often used when we use pointers. The so-called hanging pointer is a pointer pointing to an uncertain memory area. Operations on such pointers may cause unpredictable errors to the program, therefore, we should avoid hanging pointers in the program. Some good programming habits can help us reduce the occurrence of such events.
There are usually three reasons for hanging pointers. We will discuss them one by one.
First, a pointer is not initialized when it is declared. The declared automatic variables are not initialized in the C language, so the default value of this pointer will be randomly generated, and it is likely to point to the System-protected memory, in this case, if the pointer is unreferenced, a runtime error is thrown. The solution is to initialize the pointer to a NULL or zero pointer constant when declaring it. You should get into the habit of initializing each newly created object. Some work done at this time will save you a lot of trouble.
Second, the pointer pointing to the dynamically allocated memory is used again after being free and no value is assigned again. Like the following code:
Int * p = (int *) malloc (4 );
* P = 10;
Printf ("% d \ n", * p );
Free (p );
......
......
Printf ("% d \ n", * p );
This may cause an error. First, we declare a p and point to a dynamically allocated memory space. Then, we assign a value to this space through p, and then pass free () the function releases the memory that p points. Note that the free function is used to release the memory space pointed to by p through the pointer p, and does not release p. The so-called release is to destroy the objects in this memory, and return the memory to the system for other purposes. The value in the pointer p is still the first address of the memory. If this memory is assigned to store other values, then the current value can be accessed by unreferencing p, however, if the status of the memory is uncertain, it may be protected, or it may not save any objects, a runtime error may occur if p is referenced, this error is very difficult to detect. Therefore, for the sake of security, after free a pointer, set this pointer to NULL or zero pointer constant. Although it is illegal to dereference null pointers, If we accidentally refer to null pointers, it is much easier to debug an error than to parse an error that points to an unknown object, because this error is predictable.
Third: A pointer pointing to a local variable is returned. Similar to the second type, this causes a pointer to an existing object, but the object no longer exists. The difference is that this object no longer exists. In the second reason, the reason that this object no longer exists is that the memory is manually released, and in the third reason, the pointer points to a local variable in a function, after the function is complete, local variables are automatically released (no need for programmers to manually release them ). The following procedure:
# Include <stdio. h>
# Include <stdlib. h>
Int * return_pointer ()
{
Int I = 3;
Int * p = & I;
Return p;
}
Int main ()
{
Int * rp = return_pointer ();
Printf ("% d \ n", * rp );
Return 0;
}
In the return_pointer function, create a pointer p pointing to the variable I in the function (the variable created in the function is called a local variable) and use the pointer as the return value. In the main function, a pointer receives the return value of return_pointer, then unreferences it and outputs it. The output may be 3, 0, or another value. The essential reason is that we have returned a pointer to a local variable, which will be destroyed by the compiler after the function is completed. The time for destruction is determined by the compiler, in this case, p may point to the memory that does not store any objects, or it may be a random value in the memory. In short, the memory is uncertain and p returns an Invalid Address.
This article from the CSDN blog, reproduced please indicate the source: http://blog.csdn.net/porscheyin/archive/2008/12/06/3461670.aspx