Typedef function pointers and extern keyword

I am having trouble understanding the syntax of a function pointer using typedef. I read a lot of answers, but still could not understand anything. I will try to explain how I see things so that you can understand my thoughts.

So, we use typedef to provide aliases to existing types, for example:

typedef int number; 

We make it so that we can use a number that is the same as an integer (similar to preprocessor directives - I know that there are some differences, for example, when creating a typedef pointer). Another example:

 typedef struct { int num; } MyStruct; 

Give the unnamed structure an alias named MyStruct.

So, here is the syntax of a pointer to a typedef function:

 typedef int (*pFunc)(int, int); 

Maybe it’s hard for me to understand this, since typedef is similar to a name giving TYPES aliases, and the function is not exactly a type, but in any case, in my opinion, it is more likely a pointer to some kind of function signature, so the first int is the return type , the second bracket indicates which types are arguments passed to the function. Now I do not quite understand this part:

 (*pFunc) 
  • I think we are creating a new type (using typedef) named pFunc, which is a pointer and that is the role *. Now we can create variables of this type that will point to any function with the signatures that we have described. Am I right?

Well, say I'm right, usually pointers to some memory are declared as follows:

 int *p; double *p; . . . 

Therefore, it does not make sense to do this as follows:

 (pFunc*) 

Since it seems to me that the asterisk in front of the name looks like pFunc - this is the name of a variable of type pointer of some type, not the actual type pointer.

  • Can we do this? If so, is it usually used to place an asterisk after and not earlier? If it is more common to confront it, why is that? Because, as I said, when we determine the type of pointer, we always put an asterisk after the name itself, as in the above examples, so why is this accurate?
  • Another question regarding this, I do not quite understand what the parentheses work around * pFunc. I think they are used to indicate that the type of the pFunc type is something, and if we did not put the brackets, then the returned signature type will be of type int *, and not just int, will I fix it here?

Okay, one more thing that bothers me is the syntax order. So far, in all typedef definitions, we had a type on the left and an alias on the right.

 typedef int number; typedef struct { int num; } MyStruct; 

We see that int and struct are the types on the left, and the alias we provided to them is on the right.

Now that the pointers to the typedef function do not match this convention. We have the return type of the function on the right, and then the type-name in brackets, and then the type of the arguments in brackets, this order confuses me, seeing how another typedef works with the same order.

  • Wouldn't it make more sense to do something like that?

    typedef int (int,int) Func; So, first we have typedef, the type we want to give the alias (es), which in this case is a signature function that takes 2 ints and returns int, and then on the right we have the alias. Doesn't that make sense? this follows a different typedef order, I just don't get the function pointer order, which is a lot ..

  • Another question of mine: when we make a pointer to a function, what does it really mean? I understand that we can call this function using our alias, but pointers like variables are stored in the memory address? What needs to be stored for the function?
  • Finally, I read that the extern keyword has something to do with function pointers, but could not understand what this keyword does, can someone explain to me what it does?
+5
source share
3 answers

typedef uses the same syntax to declare types, which is commonly used to declare values.

For example, if we declare an int called myInt , we do:

 int myInt; 

If we want to declare a type named myIntType as int, we simply add a typedef :

 typedef int myIntType; 

We can declare the myFunc function as follows:

 int myFunc(int a, int b); 

Tells the compiler that there is an actual function with this name and signature that we can call.

We can also declare the function type myFuncType by doing:

 typedef int myFuncType(int a, int b); 

And we could do:

 myFuncType myFunc; 

This is equivalent to the previous declaration of myFunc (although this form was rarely used).

A function is not a conditional value; it is a block of code with the address of the entry point. Function declarations like the ones above are implicitly extern ; they tell the compiler that the named thing exists elsewhere. However, you can take the address of a function called a function pointer. A function pointer can point to any function with the correct signature. The pointer is declared by prefixing the type / value name with * , so we can try:

 int *myFuncPtr(int a, int b); 

But that would be wrong, because * more closely related to int , so we announced that myFuncPtr is a function that returns a pointer to int . We need to put parens around the pointer and name to change the binding order:

 int (*myFuncPtr)(int a, int b); 

And to declare a type, we simply add a typedef to the beginning:

 typedef int (*myFuncPtrType)(int a, int b); 

In the myInt above, the compiler allocated some memory for the variable. However, if we were writing some code in another compilation unit and wanted to reference myInt , we would need to declare it as extern (in the compilation reference unit) so that we would reference the same memory. Without extern compiler would allocate a second myInt , which would result in a linker error (in fact, this is not entirely true, because C allows for preliminary definitions that you should not use).

As noted above, functions are not normal values ​​and are always implicitly extern . However, function pointers are normal values ​​and need extern if you are trying to reference a global function pointer from a separate compilation unit.

Usually you add extern for your global variables (and functions) to the header file. You then include the header in compilation units that contain definitions of these variables and functions so that the compiler can verify that the types match.

+6
source

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; // two int variables a and b int *a, b; // pointer to an int variable a and an int variable b int a, *b, **c; // int variable a, pointer to an int variable b, and pointer to a pointer to an int variable c 

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; // set a == 5 b = &a; // set b == address of a c = &b; // set c == address of b which in this case has the address of int variable a d = **c; // put value of a into d using pointer to point to an int variable a d = *b; // put value of a into d using pointer to an int variable a d = a; // put value of a into d using the variable a directly 

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 { // create a struct declaration with the tag of myTagStruct int a; int b; }; struct myTagStruct myStruct; // create a variable myStruct of the struct 

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; // create typedef for pointer to an int int *a; // create a variable that is a pointer to an int pInt b; // create a variable that is a pointer to an int typedef int (*pIntFunc)(int a1, int b1); // create typedef for pointer to a function int (*pFuncA)(int a1, int b1); // create a variable pFuncA that is a pointer to a function pIntFunc pFuncB; // create a variable pFuncB that is a pointer to a function 

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(); } 
+1
source

To clarify the explanation given by others in C / C ++, the brackets are correct associative, so the following expression:

 typedef int *pFunc(int, int); 

is equivalent to:

 typedef int *(pFunc(int, int)); 

which will be the prototype of declaring a function that returns a pointer to an integer, rather than declaring a pointer to a function that returns an integer.

That's why you need to write parentheses around (*pFunc) to break the correct connection and tell the compiler that pFunc is a pointer to a function, not just a function.

+1
source

Source: https://habr.com/ru/post/1203048/


All Articles