This is a question far too deep to answer completely here. However, most compilers use a graph data structure to represent types. (Many years ago, the graphs were encoded in elaborate ways to save space, but these days that's not necessary.) The graph nodes for C are a recursive type (as most graph nodes are) roughly like this:
typedef enum {
VOID, INT, CHAR, DOUBLE, ENUM, POINTER, ARRAY, STRUCT, UNION, FUNCTION,
} KIND;
typedef struct type_s {
KIND kind;
union {
struct enumeration_s {
int n_values;
struct enum_value_s *values;
} enumeration;
struct pointer_s {
struct type_s *to_type;
} pointer;
struct array_s {
struct type_s *of_type;
size_t n_elements;
} array;
struct struct_or_union_s {
size_t n_fields;
struct field_s *fields; // Variable-sized array of fields.
} struct_or_union;
struct function {
struct type_s *return_type;
size_t n_args;
struct field_s *args; // Variable-sized array of args.
} function;
} u;
} TYPE;
typedef struct enum_value_s {
char *name;
int value;
} ENUM_VALUE;
typedef struct field_s {
char *name;
struct type_s *type;
} FIELD;
If you have already built a compiler, then you ought to know what an abstract syntax tree is. This is just an AST for types. You should be able to easily draw a graph (it's a graph because you want the nodes for the leaf types INT,... to be singletons) of the type for int ** (*fp)(int, int)
.
And yes (with the exception of the well known typedef ambiguity that you might already be handling) it's not hard to generate these type graphs in an LL(1) or LR(1) parser.