For the experts: It surprised me just now to learn that 'gcc -ansi'
actually permits
struct foo {
const int bar;
};
This fragment is strictly conforming. The "bar" member of a "struct
foo" is const-qualified and thus read-only. (Note that this is
different from the original example, which -- after spelling
corrections -- declared one member as "pointer to const struct
....", i.e., a read/write pointer to a read-only object.)
Does this mean what I think it means --- 'bar' can only be given a
value during initialization --- or is this just a quirk of GCC or
of the Standard?
The member named "bar" can indeed only be given a value via
initialization (in strictly conforming code anyway -- in practice,
"going behind the compiler's back" to write on the member tends
to "work", for some definition of "work" anyway).
Consider a larger structure:
struct S {
int a;
const int b;
int c;
};
An object of type "struct S" has three members named a, b, and c,
with a and c being read/write and b being read-only -- but by the
other rules about structures, the address of b has to be "between"
that of a and c. On conventional machines with page-granular memory
protection (e.g., 4096 or more bytes at a time are either read/write
or read-only), a "struct S" object will have to occupy wholly-read/write
memory. Thus, "sneaky" code such as:
struct S s_obj = { 1, 2, 3 };
...
int *p = &s_obj.a;
p[1] = 99; /* UNDEFINED, but tends to compile and run anyway */
will tend to overwrite s_obj.b (note that I have assumed there
is no padding here!). The compiler is of course allowed to *assume*
that s_obj.b has not changed (in this case) so whether you can
*see* the change tends to be optimization-level-dependent, for
instance. (In other words, "don't do this".

)
On another note, many embedded-systems C programmers like to
use volatile-qualified types for hardware register layout
data structures:
struct fooreg {
volatile int csr;
volatile int dar;
/* etc */
};
This is also valid, Standard C (although the precise meaning of
"volatile" is up to the compiler anyway). I do not share their
enthusaism: I prefer to make the structure contain ordinary,
unqualified types, and then have the pointer that points to that
structure carry the qualifier:
struct fooreg {
int csr;
int dar;
/* etc */
};
...
volatile struct fooreg *reg = (volatile struct fooreg *)addr;
I have two reasons for this, one of which I think is easier to
argue for: the unqualified "struct" version allows you to copy the
hardware registers to software data structures for debugging, and
yet get the software-structure access optimized without having the
optimization interfere with real hardware access. (As it turns
out, to make device drivers portable across widely different
architectures, it is often a good idea not to use such structures
in the first place -- at least not directly, anyway.)