<C traps and defects> -- "Understanding function declaration"
Once, a programmer talked to me about a problem. He was writing a C program that runs independently on a microprocessor. When the computer starts, the hardware calls the child routine with the first address 0. To simulate startup, we must design a C statement to explicitly call the routine. After a period of thinking, we finally get the following statement:
(* (Void (*) () 0 )();
An expression like this may make every C programmer feel "chilling ". However, they don't have to worry about it, because there is only one simple rule to construct such expressions: to declare them in the way they are used.
The declaration of any c variable consists of two parts: type and a set of declarations similar to expressions (declarator ). On the surface, the Declaration is similar to the expression. evaluate it and return a result of the given type in the Declaration. The simplest declaration Operator is a single variable. For example:
Float F, G;
The meaning of this statement is: when the expression F and G are evaluated, the type of the expression F and G is float ). Because the declaration Operator is similar to the expression, we can also use any brackets in the declaration Operator:
Float (f ));
The meaning of this statement is: when the value is evaluated, the (f) type is a floating point type, which can be inferred that F is also a floating point type.
The same logic applies to declarations of function and pointer types, for example:
Float ff ();
The meaning of this statement is: The expression ff () returns a floating point number, that is, FF is a function that returns a floating point dung type. Similarly,
Float * PF;
This statement indicates that * PF is a floating point number, that is,. PF is a pointer to a floating point number.
These forms can also be combined in the declaration, just as in the expression. Therefore,
Float * g (), (* H )();
* G () and (* H) () are floating point expressions. Because the combination priority of () is higher than *, g () is * (G (): G is a function, and the return value type of this function is a pointer to a floating point. Similarly, we can conclude that H is a function pointer, And the return value of H pointing to the function is a floating point type.
Once we know how to declare a variable of a given type, the type conversion operator of this type is easy to get: just remove the variable name and semicolon at the end of the Declaration in the Declaration, then, encapsulate the remaining parts with a bracket. For example, because of the following statement:
Float (* H )();
Indicates that H is a pointer to a function with a return value of the floating point type. Therefore,
(Float (*)());
It is a type conversion character that refers to a pointer to a function with a return value of the floating point type.
With these preparations, we can now analyze the expression (* (void (*) () 0) () in two steps )().
Step 1: assuming that the variable FP is a function pointer, how can we call the function that FP points? The call method is as follows:
(* FP )();
Because FP is a function pointer, * FP is the function pointed to by this pointer, so (* FP) () is the method to call this function. The ansic standard allows programmers to abbreviated the above formula as FP (), but remember that this method is just a short form.
In the expression (* FP) (), * brackets on both sides of the FP are very important because the priority of the function operator () is higher than that of the single object operator *. If * FP has no parentheses on both sides, * FP () actually has exactly the same meaning as * (FP (). ansic regards it as * (* FP )()).
Now, the problem is to find an appropriate expression to replace FP. We will solve this problem in step 2 of the analysis. If the C compiler can understand the types in our brains, we can write as follows:
(* 0 )();
The preceding formula does not take effect, because the operator * must have a pointer for the operand. In addition, this pointer should also be a function pointer so that the result after the operator * can be called as a function. Therefore, type conversion must be performed on 0 in the above formula. The converted type can be roughly described as: "pointer to function with return value of void type ".
If FP is a pointer to a function whose return value is void, the value of (* FP) () is void. The FP declaration is as follows:
Void (* FP )();
Therefore, we can use the following method to call a subroutine whose storage location is 0:
Void (* FP )();
(* FP )();
Note: FP is initialized to 0 by default.
The cost of this writing is to declare a "dumb" variable.
However, once we know how to declare a variable, we naturally know how to convert the type of a constant to the type of the variable: you only need to remove the variable name from the variable declaration.
Therefore, the constant 0 is converted to the "pointer to the function whose return value is void" type, which can be written as follows:
(Void (*) () 0;
Therefore, we can replace FP with (void (*) () 0 to get:
(Void (*) () 0 )();
The semicolon at the end makes the expression a statement.
When I first solved this problem, there was no typedef statement in C language. Although typedef is not used to solve this problem, it is a good way to analyze the details of this example, but it is clear to use typedef to make the statement clearer:
Typedef void (* function )();
(* (Function) 0 )();