Blog coding and discussion of coding about JavaScript, PHP, CGI, general web building etc.

Friday, August 26, 2016

Is the strict aliasing rule incorrectly specified?

Is the strict aliasing rule incorrectly specified?


As previously established, a union of the form

union some_union {      type_a member_a;      type_b member_b;      ...  };  

with n members comprises n + 1 objects in overlapping storage: One object for the union itself and one object for each union member. It is clear, that you may freely read and write to any union member in any order, even if reading a union member that was not the last one written to. The strict aliasing rule is never violated, as the lvalue through which you access the storage has the correct effective type.

This is further supported by footnote 95, which explains how type punning is an intended use of unions.

A typical example of the optimizations enabled by the strict aliasing rule is this function:

int strict_aliasing_example(int *i, float *f)  {      *i = 1;      *f = 1.0;      return (*i);  }  

which the compiler may optimize to something like

int strict_aliasing_example(int *i, float *f)  {      *i = 1;      *f = 1.0;      return (1);  }  

because it can safely assume that the write to *f does not affect the value of *i.

However, what happens when we pass two pointers to members of the same union? Consider this example, assuming a typical platform where float is an IEEE 754 single precision floating point number and int is a 32 bit two's complement integer:

int breaking_example(void)  {      union {          int i;          float f;      } fi;        return (strict_aliasing_example(&fi.i, &fi.f));  }  

As previously established, fi.i and fi.f refer to an overlapping memory region. Reading and writing them is unconditionally legal (writing is only legal once the union has been initialized) in any order. In my opinion, the previously discussed optimization performed by all major compilers yields incorrect code as the two pointers of different type legally point to the same location.

I somehow can't believe that my interpretation of the strict aliasing rule is correct. It doesn't seem plausible that the very optimization the strict aliasing was designed for is not possible due to the aforementioned corner case.

Please tell me why I'm wrong.

A related question turned up during research.

Please read all existing answers and their comments before adding your own to make sure that your answer adds a new argument.

Answer by chqrlie for Is the strict aliasing rule incorrectly specified?


Here is note 95 and its context:

A postfix expression followed by the . operator and an identifier designates a member of a structure or union object. The value is that of the named member, (95) and is an lvalue if the first expression is an lvalue. If the first expression has qualified type, the result has the so-qualified version of the type of the designated member.

(95) If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ?type punning?). This might be a trap representation.

Note 95 clearly applies to an access via a union member. Your code does not do that. Two overlapping objects are accessed via pointers to 2 separate types, none of which is a character type, and none of which is a postfix expression pertinent for type punning.

This is not a definitive answer...

Answer by a3f for Is the strict aliasing rule incorrectly specified?


The C11 standard (6.5.2.3.9 EXAMPLE 3) has following example:

The following is not a valid fragment (because the union type is not visible within function f):

 struct t1 { int m; };   struct t2 { int m; };   int f(struct t1 *p1, struct t2 *p2)   {         if (p1->m < 0)                 p2->m = -p2->m;         return p1->m;   }   int g()   {         union {                 struct t1 s1;                 struct t2 s2;         } u;         /* ... */         return f(&u.s1, &u.s2);   }  

But I can't find more clarification on this.

Answer by Purag for Is the strict aliasing rule incorrectly specified?


Under the definition of union members in 6.5.2.3:

3 A postfix expression followed by the . operator and an identifier designates a member of a structure or union object. ...

4 A postfix expression followed by the -> operator and an identifier designates a member of a structure or union object. ...

See also 6.2.3 ?1:

  • the members of structures or unions; each structure or union has a separate name space for its members (disambiguated by the type of the expression used to access the member via the . or -> operator);

It is clear that footnote 95 refers to the access of a union member with the union in scope and using the . or -> operator.

Since assignments and accesses to the bytes comprising the union are not made through union members but through pointers, your program does not invoke the aliasing rules of union members (including those clarified by footnote 95).

Further, normal aliasing rules are violated since the effective type of the object after *f = 1.0 is float, but its stored value is accessed by an lvalue of type int (see 6.5 ?7).

Note: All references cite this C11 standard draft.

Answer by Peter for Is the strict aliasing rule incorrectly specified?


Essentially the strict aliasing rule describes circumstances in which a compiler is permitted to assume (or, conversely, not permitted to assume) that two pointers of different types do not point to the same location in memory.

On that basis, the optimisation you describe in strict_aliasing_example() is permitted because the compiler is allowed to assume f and i point to different addresses.

The breaking_example() causes the two pointers passed to strict_aliasing_example() to point to the same address. This breaks the assumption that strict_aliasing_example() is permitted to make, therefore results in that function exhibiting undefined behaviour.

So the compiler behaviour you describe is valid. It is the fact that breaking_example() causes the pointers passed to strict_aliasing_example() to point to the same address which causes undefined behaviour - in other words, breaking_example() breaks the assumption that the compiler is allowed to make within strict_aliasing_example().

Answer by Eric Hein for Is the strict aliasing rule incorrectly specified?


Let's back away from the standard for a second, and think about what's actually possible for a compiler.

Suppose that strict_aliasing_example() is defined in strict_aliasing_example.c, and breaking_example() is defined in breaking_example.c. Assume both of these files are compiled separately and then linked together, like so:

gcc -c -o strict_aliasing_example.o strict_aliasing_example.c  gcc -c -o breaking_example.o breaking_example.c  gcc -o breaking_example strict_aliasing_example.o breaking_example.o  

Of course we'll have to add a function prototype to breaking_example.c, which looks like this:

int strict_aliasing_example(int *i, float *f);

Now consider that the first two invocations of gcc are completely independent and cannot share information except for the function prototype. It is impossible for the compiler to know that i and j will point to members of the same union when it generates code for strict_aliasing_example(). There's nothing in the linkage or type system to specify that these pointers are somehow special because they came from a union.

This supports the conclusion that other answers have mentioned: from the standard's point of view, accessing a union via . or -> obeys different aliasing rules compared with dereferencing an arbitrary pointer.

Answer by Eric J for Is the strict aliasing rule incorrectly specified?


The strict aliasing rule forbids access to the same object by two pointers that do not have compatible types, unless one is a pointer to a character type:

7?An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)

  • 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.

In your example, *f = 1.0; is modifying fi.i, but the types are not compatible.

I think the mistake is in thinking that a union contains n objects, where n is the number of members. A union contains only one active object at any point during program execution by 6.7.2.1 ?16

The value of at most one of the members can be stored in a union object at any time.

Support for this interpretation that a union does not simultaneously contain all of its member objects can be found in 6.5.2.3:

and if the union object currently contains one of these structures

Finally, an almost identical issue was raised in defect report 236 in 2006.

Example 2

// optimization opportunities if "qi" does not alias "qd"  void f(int *qi, double *qd) {      int i = *qi + 2;      *qd = 3.1;       // hoist this assignment to top of function???      *qd *= i;      return;  }      main() {      union tag {          int mi;          double md;      } u;      u.mi = 7;      f(&u.mi, &u.md);  }  

Committee believes that Example 2 violates the aliasing rules in 6.5 paragraph 7:

"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)."

In order to not violate the rules, function f in example should be written as:

union tag {      int mi;      double md;  } u;    void f(int *qi, double *qd) {      int i = *qi + 2;      u.md = 3.1;   // union type must be used when changing effective type      *qd *= i;      return;  }  

Answer by davmac for Is the strict aliasing rule incorrectly specified?


Starting with your example:

int strict_aliasing_example(int *i, float *f)  {      *i = 1;      *f = 1.0;      return (*i);  }  

Let's first acknowledge that, in the absence of any unions, this would violate the strict aliasing rule if i and f both point to the same object; assuming the object has no effective type, then *i = 1 sets the effective type to int and *f = 1.0 then sets it to float, and the final return (*i) then accesses an object with effective type of float via an lvalue of type int, which is clearly not allowed.

The question is about whether this would still amount to a strict-aliasing violation if both i and f point to members of the same union. On union member access via the "." member access operator, the specification says (6.5.2.3):

A postfix expression followed by the . operator and an identifier designates a member of a structure or union object. The value is that of the named member (95) and is an lvalue if the first expression is an lvalue.

The footnote 95 referred to in above says:

If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ??type punning??). This might be a trap representation.

This is clearly intended to allow type punning via a union, but it should be noted that (1) footnotes are non-normative, that is, they are not supposed to proscribe behaviour, but rather they should clarify the intention of some part of the text in accordance with the rest of the specification, and (2) this allowance for type punning via a union is made only for access via the union member access operator, which means that your example stores via a pointer to a non-existing union member, and thereby commits a strict aliasing violation since it accesses the member that is active using an lvalue of unsuitable type. (I might add that I can not see how the footnote describes behavior that is otherwise inherent in the specification - that is, it seems to break the ISO rule of not proscribing behaviour; nothing else in the specification seems to make any allowance for type punning via a union).

With this in mind, your example clearly violates the strict aliasing rule if f and i point to the same object (or indeed, if they point to overlapping objects).

There is often confusion caused by another part of the specification, however, also in 6.5.2.3:

One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible.

Although this does not apply to your example since there is no common initial sequence, I've seen people read this as being a general rule for governing type punning (at least when a common initial sequence is involved); they believe that it implies that it should be possible to use such type punning using two pointers to different union members whenever the complete union declaration is visible (since words to that effect appear in the paragraph quoted above). However, I would point out that the paragraph above still only applies to union member access via the "." operator. The problem with reconciling this understanding is, in that case, that the complete union declaration must anyway be visible, since otherwise you would not be able to refer to the union members. I think that it is this glitch in the wording, combined with similarly bad wording in Example 3 (The following is not a valid fragment (because the union type is not visible ...), when union visibility is not really the deciding factor), that makes some people construe that the common-initial-sequence exception is intended to apply globally, not just for member access via the "." operator, as an exception to the strict aliasing rule; and, having come to this conclusion, a reader might then (incorrectly) interpret the footnote regarding type punning to apply globally also.

(Incidentally, I am aware of several compilers that do not implement the "global common initial sequence" rule - I assume that their authors do not interpret the specification to imply this rule. I am not aware of any compilers which implement the "global common initial sequence" rule while not also allowing arbitrary type punning, i.e. I know of no compilers which go to a specific effort to implement the rule. On the other hand, it's not clear to me that such a rule would on balance be a bad thing; it's just not what the specification actually mandates, and it's not what compilers implement).

At this point you could well question how reading a non-active union member via the member-access operator doesn't violate strict aliasing, if doing the same via a pointer does so. This is again an area where the specification is somewhat hazy; the key is in deciding which lvalue is responsible for the access. For instance, if a union object u has a member a and I read it via the expression u.a, then we could interpret this as either an access of the member object (a) or as merely an access of the union object (u) which the member value is then extracted from. In the latter case, there is no aliasing violation since it is specifically allowed to access an object (i.e. the active member object) via an lvalue of aggregate type containing a suitable member (6.5?7). Indeed, the definition of the member access operator in 6.5.2.3 does support this interpretation, if somewhat weakly: the value is that of the named member - while it is potentially an lvalue, it is not necessary to access the object referred to by that lvalue in order to obtain the value of the member, and so strict aliasing violation is avoided.

(To me it seems under-specified, generally, just when an object has "its stored value accessed ... by an lvalue expression" as per 6.5?7; we can of course make a reasonable determination for ourselves, but then we must be careful to allow for type-punning via unions as per above, or otherwise be willing to disregard footnote 95. Despite the often unnecessary verbiage, the specification is sometimes lacking in necessary detail).

Arguments about union semantics invariably refer to DR 236 at some point. Indeed, your example code is superficially very similar to the code in that Defect Report. I would note that:

  1. "Committee believes that Example 2 violates the aliasing rules in 6.5 paragraph 7" - this doesn't contradict my reasoning above;
  2. "In order to not violate the rules, function f in example should be written as" - this supports my reasoning above; you must use the union object (and the "." operator) to change the active member type, otherwise you are accessing a non-existent member (since the union can contain only one member at a time);
  3. The example in DR 236 is not about type-punning. It is about whether it is ok to assign to a non-active union member via a pointer to that member. The code in question is subtly different to that in the question here, since it does not attempt to access the "original" union member again after writing to the second member. Thus, despite the structural similarity in the example code, the Defect Report is largely unrelated to your question.
  4. The Committee Response in DR 236 claims that "Both programs invoke undefined behavior". This however is not supported by the discussion, which shows only that Example 2 invokes undefined behaviour. I believe the response is erroneous.

Answer by supercat for Is the strict aliasing rule incorrectly specified?


Prior to the C89 Standard, the vast majority of implementations defined the behavior of write-dereferencing to pointer of a particular type as setting the bits of the underlying storage in the fashion defined for that type, and defined the behavior of read-dereferencing a pointer of a particular type as reading the bits of the underlying storage in the fashion defined for that type. While such abilities would not have been useful on all implementations, there were many implementations where the performance of hot loops could be greatly improved by e.g. using 32-bit loads and stores to operate on groups of four bytes at once. Further, on many such implementations, supporting such behaviors didn't cost anything.

The authors of the C89 Standard state that one of their objectives was to avoid irreparably breaking existing code, and there are two fundamental ways the rules could have been interpreted consistent with that:

  1. The C89 rules could have been intended to be applicable only in the cases similar to the one given in the rationale (accessing an object with declared type both directly via that type and indirectly via pointer), and where compilers would not have reason to expect aliasing. Keeping track for each variable whether it is currently cached in a register is pretty simple, and being able to keep such variables in registers while accessing pointers of other types is a simple and useful optimization and would not preclude support for code which uses the more common aliasing patterns (having a compiler interpret a float* to int* cast as necessitating a flush of any register-cached float values is simple and straightforward; such casts are rare enough that such an approach would be unlikely to adversely affect performance).

  2. Given that the Standard is generally agnostic with regard to what makes a good-quality implementation for a given platform, the rules could be interpreted as allowing implementations to break code which uses aliasing in ways that would be both useful and obvious, without suggesting that good quality implementations shouldn't try to avoid doing so.

If the Standard defines a practical way of allowing in-place aliasing which is not in any way significantly inferior to other approaches, then approaches other than the defined way might reasonably be regarded as deprecated. If no Standard-defined means exists, then quality implementations for platforms where aliasing is necessary to achieve good performance should endeavor to efficiently support common aliasing platforms on those platforms whether or not the Standard requires them to do so.

Unfortunately, the lack of clarity as to what the Standard requires has resulted in a situation where some people regard as deprecated constructs for which no replacements exist. Having the existence of a complete union type definition involving two primitive types be interpreted as an indication that any access via pointer of one type should be regarded as a likely access to the other would make it possible to adjust programs which rely upon in-place aliasing to do so without Undefined Behavior--something which is not achievable any other practical way given the present Standard. Unfortunately, such an interpretation would also limit many optimizations in the 99% of cases where they would be harmless, thus making it impossible for compilers which interpret the Standard that way to run existing code as efficiently as would otherwise be possible.

As to whether the rule is correctly specified, that would depend upon what it is supposed to mean. Multiple reasonable interpretations are possible, but combining them yields some rather unreasonable results.

PS--the only interpretation of the rules regarding pointer-comparisons and memcpy that would make sense without giving the term "object" a meaning different from its meaning in the aliasing rules would suggest that no allocated region can be used to hold more than a single kind of object. While some kinds of code might be able to abide such a restriction, it would make it impossible for programs to use their own memory management logic to recycle storage without excessive numbers of malloc/free calls. The authors of the Standard may have intended to say that implementations are not required to let programmers create a large region and partition it into smaller mixed-type chunks themselves, but that doesn't mean that they intended general-purpose implementations would fail to do so.


Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72

0 comments:

Post a Comment

Popular Posts

Powered by Blogger.