I didn't know the knowledge of these details about structs before. and
locating of struct members by offset.
Fine, but don't forget that the elements have names for
a reason: to make it easy to refer to them. Refer to struct
elements by their names whenever you know them -- that is,
whenever you know the type of the struct. It is possible to
get at a struct's elements using offsets and pointers and
casts, but this is best done only when you *don't* know the
struct type and hence the element names. It works, but it
has various drawbacks:
- As you have seen, it is easy to make misteaks that the
compiler will not detect.
- You lose the convenience of having the compiler keep
track of the element types. Refer to `s1.i7' and the
compiler knows it's an int, but use casts and offsets
and the compiler just has to trust your casting.
- It makes maintenance and debugging harder. If you found
that `s1.i7' is being set to a garbage value, you might
search your source for references to `i7' and put assert()
macros at each site. But you won't find cast-and-offset
references this way.
- It may make your code slower. Derive an `int*' from a
bunch of other data and store through it, and the compiler
may need to assume that every `int' variable it knows of is
a potential target. It may move register-resident values
back to memory, do your store, and then reload everything --
whereas if you'd just said `s1.i7 = 42' it would have known
that `i' and `j' and `k' were unaffected and could remain
safely and conveniently in their CPU registers.
In short, it's a technique that's available to you when it's
needed, but it's a technique of last resort. On a desert island
you might have to perform that appendectomy with two teaspoons
and an eggbeater, but it's not the method of choice.
Some people said the offsetof macro in this way
#define offsetof(type, memb) ((size_t) &((type *) 0)-> memb)
dereferences NULL /* 0 */ pointer and it's undefined behavior. I'm
even more anxious on this. And it's not put in this form
#define offsetof(type, memb) \
((size_t) ((char *) &((type *) 0)-> memb - (char *) &((type *)
0)))
Could you please talk about this more?
Keith Thompson has explained this elsethread. Briefly, it's
perfectly all right for the implementation's own code to rely on
things the Standard does not guarantee, because the implementation
can rely on its own behavior.