moveityourself01
moveityourself01

Reputation: 49

I can't understand why the command 'typedef' can be used like this

I was studying C99 - Function pointer part and my textbook offered a way to use 'typedef' for simplifying the code below :

int (*p[4])(int,int) = {&Sum,&Sub,&Mul,&Div};

typedef int (*OP_TYPE)(int,int);
OP_TYPE p[4]={&Sum,&Sub,&Mul,&Div};

I've known that 'typedef' can be used only like this : eg.

typedef unsigned short int US;

Was it not wrong, int would be altered as (*OP_TYPE)(int,int), and Isn't it right the second sentence become (*OP_TYPE)(int,int)(*p[4])(int,int) = {&Sum,&Sub,&Mul,&Div};

I can't understand why the second sentence could be like that.

Upvotes: 4

Views: 256

Answers (4)

netskink
netskink

Reputation: 4539

OP_TYPE is a function pointer type. Here is an example:

// This is a function
// It has a single parameter and returns
// an int
int some_func(int a) {
  return(a+1);
}

int main(void) {

  // regular usage of the function
  printf("some_func(1) = %d\n", some_func(1));

  // fun_ptr is a function pointer
  // that points to the function some_func
  int (*fun_ptr)(int) = &some_func;
  printf("via fun_ptr->some_func(2) = %d\n", fun_ptr(2));

  // create the typedef
  typedef int (*funptr_type)(int);
  // usage of typedef
  funptr_type another_func_ptr = &some_func;
  // usage of function pointer via typedef
  printf("via typedef some_func(3) = %d\n", another_func_ptr(3));

  return(0);

}

Usage

some_func(1) = 2
via fun_ptr->some_func(2) = 3
via typedef some_func(3) = 4

Upvotes: 0

John Bode
John Bode

Reputation: 123596

Was it not wrong, int would be altered as (*OP_TYPE)(int,int), and Isn't it right the second sentence become (*OP_TYPE)(int,int)(*p[4])(int,int) = {&Sum,&Sub,&Mul,&Div};

The declaration

typedef int (*OP_TYPE)(int, int);

does not change the meaning of int - it changes the meaning of OP_TYPE.

First, some background:

Declarations in C are broken into two main sections: a sequence of declaration specifiers, followed by a comma-separated list of declarators.

The declarator introduces the name of the thing being declared, along with information about that thing's array-ness, function-ness, and pointer-ness. In a typedef, that name becomes an alias for the type.

The structure of the declarator is meant to mirror the structure of an expression in the code. For example, suppose you have an array arr of pointers to int and you want to access the integer object pointed to by the i’th element; you’d index into the array and dereference the result, like so:

printf( "%d\n", *arr[i] ); // *arr[i] is parsed as *(arr[i])

The type of the expression *arr[i] is int; that's why we write its declaration as

int *arr[N];

instead of

int *[N] arr;

In the expression, the operand of the postfix [] subscript operator is arr, and the operand of the unary * dereference operator is the expression arr[i]. Those operators follow the same rules of precedence in a declaration, hence the structure of the declaration above.

The declaration reads as

     arr      -- arr
     arr[N]   -- is an array of
    *arr[N]   --   pointer to
int *arr[N];  --     int

Thus, object named arr has type "array of pointer to int";

Similarly, if you have an array of function pointers and you want to call one of those functions, you’d index into the array, dereference the result, and then call the resulting function with whatever arguments:

x = (*p[i])(a, b);

Again, the type of the expression (*p[i])(a, b) is int, so it follows that the declaration of p is written

int (*p[4])(int, int);

and not

int (*[4])(int, int) p;

Again, the declaration reads as

      p                  -- p
      p[4]               -- is a 4-element array of
     *p[4]               --   pointer to
    (*p[4])(        )    --     function taking
    (*p[4])(   ,    )    --       unnamed parameter
    (*p[4])(int,    )    --       is an int
    (*p[4])(int,    )    --       unnamed parameter
    (*p[4])(int, int)    --       is an int
int (*p[4])(int, int);   --     returning int

so the object named p has the type "array pointer to function taking two int parameters and returning int".

Clear so far?

So, how does typedef affect this?

Let's start with a declaration without the typedef:

int (*OP_TYPE)(int, int);

This declares OP_TYPE as an object of type "pointer to function taking two int parameters and returning int". If we add the typedef keyword:

typedef int (*OP_TYPE)(int, int);

it changes the meaning of the declaration such that OP_TYPE is an alias for the type "pointer to function taking two int parameters and returning int". It doesn't change the structure of the declaration at all, only the meaning. Thus, you can write

OP_TYPE fp;

and it will mean exactly the same thing as

int (*fp)(int, int);

Some other examples may help drive the concept home; going back to the earlier examples, if

int *ap[N];

declares ap as an object of type "4-element array of pointer to int", then

typedef int *ap[N];

declares ap as an alias for the type "4-element array of pointer to int", such that

ap foo;

is equivalent to

int *foo[N];

If

int (*blah)[N];

declares blah as an object of type "pointer to N-element array of int", then the declaration

typedef int (*blah)[N];

declares blah as an alias for the type "pointer to N-element array of int".

In the example you provide,

unsigned short int US;

declares US as object of type unsigned short int; thus,

typedef unsigned short int US;

declares US as an alias for the type unsigned short int.

Declarations (and thus typedefs) can get arbitrarily complex:

int *(*(*foo())[N])(double);

foo is a pointer to a function that returns a pointer to an array of pointers to functions taking a double parameter and returning a pointer to int. Adding a typedef:

typedef int *(*(*foo())[N])(double);

changes foo to be an alias for the type "function returning a pointer to an array of pointers to functions taking a double parameter and returning a pointer to int; again,

foo bar;

is equivalent to writing

int *(*(*bar())[N])(double);

Upvotes: 1

Ian Abbott
Ian Abbott

Reputation: 17513

int is not "altered" by a typedef. typedef unsigned short int US; is not "altering" unsigned short int, it is defining the identifier US to denote the same type as unsigned short int. Likewise, typedef int (*OP_TYPE)(int,int) is defining the identifier OP_TYPE to denote the same type as int (*)(int, int) (a pointer to a function returning int with parameter type list int, int).

Syntactically, typedef is like a storage class specifier but it defines a "typedef name" instead of declaring a variable. Compare typedef int (*OP_TYPE)(int, int); to static int (*op)(int, int);. OP_TYPE is a typedef name and op is a variable. OP_TYPE and op both have the same type int (*)(int, int). OP_TYPE can be used as a type in subsequent declarations, so that static OP_TYPE op; is equivalent to static int (*op)(int, int);

Upvotes: 0

tstanisl
tstanisl

Reputation: 14167

The typedef keyword behaves grammatically like storage modifiers (i.e. auto, register, static etc) except the initialization part. My guess the reason for that is that the early versions of C compilers shared code between variable declaration and type alias declarations.

Therefore the type alias declaration and variable declaration look more or less the same:

static  int (*A[4])(int,int); // staic variable
typedef int (*B[4])(int,int); // type alias
        int (*C[4])(int,int); // local variable

I guess the question is why one cannot do:

int (*[4])(int,int) C;

I have no good answer to that, C grammar is simply defined this this way. I can guess that without the anchor the compiler cannot correctly parse the type.

So why one can't do:

(int (*[4])(int,int)) C;

Answer:

It would solve the problem with missing anchor. However, it cannot be used because it will conflict with cast operator (type) expr. Note that symbol C may be defined on out scope resulting in ambiguity.

int C;
{
  // cast `C` to `int*` or maybe declare a pointer to `int`
  (int*)C;
}

However, there is a workaround with typeof extension (a feature in upcoming C23).

typeof(int (*[4])(int,int)) C;

This is more or less the same how the compiler expands the following declaration:

typedef int (*OP_TYPE)(int,int);
OP_TYPE p[4]={&Sum,&Sub,&Mul,&Div};

  to

typeof(int(*)(int,int)) p[4] = { ... }

Moreover, using this trick allows a neat and readable declaration of complex types.

typeof(int(int,int))* p[4] = { .... }

It is easy to see that p is a 4-element array of pointers to int(int,int) function.

Upvotes: 3

Related Questions