Behavior, upon use of a nonportable program construct, for which the C
Standard imposes no requirements is by definition 3.4.3 so-called
"undefined behavior".
(That doesn't imply that an existing implementation would behave
unpredictable. If you don't care for portability and have found by
inspection of code, data, machine architecture, whatever, that your
program behaves predictable, then don't worry.)
I would kindly ask you to read the rest of this thread, and realize
that I am quite well versed on the issues at hand, and I understand
that the intent of the C standards committee was to make the following
program have undefined behavior.
#include <stdlib.h>
typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;
int main(void)
{ T1 *p = malloc(sizeof *p);
p->x = 1;
p->y = 2;
return ((T2*)p)->y;
}
I don't mean to dispute that's how people understand the standard. I
don't plan on writing any code any time soon that violates the well
understood intent of the standard.
However, that's not the rules as written. What I do want to discuss is
if there's any sensible reading of the standard as written which can
give the desired conclusion, while preserving idiomatic usages of C,
such as casting the return of malloc to a struct type pointer, and
assigning to members of that struct.
I note how you did not answer any of my questions, and instead read
the standard line given to those new to the issues. I understand that
this is a generally acceptable method of imparting information, but it
does not apply in this case.
Again: Which of the following programs have UB as written, 1, 2, both,
neither? Why? Please quote exact parts of the standard (C or C++) with
thorough reasoning. Which of the following would have UB if the return
was replaced with "return ((T2*)p)->y;", 1, 2, both, neither? Why?
Please quote exact parts of the standard (C or C++) with thorough
reasoning.
//program 1
#include <stddef.h>
#include <stdlib.h>
typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;
int main(void)
{ T1 *p = (T1*) malloc(sizeof *p);
p->x = 1;
p->y = 2;
return p->y;
}
//program 2
#include <stddef.h>
#include <stdlib.h>
typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;
int main()
{
void* p = malloc(sizeof(T1));
* (int*) (((char*)p) + offsetof(T1, x)) = 1;
* (int*) (((char*)p) + offsetof(T1, y)) = 2;
return ((T1*)p)->y;
}
PS: I hope the intended answer is both do not have UB as written. I
understand the answer that the first would have UB if the return was
changed to "return ((T2*)p)->y;", but even I cannot grasp at the
straws to come to the conclusion that program 2 would have UB if the
return was changed to "return ((T2*)p)->y;".
Note that the above is all assuming a particular implementation where
sizeof(T1) == sizeof(T1), and offsetof(T1, y) == offsetof(T2, y). I
know it's definitely not portable, but I don't see any /rules as
written/ which demand UB on all platforms. (offsetof(T1, x) == 0 and
offsetof(T2, x) == 0 by an already existing guarantee in the C and C++
standards.)
offsetof is just a macro which evaluates to an integer. So what that I
passed T1 to it. It shouldn't matter. I should be able to hardcore 4
in place of offsetof(T1, y) and offsetof(T2, y) and expect it to work
on some platforms, like the common x86 win32. I see nothing in program
2 that says we have an object of type or effective type T1 nor T2. I
see only writes through int lvalues. Moreover, I see little to no
difference between program 1 and program 2 in this regard - I see
little reason to talk about an object with type or effective type T1
nor T2 in program 1.
This is especially true in light of the rules for volatile, and the
rules of POSIX, win32 (maybe?) and C++0x threading, which heavily
interact with the definition of "access". What does "access" mean? I
would argue that if that word is to have any meaning, it means exactly
a read, a write, or both. What does it mean to access an object of
struct type with a member expression, ex: "x.y"? It definitely doesn't
read the full struct x, nor write the full struct x. Hell, it doesn't
even imply a read or a write, ex: "int * a = & x.y;". From our well
understood knowledge of threading, that is neither a read nor a write
of "x" nor "x.y". So, what can the strict aliasing rules say about
this? In the above programs, there is no single expression which we
can say "accesses" an object through a T1 nor T2 lvalue, unless we
want to start using two different contradictory definitions of the
word "access". The same conclusion can be reached through a discussion
of the observable behavior requirements of volatile objects.
Let me again emphasis that I don't plan to write production code like
this ever, but these simple examples elucidate the actual scope and
effect of the standards, such as the proper and correct way to write a
pooling memory allocator on top of malloc or the new operator.
Specifically, at least in the C++ case, we need to know when the
lifetimes of objects begin and end, and what it means to access an
object through an incorrectly typed lvalue.
Again, finally, as far as I can see, the only way to make program 1
have UB with the T2 cast return is to invent some rules which
explicitly mention data dependency analysis in the effective type
rules of C, in the object lifetime rules of C++ (or just copy the
effective type rules of C for POD types into C++), and in the allowed
lvalue access rules, aka the strict aliasing rules of C and C++. When
you consider the implications raised with volatile and threading which
heavily interact with the definition of "access", this seems like the
only way out.
Or, if you can prove me wrong, and help me understand an error of
mine, please do so.