Assuming No Aliasing (/Oa and /Ow)

The /Oa and /Ow options control the assumptions the compiler makes regarding “aliasing” when it performs optimizations. These options can significantly improve the performance of your program, but there are a few situations in which these options are not appropriate.

“Aliasing” occurs when more than one name is used to refer to a single memory location. For example,

char c;

char *cptr;

cptr = &c; /* Take the address of c */

/* c now has two names: c and *cptr */

c = 1; /* Use first name */

*cptr = 2; /* Use second name */

The expression *cptr is an alias for c, because it is another name for that variable. If you refer to the variable by both names, you are using aliasing.

One optimization technique that the compiler performs is to store frequently used variables in registers because accessing a register takes less time than accessing a memory location. If the compiler detects the use of aliasing, it does not place the variable in a register because modifications through an alias could lead to inconsistent values being used for that variable. For example, consider the code fragment above. If the compiler placed c in a register, and if you modified *cptr, the compiler would have inconsistent values for c: one stored in a register, and another stored in a memory location. To avoid this problem, the compiler does not place c in a register.

The compiler can detect simple cases of aliasing like the one described above. However, the compiler cannot identify all possible forms of aliasing. By default, the compiler assumes that your program may be using aliasing that it cannot detect, just to be on the safe side. This means that the compiler assumes that any time you modify a memory location through a pointer, you might also be modifying the value of one of the following:

Any global variable

Any local variable whose address has been taken

The memory location referenced by any other pointer

This assumption limits the amount of optimization the compiler can perform.

The /Oa option tells the compiler that your program does not perform any aliasing (other than the very simple forms that the compiler can detect). This allows the compiler to optimize your code more fully. However, if you specify this option when compiling a program that does perform aliasing, the compiler may produce incorrect code.

Here is an example of a program that performs aliasing that the compiler cannot detect. This program generates incorrect results when compiled under /Oa:

/* OATEST.C

* Fails when compiled with \Oa.

* Passes when compiled with default optimization.

*/

#include <stdio.h>

char buf[10]; /* Global array */

char *return_buf()

{

return buf;

}

void main()

{

char *first,

*second;

first = buf;

second = return_buf();

*first = 2;

*second = 3;

if( *first == 3 )

printf( "Pass\n" );

else

printf( "Fail\n" );

}

In this example, both *first and *second refer to the same memory location. This location is assigned two different values, one through each of its names. If this program is compiled without the /Oa option, the compiler assumes that the reference to *second could refer to the same memory location as does *first, and that *first might be modified by the statement *second = 3. If, however, you do specify the /Oa option, the compiler assumes that *first and *second refer to different memory locations. The compiler then assumes *first retains the value of 2, so it skips the if statement and goes straight to the else clause, which prints “Fail.”

The reason the compiler cannot detect the aliasing in the previous example is that the compiler examines code in only one function at a time when it performs optimization. The compiler doesn't examine the code in return_buf when it is compiling the function main. Consequently, the compiler has no indication that *first and *second refer to the same variable. If you were to explicitly set second to the value of first within the function main, the compiler would detect the aliasing.

The compiler can detect aliasing only when a pointer is explicitly set to the address of a variable. Aside from that case, when /Oa is in effect, the compiler assumes that a variable's value is changed by operations to only that variable itself, and not by operations to any other variable. The compiler does assume that a variable's value may be changed when the variable's address is passed to a function. This also applies if the address is cast to an integer when the function is called. For example,

void func1( char *cptr ); /* Prototype for func1 */

void func2( int i ); /* Prototype for func2 */

void main()

{

char c;

int j;

c = 'a';

func1( &c ); /* Compiler assumes c may be modified */

func2( (int)&c ); /* Compiler assumes c may be modified */

j = (int)&c;

func2( j ); /* Dangerous: compiler doesn't assume */

/* that c may be modified */

}

Note that the compiler does not assume that func2 can change the value of c when a separate integer variable is used to pass c's address.

Summary: The /Oa option means “assume no aliasing.” The /Ow option means “assume no aliasing, except between functions.”

Another option that controls aliasing assumptions is the /Ow option, which tells the compiler that you are performing aliasing between functions. When you specify the /Ow option, the compiler assumes that calling any function may have side effects, instead of only those functions that take pointers as parameters. That is, calling any function may modify the value of any global variable, or any local variable whose address has been taken, or the memory location referenced by any pointer. (This option is useful in Windows programming, because certain Windows functions may cause the contents of handles to be modified.) Consequently, after a function call the compiler reloads the value of variables stored in registers, and the compiler does not perform certain optimizations (such as common subexpression elimination or dead-store elimination) across function calls.

Here is a program that performs aliasing between functions. This program generates incorrect results when compiled with /Oa, but generates correct results when compiled with /Ow:

/* OWTEST.C

* fails when compiled with -Oa

* passes when compiled with -Ow

*/

#include <stdio.h>

#include <malloc.h>

typedef struct list

{

struct list *next,

*back;

int val;

} LIST;

LIST *glob_plist;

LIST *setup( size_t size )

{

glob_plist = malloc( size );

return glob_plist;

}

void ow_func()

{

glob_plist->val = 22;

}

void main()

{

LIST *plist;

plist = setup( sizeof( LIST ) );

plist->val = 23;

ow_func(); /* Function modifies plist->val */

if( plist->val == 22 )

{

printf( "Pass\n" );

exit( 0 );

}

else

{

printf( "Fail\n" );

exit( 1 );

}

}

In this example, both plist->val and glob_plist->val refer to the same memory location, though each appears in a different function. If you compile this program with /Oa, the compiler assumes that the value of plist->val is not changed by the call to ow_func. As a result, the compiler assumes that plist->val still equals 23 in the next statement, so it skips the if statement and goes straight to the else clause, which prints “Fail.” If you compile this program with /Ow, the compiler assumes that ow_func may change the value of plist->val. The compiler performs the if statement, so the program prints “Pass.”

Like the /Oa option, the /Ow option assumes that code within a single function does not perform aliasing; the only difference between the two is in their assumptions regarding function calls. The /Ow option is “weaker” than the /Oa option, because it tells the compiler that you may perform a certain type of aliasing, while the /Oa option tells the compiler that you are performing none. As a result, the /Ow option causes the compiler to perform less optimization than the /Oa option (but more than if neither option were specified).

Take the following steps to see if the /Oa or /Ow options are appropriate for your program:

1.When developing your program, compile your program without the /Oa option. (To make it easier to debug your program with a symbolic debugger, you should compile your program without any optimizations, using /Od.)

2.Once you're satisfied that your program executes correctly, compile the program with /Oa and any other optimizations you want.

3.If the program no longer executes correctly, your program is performing aliasing that the compiler cannot detect. Aliasing bugs most frequently show up as corruption of data, where global or local variables are being assigned seemingly random values. If you can locate the functions in which aliasing is occurring, you can use pragmas to turn off the /Oa option for those particular functions.

4.If you cannot find the functions in which aliasing is occurring, replace the /Oa option with /Ow and recompile your program.

5.If the program still does not execute correctly, and you cannot find the functions in which aliasing is occurring, remove both /Oa and /Ow from your compile options.

If you are looking for instances of aliasing in your program, look for the following situations:

When a variable, particularly a global variable, is referenced through both the variable itself and a pointer to that variable

When multiple pointers are used to reference the same memory location(s)

In the preceding list, the term “reference” means read or write; that is, whether a variable is on the left-hand side of an assignment statement or the right-hand side, you are still referring to it. In addition, any function calls that use a variable as a parameter are references to that variable.

Note that the compiler assumes the value of variables declared as volatile may change at any time. As a result, the compiler does not perform any optimization on such variables.