Let's say we have the following structure:
struct MY_STRUCT
{
int my_int;
float my_float;
};
We can define MY_STRUCT
to omit the struct
keyword so we don't have to type struct MY_STRUCT
each time we use it. This, however, is optional.
typedef struct MY_STRUCT MY_STRUCT;
If we then have a pointer to an instance of this struct
MY_STRUCT *instance;
If this statement appears at file scope, instance
will be initialized with a null pointer when the program starts. If this statement appears inside a function, its value is undefined. The variable must be initialized to point to a valid MY_STRUCT
variable, or to dynamically allocated space, before it can be dereferenced. For example:
MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;
When the pointer is valid, we can dereference it to access its members using one of two different notations:
int a = (*instance).my_int;
float b = instance->my_float;
While both these methods work, it is better practice to use the arrow ->
operator rather than the combination of parentheses, the dereference *
operator and the dot .
operator because it is easier to read and understand, especially with nested uses.
Another important difference is shown below:
MY_STRUCT copy = *instance;
copy.my_int = 2;
In this case, copy
contains a copy of the contents of instance
. Changing my_int
of copy
will not change it in instance
.
MY_STRUCT *ref = instance;
ref->my_int = 2;
In this case, ref
is a reference to instance
. Changing my_int
using the reference will change it in instance
.
It is common practice to use pointers to structs as parameters in functions, rather than the structs themselves. Using the structs as function parameters could cause the stack to overflow if the struct is large. Using a pointer to a struct only uses enough stack space for the pointer, but can cause side effects if the function changes the struct which is passed into the function.