ANSI aliasing rules

You must indicate whether your source code conforms to the ANSI aliasing rules when you use the IPA or the OPT(2) (or above) z/OS® XL C/C++ compiler options. If the code does not conform to the rules, it must be compiled with NOANSIALIAS. Incorrect use of these options might generate bad code.

Note: The compiler expects that the source code conforms to the ANSI aliasing rules when the ANSIALIAS option is used. This option is on by default.
The ANSI aliasing rules are part of the ISO C Standard, and state that a pointer can be dereferenced only to an object of the same type or compatible type. Because the z/OS XL C/C++ compiler follows these rules during optimization, the developer must create code that conforms to the rules.
Note: The common coding practice of casting a pointer to an incompatible type and then dereferencing it violates ANSI aliasing rules.
When you are using ANSI aliasing, you can cast an integer pointer only to the types described in Table 1.
Table 1. Examples of acceptable alias types
Type Reason for acceptance
int This is the declared type of the object.
const int or volatile int These types are the qualified version of the declared type of the object.
unsigned int This is a signed or unsigned type corresponding to the declared type of the object.
const unsigned int or volatile unsigned int These types are the signed or unsigned types corresponding to a qualified version of the declared type of the object.
struct foo {
unsigned int bar;
};
This is an aggregate or union type that includes one of the aforementioned types among its members. This can include, recursively, a member of a subaggregator-contained union.
char or unsigned char The char pointers are an exception to the rules, as any pointer can be used to point to a char variable.

Conversely, your code breaks the aliasing rules if it casts a float to an int and then assigns it to the int pointer.

For more information, see type-based aliasing in z/OS XL C/C++ Language Reference and ANSIALIAS in z/OS XL C/C++ User's Guide.

You can cast and mix data types as long as you are careful how you intermix values and their pointers in your code. The compiler follows the ANSI aliasing rules to determine:

When you use the NOANSIALIAS option, the compiler generates code to accommodate worst-case assumptions (for example, that any variable could have been updated by the store through a pointer). This means that every variable (local and global) must be stored in memory to ensure that any value can be read through a pointer. This severely limits the potential for optimization.

int ei1;
float ef1;
int *eip1;
float *efp1;

float exmp1 ()
{
   ef1 = 3.0;
   ei1=5;
   *efp1 = ef1;
   *eip1 = ei1;
   return *efp1;
}

Table 2 shows the difference between code generated with, and without, ANSI aliasing.

Table 2. Comparison of code generated with the ANSIALIAS and NOANSIALIAS options
ANSIALIAS RENT and OPT(2) NOANSIALIAS RENT and OPT(2)
* {
*  ef1 = 3.0;
     L     r4,=A(@CONSTANT_AREA)(,r3,94)
     L     r2,=Q(EF1)(,r3,98)
     LD    f0,+CONSTANT_AREA(,r4,0)
     L     r14,_CEECAA_(,r12,500)
     L     r15,=Q(EFP1)(,r3,102)
     L     r4,=Q(EIP1)(,r3,106)
     L     r1,#retvalptr_1(,r1,0)
     STE   f0,ef1(r2,r14,0)
     L     r15,efp1(r15,r14,0)
* {
*  ef1 = 3.0;
     L     r2,=A(@CONSTANT_AREA)(,r3,110)
     L     r14,_CEECAA_(,r12,500)
     L     r4,=Q(EF1)(,r3,114)
     L     r15,=Q(EFP1)(,r3,118)
     LD    f0,+CONSTANT_AREA(,r2,0)
*  ei1=5;
     L     r2,=Q(EI1)(,r3,110)
     LA    r0,
     L     r4,eip1(r4,r14,0)
*  ei1=5;
     L     r2,=Q(EI1)(,r3,122)
     STE   f0,ef1(r4,r14,0)
*  *efp1 = ef1;
     STE   f0,(*)float(,r15,0)
     ST    r0,ei1(r2,r14,0)
*  *efp1 = ef1;
     L     r4,efp1(r15,r14,0)
*  *eip1 = ei1;
     ST    r0,(*)int(,r4,0)
*  *eip1 = ei1;
     L     r5,=Q(EIP1)(,r3,126)
     LA    r0,5
     ST    r0,ei1(r2,r14,0)
     STE   f0,(*)float(,r4,0)
     L     r4,eip1(r5,r14,0)
     L     r0,ei1(r2,r14,0)
*  return *efp1;
     STD   f0,#retval_1(,r1,0)
*  }
*  return *efp1;
     L     r1,#retvalptr_1(,r1,0)
     ST    r0,(*)int(,r4,0)
     L     r14,efp1(r15,r14,0)
     SDR   f0,f0
     LE    f0,(*)float(,r14,0)
     STD   f0,#retval_1(,r1,0)
*  }
  • In the ANSIALIAS case:
    • f0, loaded with 3.0, is used whenever referring to ef1 or efp1
    • r0 is loaded with the value of 5, which is used for ei and eip
  • In the NOANSIALIAS case, the loads and stores are always done. This removes opportunities for optimizations. For example, if a + b + c were used instead of 3.0 and ef1, saving through the pointer might have updated a, b, or c, and therefore you cannot common at all, and many more reloads.
  • ANSIALIAS would not help if all the floats were also integers
  • There is a group of problems that occurs when the ANSIALIAS option is used to compile code that does not conform to ANSI-aliasing rules (for example, when it casts a variable to a non-ANSI-aliasing type and then assigns the address of the value to a pointer for later use). If the ANSIALIAS option is in effect (it is the default) when a value is used through a pointer, the compiler might not reload the pointer value when the original value is updated, and the value might be stale when it is read.