[from article <46************ **********@news .orange.fr>: gcc accepts:]
>>int main(void)
{
long long a;
char *n;
(char *)a = n;
}
In article <f5***********@ pc-news.cogsci.ed. ac.uk>
Richard Tobin <ri*****@cogsci .ed.ac.ukwrote:
>Gcc has, I think, always warned about this if you give enough flags,
Indeed.
>and seems to reject it by default in recent versions.
Yes. This illustrates one of the dangers of relying on compiler
extensions: the compiler-writer may eventually realize the folly
of that particular extension, and remove it. (The extension, not
the folly. :-) )
GCC's old definition of (cast)obj = expr was:
((cast)(obj = (T)(expr)))
where T is the type of the object "obj", -- so the code above, with
"a" being "long long", really "meant":
(char *)(a = (long long)n);
Many programmers seemed to have expected it to mean:
*(char *)&a = n;
which, on a typical 32-bit-pointer, 64-bit-"long long" machine,
sets just half of the object "a", leaving the other half unmodified.
GCC's old interpretation sets all 64 bits. The overly-clever
programmer, believing it set only 32 bits, set the other 32 bits
to something useful first, and then was surprised when those bits
were clobbered.
(If you use Standard C to write "what you mean", you will not be
surprised by whatever bizarre interpretation some compiler-writer
uses for his or her particular extension.)
>I've always thought its use in implementing a "program counter"
variable was reasonable, where a generic pointer is cast to various
types and incremented by the same amount, e.g.
op = *((operator *)pc)++;
switch(op)
{
...
case ADD:
arg1 = *((int *)pc)++;
arg2 = *((int *)pc)++;
whatever = arg1 + arg2;
...
But those writing this style of code can easily enough work around it.
Yes -- and, assuming "void *pc" for the above and that "operator" is
a typedef-name for a type smaller than plain "int", the above tends
to malfunction on various machines. So it might not be the best
way to write the code, even with the more verbose workaround:
op = *(operator *)pc, pc = (unsigned char *)pc + sizeof(operator );
switch (op)
{
...
case ADD:
arg1 = *(int *)pc, pc = (unsigned char *)pc + sizeof(int);
arg2 = *(int *)pc, pc = (unsigned char *)pc + sizeof(int);
whatever = arg1 + arg2;
where the comma-expression versions above might be hidden behind
a macro and then modified a bit:
#define FETCH(ty, pc) \
(pc = (unsigned char *)(pc) + sizeof(ty), ((ty *)pc)[-1])
Note: giving "pc" type "unsigned char *" simplifies this even
further:
#define FETCH(ty, pc) (((ty *)(pc += sizeof(ty)))[-1])
switch (FETCH(operator , pc)) {
...
case ADD:
arg1 = FETCH(int, pc);
whatever = arg1 + FETCH(int, pc);
...
The alignment trap remains, however: for this to work in general,
the pointer must remain aligned for each type "ty". The simplest
way to guarantee such alignment is to use just a single type, in
this case, "int". If "pc" is always going to refer to "int"s, we
can remove the FETCH macro's "ty" argument, change "pc" to "int
*", and simply use *pc++ -- and thus remove all need for the FETCH
macro itself.
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it
http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.