This is a naming confusion.
node
== struct data
thanks to typedef
.
But node
!= struct node
. They are 2 distinct types. And struct node
has not been defined.
This is a pity that such a confusing construction is allowed by the C standard, and not even flagged as a warning by compilers. But that's the way C is defined today, so we have to live with it.
My recommendation : don't use the same names for struct
and typedef
. Create your own convention, for example struct thing_s
and typedef struct thing_s thing_t;
. This will avoid naming confusions such as this one.
The solution is now pretty obvious.
Replace :
typedef struct data { int num; struct node *link; } node;
by
typedef struct data { int num; struct data *link; } node;
and the problematic printf
will now work.
But your question is : why does your program worked at all without this printf
?
Now that's an interesting one.
Let's go back to the initial definition.
As we said, struct node
doesn't exist, and is therefore incomplete.
Just to better follow what we are going to explain, let's call it struct incomplete_s
instead. Now, node
becomes :
typedef struct data { int num; struct incomplete_s *link; } node;
It will still work without the problematic printf
.
The reason is, node
is properly defined. It is a structure with known size and types. Because it doesn't matter that struct incomplete_s
is incomplete, since link
is defined as a pointer to it. You could as well have defined void * link;
and it would still work.
void*
or struct incomplete_s *
or whatever *
have all same size : they are all pointers. So the structure hosting them can be properly created.
In your main loop, your program does :
while(e!=NULL)
{
printf("%d\n",e->num);
e=e->link;
}
that is e
, which is a pointer node*
, takes the value of e->link
, which is a pointer struct incomplete_s *
.
Note that both pointers are supposed to point to different types.
But they are both pointers, so yeah, this assignment is technically possible and authorized by the standard.
Now a more cautious compiler would likely issue a warning here, since you are not supposed to mix pointers of different types. This is a silent type-casting, which is a recipe for future bugs.
I don't know which compiler you use, but you could increase its warning level (use "warning level 4" for Visual, or -Wall -Wextra
for gcc), and it will likely not like this =
operation.
A more explicit type casting would solve that (*) :
e = (node*)(e->link);
Now this is no more silent, programmer is in charge, warnings will disappear.
e->link
definitely exists, so compiler can grab this value.
But s->link->num
doesn't exist, since s->link
is struct incomplete_s*
, and we don't know what it is, so we don't know if it has a num
member.
(*) Overkill complement : At some higher optimization level, this will still be not good : strict aliasing might get in the way. So dereferencing a pointer type into another pointer type remains a dangerous operation.