Recently I’ve been working mostly with C code and one thing I noticed over and over was that most of the structs were declared with a typedef..

For example:

  
 typedef struct {  
 int a;  
 int b;  
 } my_struct_t;  

instead of:

  
 struct my_struct {  
 int a;  
 int b;  
 };  

The main difference between the two declarations is that the one with the typedef creates a new type called mystructt and the latter creates a tag called my_struct, not a type.

So this code would be valid:

  
 struct my_struct my_struct;  

it is creating a variable with the name my_struct that has a type of
struct my_struct. The compiler treats tags and types differently.

Using typedef when declaring structs besides saving some keystrokes makes the code easier to read since you don’t need to explicit say the keyword struct everytime you want to refer to your struct.

So instead of coding:

  
 struct my_struct some_function(int a, struct my_struct);  

you can reference struct my_struct by its new type:

  
 my_struct_t some_function(int a, my_struct_t);  

Another important thing to mention, is if you need to reference the struct you are declaring as one of its own members, for example in a linked list.

  
 typedef struct S1 {  
 int a;  
 int b;  
 struct S1 *s;  
 struct S1 s; // error: field ‘s’ has incomplete type  
 S1_t *s; // error: ‘S1_t’ does not name a type  
 } S1_t;  

The error “field ‘s’ has incomplete type” happens because one of the members of the struct is the struct itself, so the compiler looks up for the struct S1 type but it can’t find, since it has not been declared yet.

The same thing happens if you try to reference the name giving in the typedef, in this case S1_t, S1_t represents a struct S1. However during the declaration of struct S1, S1_t doesn’t exist yet.

The solution is to create a pointer to a struct S1.
A pointer points to a memory address, it doesn’t matter the type of the data the pointer is pointing to, the memory address will always have the same size, so the compiler knows how to interpreter during compilation time.

Now at runtime you can allocate memory for a struct s1 and assign to the s pointer.
The only thing the compiler will check is if the memory block represents a struct s1, since it knows what a struct s1 looks like.

  
 #include   
 #include 

typedef struct S1 {  
 int a;  
 int b;  
 struct S1 *s;  
 // struct S1 s; // error: field ‘s’ has incomplete type  
 // S1_t *s; // // error: ‘S1_t’ does not name a type  
 } S1_t;

int main (void) {  
 S1_t s1;  
 S1_t s2;

 s1.a = 1;  
 s1.b = 2;

 s2.a = 3;  
 s2.b = 4;

 //s1.s = &s2;  
 s1.s = (S1_t*) malloc(sizeof(S1_t));  
 s1.s->a = 3;  
 s1.s->b = 4;

 printf("s1.a: %dn", s1.a);  
 printf("s1.b: %dn", s1.b);  
 printf("s1.s->a: %xn", s1.s->a);  
 printf("s1.s->b: %xn", s1.s->b);

 return 0;  
 }  

You can find a more detail explanation here.