First of all, you must realize that you are using the comma operator here. See What does the comma operator , do?
There is no practical difference between the comma operator and semicolons. The key here is to understand sequence points, an abstract C term for points in the source where all previous evaluations must be done. A semicolon always introduces a sequence point, but so does the comma operator. That's why your code works and why z->a=1
is guaranteed to be executed ("sequenced") before z->b=2
.
However, using the comma operator leads to strange, hard-to-read code. It is often confused with commas in initializer lists or function call parameter lists. In your comment, you confuse this for a multiple variable initialization.
As a rule of thumb, the comma operator should not be used at all, except for very specialized cases.
The main use of the comma operator and the reason why it is still in the language, is that it allows for function-like macros that do multiple things but still return a value like a function. Example:
#include <stdio.h>
int foo (void)
{
puts("hello world");
return 1;
}
#define foo() (puts("mocking function foo"), 2)
int main()
{
printf("%d", foo());
return 0;
}
Here a macro is completely replacing the function with the same name (known as "function mocking"), but keeps the calling API intact, which might be useful for debug and test purposes. There's a place for the comma operator in special-purpose macros like these.