Hacker News new | past | comments | ask | show | jobs | submit login

First, this is not undefined, so you are going to need to find another example :)

6.7.2.1/13

"Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase in the order in which they are declared. A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa. There may be unnamed padding within a structure object, but not at its beginning."

(This is also known as the first member rule)

6.5/7 then says: " 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 qualified version of a type compatible with the effective type of the object, a type that is the signed or unsigned type corresponding to the effective type of the object, a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object, an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or a character type."

These are both types compatible with the effective type of the object.

IE your first pointer is really also an int*, as is your second pointer. Your dereference attempt, which is an access to an int, through something with an effective type of pointer to an int, is a valid thing to do.

(Note: I implemented a large part of of GCC's current pointer aliasing analysis and rules, though the underlying TBAA set construction predates me by quite a while :P)

Now, if you added a field and tried this with the second member, of a different type than the first, you'd maybe get a different answer.

However, pay close attention to:

an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

GCC takes this very seriously. So if you construct types that contain both compatible types, and use that, gcc will consider it okay, no matter how weird what you are doing is.

(Note also i do not claim either set of rules produces sane results, and in fact, can easily construct examples where the standard says nonsensical things, or rules seem to change depending on whether you have one source file or two or ...)

In general, however, your complaint is basically: "C is not a pure structural type system, it's a name based one for the most part". This is true, and it is meant to be that way.

There are other programming languages with structural type systems (OCaml is a good example).




> First, this is not undefined, so you are going to need to find another example :)

This example will do just fine, in fact. It is arguably not undefined, as you say. It is also silently miscompiled by both Clang and GCC.

https://godbolt.org/g/7Gb9S3

And there you have one of the problems with type-based aliasing optimizations: the people who write compilers have not read the standard and just make it up as they are going along. They have been acting like the six-year-old who pretends to be reading rules from the back of the box in a game of Monopoly.


You should file a bug report. This should in fact, work, and did, in fact, work, when i stopped working on it.

If you split the type definitions across different translation units, it will stop working (because it can't know they are compatible), but this is one of the weird edge cases.

"And there you have one of the problems with type-based aliasing optimizations: the people who write compilers have not read the standard and just make it up as they are going along. They have been acting like the six-year-old who pretends to be reading rules from the back of the box in a game of Monopoly. " This is, well, bullshit. The situations get incredibly complex very quickly, and it's completely unclear in a lot of cases what the standard meant to happen.

You should not assume bad faith without a good reason. The vast majority of people implementing this stuff either are committee members, or work closely with them, so saying "they haven't read the standard" just makes you look petty, because, in a lot of cases, they helped write the standard.

At the same time, while implementing it, i think we filed something like 15 DR's against the standard, some of which are still unresolved because the committee didn't know what to do and punted on it due to lack of consensus. So if you want to say who makes it up as they go along, i think you may be pointing fingers in the wrong direction:

Take, for example, DR 236 which was filed in 2000, and was not resolved until 2006, and they basically punted (note how the answer does not answer the questions): http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_236.htm

(note also that most DR's from the same time period were resolved in 1 year or less)

GCC had at least 3 committee members who were consulted on most of this stuff, and agreed with the current set of interpretations.

In short, if you think it's so easy to do, feel free to fix it. I think you will find yourself quickly in a world of trying to figure out what anyone meant to happen.

Contrary to what you seem to think, there are rarely objectively right answers, just interpretations that you can get consensus or no consensus for.

(Not sure what one would expect from a programming language standard built by something akin to the UN)


>You should file a bug report. This should in fact, work, and did, in fact, work, when i stopped working on it.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=14319 (12 years ago, status: suspended)


Err, this is a completely different case.

This is the union case you can't make work right all the time, and is actually two DR's, as the bug says. This is a case where the standard is completely broken (and will likely never be fixed)

The viewpoint of committee members i spoke with is that explicitly visible union accesses should be required to get the right answer here, because anything else is insanity.

I could place your two memory accesses in a union, in a different translation unit, and pass it to this function, and you would never have any reason to know they alias, because it looks like i handed you two struct pointers. IE imagine the function and main were in two different files, one of which had the union, and other other did not.

So either the compiler assumes that literally all memory, everywhere, aliases, forever (despite any other rules that the standard says exist, which directly contradict this), or we require programmers to make explicit union accesses (or your answers change depending on how much code the compiler can see, and whether it does whole program optimization or not or ....)

Like I said, this is a case where the standard is truly broken, and the best you can do is try to build consensus about what to do.

Note also the bug was suspended to figure out what the language was supposed to mean here. Nobody said it was not a bug, they said "no idea what supposed to happen here". The committee punted (contrary to the last comment, they didn't really resolve the question), so it's never been worked on.


> In short, if you think it's so easy to do, feel free to fix it.

Naturally. The GCC developers have not documented their choices; what GCC actually does is fuzzy enough that a GCC developer who actually worked on the implementation of type-based aliasing optimizations gets it wrong. In these conditions, I am going to fix the fact that they didn't document their intentions (let alone doing what the standard says, since we have established that the standard committee, composed of compiler authors, is not helpful—I don't know what you think this is extenuating circumstances; to me, it makes things worse) by reverse-engineering GCC's assumptions, and extrapolate to what GCC might do in the near future, and help programmers determine whether their C programs might betray them now or soon.

In fact, this is exactly what we have been doing. Drop me an e-mail if you wish to help beta-test it: cuoq at-sign trust-in-soft.com


I noticed that ICC 13 on Godbolt compiled it "correctly", assuming correctly means as written. I wondered if this was just because it's an older version that wasn't yet optimized, so I tried with the current ICC 17 Beta. But with all the optimization options I tried, it stuck to its guns and compiled it as written:

   0:	c7 07 03 00 00 00    	movl   $0x3,(%rdi)
   6:	c7 05 00 00 00 00 04 	movl   $0x4,0x0(%rip)
   d:	00 00 00
  10:	8b 07                	mov    (%rdi),%eax
  12:	c3                   	retq
From what I can tell testing with http://webcompiler.cloudapp.net, MSVC seems to also produce the "correct" result at all optimization settings, while GCC and Clang do so only with -O1 or lower for all versions that I tried.


"arguably not X" is the same as "arguably X".


I find your conclusion very surprising, foo and bar are certainly not compatible. psbar->x is equivalent to (*psbar).x and dereferencing psbar violates the effective type rule, thus UB. The fact that the type of the 'x' fields are the same is immaterial as you are already in UB territory.

I believe that GCC uses a structural type system for its TBAA analysis (and generally much looser rules), but from the standard point of view it seems that this is a pretty clear violation of aliasing rules.


> "an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or"

I don't think you're properly understanding this wording (which was there since 1989 ANSI C, IIRC).

Firstly, we can interpret it very poorly, so that this appears defined:

  { double x = 42.0;  /* declared type is double */
    struct foo { int y; double z; } *p = (struct foo *) &x;

    return p->y; }
Hey, x is being accessed via an aggregate type, and that type includes one of the aforementioned types (the declared type of x) as one of its members, namely member z!

Here is the real intent of the wording. Why it is necessary is simply because members are implicitly accessed when an aggregate is accessed as a whole. That is all! If we assign one "struct foo" to another, for instance, and that "struct foo" has a member "int x", that member is accessed and it is being accessed through an lvalue of type "struct foo". Without the above wording, that use would appear not to be conforming, because a "struct foo" type is being used to access an "int".

> In general, however, your complaint is basically: "C is not a pure structural type system, it's a name based one for the most part".

No, that isn't my complaint at all because, note that I still want a diagnostic if we remove the cast:

   // diagnosable constraint violation:
   struct foo *p = &struct_bar_instance;
I just want the access to be defined to members that happen to be of the same type and at the same offsets in these structures, without going all the way to assert that they are compatible types for the purposes of assignment/passing and pointer conversions.

The access should be defined even if the offset is the same in different ways, on those platforms where it happens to work out:

   struct foo { char c; int x; };
   struct bar { short s; int x; };
If c is one byte, s is two, and x is aligned to the next multiple of 4, so its offset is 4 in both structures, accessing x should work through either struct foo or struct bar on this implementation. I.e. it is undefined behavior if the offset isn't the same, otherwise requirements apply. Just like 1 << 30 is defined, and its value is the same, on all platforms where int is at least 32 bits wide. But it is not defined if int is, say, 16 bits (out of range shift).


"No, that isn't my complaint at all because, note that I still want a diagnostic if we remove the cast."

Okay, then you want something super strange and in-between :)

"I just want the access to be defined to members that happen to be of the same type and at the same offsets in these structures, without going all the way to assert that they are compatible types for the purposes of assignment/passing and pointer conversions. "

Okay ...

"The access should be defined even if the offset is the same in different ways, on those platforms where it happens to work out: "

This qualifies in my book as super-strange. I'm pretty sure you will find no one who views the standard this way, as it contradicts the explicit wording pretty directly :P.

So if you want that, you'd probably need a new language.

Note also that it makes actual pointer analysis (IE andersens, etc) impossible without complete and total target info.

You could never have a target independent thing do pointer analysis in this world.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: