It's been a while since I've written in C, so when I see this I apply my C++ instincts first and have to remind myself this isn't C++, it's C. I come from a time before C++, and I wrote in C for years, but I'm realizing how far back I have to reach to remember this (around 30 years).
First, type still means something. The fact that two structures have otherwise identical contents means nothing to the compiler, if the names of the types involved aren't the same then the types may not be the same. The two structs you think of being the same design are still two separate structs to the compiler.
That said, realize that the type of the return from malloc is not a number. The type is a void *. I know it looks like a number (indeed, it is an unsigned integer in reality), but the type is not a number. It is a void *. That means something different to C than it does to C++, and I have to think really hard about that to remember.
In C there's no complain assigning a pointer of any type to a void pointer. The compiler takes that to mean you're in charge, determining that the void * returned from malloc represents, to you, a pointer to whatever you're assigning it to. Thus,
Code:
void *x = malloc( sizeof( Months_Node ) );
Months_Node *mn = x;
This does not generate a complaint from the compiler, and as far as the compiler is concerned, since x is a void *, assignment of a Months_Node pointer to x is no different than assigning it to what was returned from the malloc. They are both void pointers and the compiler is fine doing that (in C++, it isn't - it's an error, not just a warning).
Now, this is a problem:
Code:
Day_Node *dn = malloc( sizeof( Day_Node ) );
Months_Node *mn = dn;
This generates a warning from C (an error in C++), and it is because Day_Node and Months_Node are different types. Despite the layout and content being similar or identical, the types are generated separately as separate structs and are not likely compatible in the compiler's view. It may allow it, but chances are you're not getting what you expect.
Code:
Months_Node *mn1 = 0;
Months_Node *mn2 = (void *)0;
Months_Node *mn3 = NULL;
For C, NULL is defined. How it is defined may depend on the compiler. Some may define it as the assigment in mn2, others may define it as the assignment in mn1, and you'll find all 3 of these compile without issue in C. That's because the compiler does "understand" that the pointer is set to a zero value (a null pointer) that is reasonable to do. It can be compared in that way, too, without confusion. This isn't true in C++ (types are a little stricter there), but in the underlying truth of what it means to "be" a pointer, assignment to zero or comparison to zero is a rather obviously sane thing to do and does not really invoke any issues with type here.
Note, however, this is not so easily passed by the compiler:
Code:
Months_Node *mn1 = 2;
This would generate a warning at least. Here, the compiler is noticing that a pointer is assigned to an integer, and that DOESN'T make much sense. Assignment to zero did, and so the context may well ignore that 0 is an integer type - it just happens to be a special integer where 0 has relevance to the content of a pointer, but 2 (or any integer) doesn't - without there being some curious context to make it work.