I'm having problems understanding the syntax of a pointer to a function using typedef. I've read a lot of answers but still couldn't understand something. I'll try to explain how I see things so you could understand my thinking.
So we use typedef to give aliases to existing types for example :
typedef int number;
Will make it so we could use a number same as an integer (similar to preprocessor directives - I know there're some differences like when making a typedef of a pointer). Another example :
typedef struct
{
int num;
} MyStruct;
Will give the unnamed structure an alias named MyStruct.
So Here's the syntax of a pointer to function typedef:
typedef int (*pFunc)(int, int);
Maybe I'm having hard time to understand this since typedef is like it's name giving aliases to TYPES and a function is not exactly type but anyway, from my understanding this is more of a pointer to some sort of a function signature, so the first int is the returned type, the second parenthesis are to indicate what types are the arguments being passed to the function. Now what I don't quite understand is this part :
(*pFunc)
Ok, say I'm correct, usually pointers to some memory are declared as follow :
int *p;
double *p;
.
.
.
So wouldn't it make more sense to do it as follow :
(pFunc*)
Because to me it looks like if the asterisk is before the name it looks like pFunc is a variable name of type pointer of some type and not an actual type pointer.
Ok, another thing that bothers me about it as the order of the syntax. So far, in all typedef definitions we had the type on the left and the alias(es) on the right.
typedef int number;
typedef struct
{
int num;
} MyStruct;
We see that the int and the struct are the types which are being on the left and the alias we gave them are on the right.
Now, in pointers to function typedef it doesn't follow this convention. We have the type returned of the function on the right then the typename in parenthesis then the type of the arguments in parenthesis, this order makes me confused after looking on how the other typedef are working on the same order.
Wouldn't it make more sens to do something like this ? :
typedef int (int,int) Func;
So we have a typedef first, the type we want to give alias(es) to, which in this case is a function
signature which takes 2 ints and return an int, and then on the right
we have the alias name. Won't it make more sense ? this follows the
other typedef order, I just don't get the function pointer order that
much ..
The typedef
uses the same syntax for declaring types as would normally be used for declaring values.
For instance, if we declare an int
called myInt
, we do:
int myInt;
If we want to declare a type called myIntType
to be an int, we simply add typedef
:
typedef int myIntType;
We can declare a function myFunc
, as follows:
int myFunc(int a, int b);
Which tells the compiler that there is an actual function with that name and signature that we can call.
We can also declare a function type myFuncType
by doing:
typedef int myFuncType(int a, int b);
And we could do:
myFuncType myFunc;
Which is equivalent to the previous declaration of myFunc
(although this form would rarely be used).
A function is not a conventional value; it represents a block of code with an entry point address. Function declarations like those above are implicitly extern
; they tell the compiler that the named thing exists somewhere else. However, you can take the address of a function, which is called a function pointer. A function pointer can point to any function with the correct signature. A pointer is declared by prefixing the name of the type/value with a *
, so, we might try:
int *myFuncPtr(int a, int b);
But this would be incorrect because the *
binds more tightly with the int
, so we have declared that myFuncPtr
is a function that returns a pointer to an int
. We must 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 typedef
to the front:
typedef int (*myFuncPtrType)(int a, int b);
In the declaration of myInt
above, the compiler allocated some memory for the variable. However, if we were writing some code in a different compilation unit, and wanted to reference myInt
, we would need to declare it as extern
(in the referencing compilation unit) so that we reference the same memory. Without the extern
, the compiler would allocate a second myInt
, which would result in a linker error (actually that's not quite true because C allows tentative definitions, which you shouldn't use).
As noted above, functions are not normal values, and are always implicitly extern
. However, function pointers are normal values, and need the extern
if you are trying to reference a global function pointer from a separate compilation unit.
Normally, you would put extern
s for your global variables (and functions) into a header file. You would then include the header into the compilation units that contain the definitions of those variables and functions so that the compiler can make sure the types match.
The syntax for a variable definition or declaration is the type followed by one or more variables possibly with modifiers. Some simple examples would be:
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
Notice that in all of these the asterisk modifies the variable to the right of the asterisk, changing it from an int
into a pointer to an int
or a pointer to a pointer to an int
. The variables defined might be used like:
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
statement
The extern
statement is used to indicate that the definition of a variable is located in some other file and that the variable has global visibility. So you can declare a variable using the extern
keyword to be explicit about a variable so that the C compiler will have the information it needs to do a good level of checking when compiling. The extern
indicates that the variable is actually defined with its memory allocation somewhere other than the file where the source using the variable is located.
Using typedef
The typedef
is a very nice feature of modern C because it allows you to create an alias that amounts to a kind of halfway new type. To have the full capability of creating a new type really requires the class type features of C++ which allows the definition of operators for the new type as well. However typedef
does provide a good way of allowing a programmer to create an alias for a type.
Most uses of typedef are to provide a way to make it shorter and cleaner to write a variable definition. It is used a lot with struct
definitions for that reason. So you might have a struct
definition like the following:
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 a pointer to the struct
and these might be used like:
MyStruct exampleStruct;
PMyStruct pExampleStrut;
pExampleStruct = &exampleStruct;
This example has the basic structure of typedef
keyword, definition of the new type in terms of existing types, and name of the new type.
Old style C before typedef
Years ago in the older C compiler days before typedef
was added to the C standard, people would often use the C Preprocessor to define macros to create an alias for a complex type. typedef
is a much cleaner way of doing it!
Before typedef
was added to the C standard, you would specify a tag for the struct and the result would be code that looked 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
At which pointer people would usually add a C Preprocessor define to make it easier to write as in:
#define MYTAGSTRUCT struct myTagStruct
and then use it something like:
MYTAGSTRUCT myStruct;
However there is one major difference between using the preferred typedef
syntax rather than the Preprocessor define
approach. The Preprocessor works with the text of the C source code file to generate a modified version of the C source which is then compiled by the C compiler. The typedef
keyword is part of the C source compiled by the C compiler so the C compiler knows about the type alias defined.
To show the difference, see the following source code.
#define PMYSTRUCT MyStruct *
typedef struct {
int a1;
int b1;
} MyStruct, *PMyStruct;
MyStruct sA, sB; //
PMyStruct psA, psB; // compiler sees this as MyStruct *psA, *psB;
PMYSTRUCT psxA, psxB; // Preprocessor generates MyStruct * psxA, psxB;
psA = &sA;
psB = &sB;
psxA = &sA;
psxB = &sB; // compiler error - psxB is not a pointer variable
Using typedef
with function pointers
The syntax of typedef
for function pointers is a bit unusual. It looks somewhat like a function declaration but does have a slight twist with an additional pointer syntax.
typedef int (*pFunc)(int a1, int b1);
This says the following:
typedef
for a variable type pFunc
pFunc
type is a pointerint
arguments that returns an int
The parenthesizes are important because they force the compiler to interpret the source text in a way different from the default rules. The C compiler has rules that it uses to parse the source text and you can change the way that the C compiler interprets the source text by using parenthesizes. The rules have to do with the parsing and how the C compiler locates the variable name and then determines the type of the variable by using rules about 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 syntax of a typedef
is similar to the syntax for a variable definition that is not using a 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
typedef int *pFuncWhat(int a1, int b1); // create a typedef for a function that returns a pointer to an int. seems to be legal but useful? doubt it.
int (*pFuncA)(int a1, int b1); // create a variable pFuncA that is a pointer to a function
int *FuncDecl(int a1, int b1); // declare a function that returns a pointer to an int
pIntFunc pFuncB; // create a variable pFuncB that is a pointer to a function
So what does it mean to have a pointer to a function? A function entry point has an address because a function is machine code that is located at a particular memory area. The address of the function is where the execution of the functions machine code is supposed to start.
When the C source code is compiled, a function call is translated into a series of machine instructions which jump to the address of the function. The actual machine instructions are not really a jump but are instead a call instruction which saves the return address before it makes the jump so that when the called function completes it can do a return back to where it was called from.
A function pointer variable is used like a function statement. The difference between the two is similar to the difference between an array variable and a pointer variable. An array variable is treated like a constant pointer to a variable by most C compilers. A function name is treated like a constant pointer to a function by most C compilers.
Using a function pointer
What a function pointer does give you though is flexibility though it is flexibility that as with any great power can also lead to great ruin.
One use of function pointer variables is to pass a function address as an argument to another function. For instance the C Standard library has a couple of sort functions that require an argument of a collation function for comparing two elements being sorted. Another example would be a threading library that when you create a thread, you specify the address of the function to be executed as a thread.
Since a function pointer is a variable then if you have a function pointer that needs global visibility when you declare the variable for files other than the source file where it is actually defined and its memory allocated you would use the extern
keyword as part of the function pointer variable declaration. However if it is a variable that is allocated on the stack within a function or if it is used within a struct
to create a member of the struct
then you would not use the extern
modifier on the variable.
file1.c
// define a function that we are going to make available
// through a function pointer. a function will have global
// visibility unless we use the static keyword to reduce the
// visibility to this source file.
static int myfunc(int a, float b)
{
return (a + (int) (b * 100.0));
}
// define a function pointer that contains the address of the
// function above that we are exporting from this file.
// this function pointer variable automatically has global visibility
// due to where the statement is located in the source file.
int(*pmyfunc)(int, float) = myfunc;
file1.h
// declare the function pointer, which has global visibility
// due to where it was defined in the source file. we declare
// the function pointer in an extern in order to make the
// function prototype with argument types available to the compiler
// when using this variable in other source files.
extern int(*pmyfunc)(int, float);
file 2.c
#include "file1.h"
int iifunc (int a, int b)
{
return (a + b/10 + 5);
}
// define a function that has as an argument a function pointer
// variable. this allows the caller to inject into the processing
// of this function a function to be used in the function.
int jjfunc (int a, int (*pf)(int, float))
{
return ((a / 10) + pf(a, 2000.0));
}
int kkfunc (int a, char *pName)
{
// an example of a local definition of a function pointer.
// we could have used pmyfunc directly.
int(*plocalfunc)(int, float) = pmyfunc;
// following two statements show difference between calling a
// function with a function pointer argument and calling a
// function with a function call in the argument list.
int k = jjfunc(a, plocalfunc);
int l = iifunc(a, pmyfunc(a, 3000.0));
printf ("%s - %d\n", pName, k);
return k;
}
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 output sinks or places where the output to go, say a file, a printer, and a terminal window. This is similar in nature to how virtual functions are implemented by C++ compilers or how COM objects are implemented through a COM interface. So you could do something like the following which is a very simple example 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();
}
Just to make clear the explanation given by others, in C/C++, the parenthesis are right associative, therefore the following declaration:
typedef int *pFunc(int, int);
is equivalent to:
typedef int *(pFunc(int, int));
which would be the declaration prototype of a function returning a pointer to an integer and not the declaration of a pointer to a function returning an integer.
This is why you need to write the parenthesis around (*pFunc)
to break the right association and tell the compiler that pFunc is a pointer to a function and not simply a function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With