On Tue, 14 Dec 2004 23:41:50 -0600, Jack Klein wrote:
....
Let's look at the terms "declared type" and "effective type".
For the rest of the discussion, assume that 'ca' defined below meets
the implementation's alignment requirements for int. And let's assume
sizeof(int) is 4.
char ca [sizeof(int)] = { 0 };
int *ip = (int *)ca;
The declared type of 'ca' is array of four chars. So far, so good.
The declared type of 'ip' is pointer to int. Regardless of what
address 'ip' contains, the effective type of '*ip' is int.
Nope. Objects have an effective type, lvalues have a type but it is not
the "effective type" as defined by C99. C99 6.5p6 says
"The /effective type/ of an object for an access to its stored value is
the declared type of the object, if any."
So the effective type of ca is array of sizeof(int) char. Always. However
it is accessed. The concept of effective type is used in aliasing rules.
C90 didn't define an effective type, but it has a problem because malloc'd
(allocated) objects don't have a declared type so the aliasing rules
in C90 don't work properly for malloc'd objects. "Effective type" was
invented to rectify this problem. With a declared effective type mimics
the C90 semantics i.e. is the same as the declared type, with malloc'd
objects it depends on what was last written to the object.
It has to be possible for the effective type of an object to be different
to the type of an lvalue used to access that object or there would be no
point in having the term. Specifically rules in C99 6.5p7 such as the
following depend on this difference:
"An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:
- a type compatible with the effective type of the object
....
- a character type."
Accessing
the value of '*ip' through its effective type (int) can cause
undefined behavior for any number of reasons, alignment, invalid
pointer, trap representation for int, and so on. But not at all how
the block of memory into which 'ip' points was defined.
Absolutely because of this, which is what the aliasing rules in 6.5p7 are
all about. Your example above violates 6.5p7 so it invokes undefined
behaviour irrespective of any of the other issues you mention. This is
also true in C90 which makes the same requirements without using the
concept of effective type (but is broken for malloc'd objects).
Remember, memory is memory, or storage is storage, or at the lowest
level in C, bytes is bytes.
Not according to the C standard. Objects also have type associated with
them. That incidentally includes qualifiers. If you try to access a
volatile defined object using a non-volatile lvalue you get undefined
behaviour.
The declared type of a region of storage
(that is, the type in the expression that defined and caused storage
to be allocated) does not change the underlying nature of the bytes.
But type affects what code is generated to access those bytes.
There are no bytes that can only hold chars, or ints, or doubles, or
pointers.
Yes, you could use memcpy() etc. to put anything you like in them. What
you can't do is use any lvalue you like to put the corresponding type of
data in a declared object, even if size, alignment const,
volatile considerations are met. Take an example
long num = 1;
short *p = (short *)&l;
*p = 2;
This invokes undefined behaviour. The type of the lvalue *p is short but
the object it is accessing has an effective type of long. This is not a
combination permitted by C99 6.5p7. It is also undefind by C90 6.3. If
you're unsure the C90 text is a good place to start.
Why does the standard make this undefined? For efficiency reasons relating
to aliasing. Compiler optimisers need to track when an object can be
accessed in order, for example, to be able to hold its value in a
register. The compiler knows that num can't be accessed through a
short lvalue without invoking undefined behaviour and so can safely ignore
the *p = 2 side-effect for the purposes of optimising access to num. It
could continue to use the value of num it happened to have held in a
register, and maybe write it back to the memory object later clobbering
what was written by *p = 2.
Lawrence