Introduction to C —6 — Type definitions: typedef and Understanding C type declarations
What is a typedef
?
Types can sometimes be either complex or non-descriptive. For example, the size of a data structe may return an unsigned int
on one system, while on another it return an unsigned long
. It would be much more helpful to a programmer to have a standard name for the type returned by an operator such as sizeof
or a function that performs a similar task. Thus, the following alias for the type returned by functions and operators related to memory used is
typedef unsigned int size_t;
Thus, any time a variable is declared to be unsigned int
and it will behave as an unsigned int
.
One issue in C and C++ is that there is no default size of int
or long
. Consequently, many libraries designed for a specific system will redefine types to indicate their size:
typedef signed char S8;
typedef char U8;
typedef short S16;
typedef unsigned short U16;
typedef int S32;
typedef unsigned int U32;
typedef long long S64;
typedef unsigned long long U64;
By default, char
is unsigned while short
, int
, and long
are signed. Now if a variable is declared to be of type U32
, the programmer knows it is a 32-bit unsigned integer.
You can also define a type to be an array of a given type, such as
typedef U32 OS_SEM[2];
typedef U32 OS_MUT[3];
typedef U32 *OS_ID;
In this case, variables, arguments, and return types declared to be OS_SEM
, OS_MUT
, and OS_ID
will be, respectively, an array of two 32-bit unsigned integers, an array of three 32-bit unsigned integers, and a pointer to a 32-bit unsigned integer.
Substitution rule
Whenever you see a variable, argument, or return value declared to be a type that has been defined through typedef
, you need only substitute the variable for the typedef symbol in the typedef definition. For example, if
typedef struct{
S8 name[256];
U32 size;
U16 fileID;
U8 attrib;
RL_TIME time;
} FINFO;
and a variable is defined as an array of three instances of this type:
FINFI array[3];
this would be equivalent to
struct {
S8 name[256];
U32 size;
U16 fileID;
U8 attrib;
RL_TIME time;
} array[3];
Using typedef
with struct
A struct may be given a name in two ways:
struct struct_name{
/* fields */
}
typedef struct {
/* fields */
} Struct_name;
These two can be combined:
struct struct_name {
/* fields */
} Struct_name;
In the first case, you must always use the keyword struct
, but in this case, the symbol struct_name
can be continue to be used as either a variable or a function name: prefixed by struct
it means the structure, and without the prefix it refers to the function or variable. With the typedef
, the defined alias is now global.
Note that the alias is defined at the end of the statement: consequently, using typedef
, one cannot define a pointer to the structure within the structure. If you wanted to define a node within a linked list, and you wanted a typedef
, you would have to try something like:
typedef struct struct_name{
int value;
struct struct_name *next; /* a pointer to this structure */
} Struct_name;
Understanding C type declarations
Why is *
used for both declaring pointers and dereferencing them? Basically, the notation of a variable declaration says what you must do to something to get something to get something of the specified type. For example:
int x;
To get an integer, just use x.int *x;
To get an integer, you must use *x.int x[3];
To get an integer, you must use x[n].int f(int, double);
To get an integer, you must call f(n, x) where n is an integer, and x is a double.int *g(void);
To get an integer, you must call g() and then dereference the result.
This is always he way that variables are declared in C.
Now, how about the declaration
int *r[10];
Is this
- A pointer to an array of integers, or
- An array of 10 pointers to integers?
- At this point, you must look at an operator precedence table to determine that if you were to call
int (*r)[10];
That is, if you dereference r and then use brackets, you get an integer.
For more fun with C declarations, https://cdecl.org/
int const *r versus int *const r
One of these declares that r cannot be changed, while the other declares that the value of r cannot be changed. From our reasoning above, we should be able to determine this:
int const *r
The thing that is consist is *r
, that is, the integer value is constant. Therefore, you can assign to r
, but you cannot assign to *r
int *const r
The thing that is const is r
, that is, something that must be dereferenced before it can be interpreted as an integer. Therefore, you can assign to *r, but you cannot assign to r
.
Note that const int *r
is identical to int const *r
Also note that int const *const r
and const int *const r
also both mean the same thing: you cannot assign to r
and you cannot assign to *r
, either.