The syntax for defining or declaring a variable is a type that is followed by one or more variables, possibly with modifiers. So, some examples:
int a, b;
Note that in all these cases, the asterisk changes the variable to the right of the asterisk, changing it from int to a pointer to int or a pointer to a pointer to int. Therefore, they can be used as:
int a, *b, **c, d; a = 5;
The extern operator is used to indicate that the variable definition is located in some other file and that the variable has global visibility. That way, you can declare a variable using the extern keyword to be explicit about the variable so that the C compiler has the information needed to perform a good level of compilation validation. extern indicates that the variable is actually defined with its memory allocation somewhere other than the file in which the source using the variable is located.
Since the function pointer is a variable, if you have a pointer to a function that requires global visibility, when you declare a variable for files other than the source file, where it is actually defined, and allocated memory, you should use the extern keyword as part of declaring a function pointer variable. However, if it is a variable that is allocated on the stack inside a function, or if it is used in a struct to create a struct member, you will not use the extern modifier for the variable.
typedef is a very nice feature of modern C because it allows you to create an alias that is a kind of half new type. For the full possibility of creating a new type, functions of the type of the C ++ class are actually required, which also allow you to define operators for the new type. However, typedef does provide a good way to let the programmer create an alias for the type.
A few years ago, in the older C compiler, the C standard was added before typedef , people often used the C preprocessor to define macros to create an alias for a complex type. typedef is a much cleaner way to do this!
Most uses of typedef are a way to make it shorter and cleaner to write a variable definition. For this reason, it is used with struct definitions. So you can have a struct definition as shown below:
typdef struct { int iA; int iB; } MyStruct, *PMyStruct;
This will create two new aliases for the struct , one for the struct itself and one for the pointer to the struct , and they can be used as:
MyStruct exampleStruct; PMyStruct pExampleStrut; pExampleStruct = &exampleStruct;
This example shows the basic structure of the typedef keyword, the definition of a new type in terms of existing types, and the name of the new type.
Before typedef was added to the C standard, you must specify a tag for the structure, and the result will be code that looks like this:
struct myTagStruct {
In which pointers do people usually add the C preprocessor definition to make it easier to write, as in:
#define MYTAGSTRUCT struct myTagStruct
and then use it like:
MYTAGSTRUCT myStruct;
However, the typedef syntax for function pointers is slightly different. This is more along the lines of a function declaration, where the function name is the actual typedef name or alias that is actually used. The basic typedef structure for a function pointer is the typedef keyword, followed by the same syntax as the function prototype, where the function name is used as the typedef name.
typedef int (*pFunc)(int a1, int b1);
Brackets are important because they force the compiler to interpret the source in a certain way. The C compiler has rules that it uses to parse the source text, and you can change the way the C compiler is interpreted by the source text using parentheses. The rules are related to parsing and how the C compiler finds the variable name and then determines the type of the variable using the rules of left and right associativity.
a = 5 * b + 1; // 5 times b then add 1 a = 5 * (b + 1); // 5 times the sum of b and 1 int *pFunc(int a1, int b1); // function prototype for function pFunc which returns a pointer to an int int **pFunct(int a1, int b1); // function prototype for function pFunc which returns a pointer to a pointer to an int int (*pfunc)(int a1, int b1); // function pointer variable for pointer to a function which returns an int int *(*pFunc)(int a1, int b1); // function pointer variable for pointer to a function which returns a pointer to an int
A function prototype is not a function pointer variable. The typedef syntax is similar to the variable definition syntax that does not use typedef .
typedef int * pInt;
So what does it mean to have a function pointer? The function entry point has an address, because a function is machine code that resides in a specific area of ββmemory. The function address is the start of the machine function code launch.
When the C source code is compiled, the function call is converted to a series of machine instructions that go to the function address. The actual machine instructions are not really jumps, but instead are a call instruction that saves the return address before it jumps, so that when the called function is completed, it can go back to where it was called from.
The function pointer variable is used as the function operator. The difference between them is similar to the difference between an array variable and a pointer variable. An array variable is treated as a constant pointer to a variable by most C compilers. A function name is treated as a constant pointer to a function by most C compilers.
What gives you a pointer to a function is flexibility, although it is flexibility that, like any other force, can also lead to great destruction.
One use of function pointer variables is to pass the address of the function as an argument to another function. For example, the C Standard library has several sort functions that require an argument to the sort function to compare two sorted elements. Another example is the thread library, which, when creating a thread, sets the address of the function that will be executed as a thread.
Another case is to provide some kind of interface that hides implementation details. Let's say that you have a print function that you want to use for several different receivers or in places where the output should go, for example, a file, printer and terminal window. This is similar to how virtual functions are implemented by C ++ compilers or how COM objects are implemented through the COM interface. So you can do something like the following, which is a very simple example of the missing details:
typedef struct { int (*pOpenSink) (void); int (*pPrintLine) (char *aszLine); int (*pCloseSink) (void); } DeviceOpsStruct; DeviceOpsStruct DeviceOps [] = { {PrinterOpen, PrinterLine, PrinterClose}, {FileOpen, FileLine, FileClose}, {TermOpen, TermLine, TermClose} }; int OpenDevice (int iDev) { return DeviceOps[iDev].pOpenSink(); } int LineDevice (int iDev, char *aszLine) { return DeviceOps[iDev].pPrintLine (aszLine); } int CloseDevice (int iDev) { return DeviceOps[iDev].pCloseSink(); }