Question regarding volatile parameters

If I have a structure such as this:

typedef struct a_Struct { int x; int y; } aStruct;

Then pass a variable of type pointer to aStruct into a function such as:

void aFunction( aStruct* param ) { param->x = 3; if( param->x == 3 ) { param->y ++; } }

What is the optimizer permitted to do?

Param->x is set to 3 then immediately tested to see if it is three. As param is not volatile it might be reasonable for an optimizer to miss this set and test out as it could always expect the if() to evaluate to true. If it does this then the function might then just increment the y member and leave the x member unchanged.

But param is passed in by reference so the optimizer has not way of knowing how the x member is used outside of the function, so when the function exits I think it should be expected that x should be set to 3. Is this assumption safe?

If when I call aFunction I cast the parameter to a pointer to volatile I get a compiler warning that the volatile has no meaning on the cast (which is correct).

I could define the function as void aFunction( volatile aStruct* param ) I think this would guarantee that x and y were set.

I would appreciate your comments.

Reply to
Spod
Loading thread data ...

No, the compiler may omit the _test_ for 3, but it must always perform the assignment to member 'x'. In other words, the function body may be reduced by a compiler to the following:

param->x = 3; param->y++;

The order in which the assigments occur is not guarantueed, but they must both be done before the function is left. If you declare the struct as volatile, the compiler must also perform the test for 3, and maintain the relative order.

Reply to
Arlet

In what world is that reasonable? It could leave out the check but not the assignment unless it knew through cross functional analysis that all calls to aFunction passed a pointer to a structure whose x member was never used. If the compiler is going to that much trouble it'll probably complain that the x member is never used.

Robert

Reply to
Robert Adsett

X will always be set without having to declare it volatile. You are right that the if() could be optimized away. The assignment can not be optimised away because it is not to an auto variable, it has scope outside of aFunction(). You only need volatile if x is a hardware register or if another thread has access to it.

Peter

Reply to
Peter

Thanks for your answers. You are confirming what I thought. I am trying to understand the behavior or GCC with -O2 optimization.

An extreme case then - say an interrupt has access to aStruct so it could be modified within aFunction and within an interrupt. In this case I think I am still safe with the following code:

void aFunction( aStruct* param ) { disable_interrupts(); param->x = 3; if( param->x == 3 ) { param->y ++; } enable_interrupts(); }

Again this should be optimised to:

void aFunction( aStruct* param ) { disable_interrupts(); param->x = 3; param->y ++; enable_interrupts(); }

which will behave as expected even though param is not volatile and GCC has no idea it could be accessed from an interrupt as I am forcing exclusive access.

Thanks again.

Reply to
Spod

Un bel giorno Spod digitò:

You can use a pointer:

void aFunction( aStruct* param ) { volatile int* vx = &param->x;

*vx = 3; if( *vx == 3 ) { param->y ++; } }

Or you can use 'barriers', even though I'm not sure if it's just a feature/macro of the Linux kernel or it is supported natively by gcc:

void aFunction( aStruct* param ) { param->x = 3; barrier(); if( param->x == 3 ) { param->y ++; } }

--
emboliaschizoide.splinder.com
Reply to
dalai lamah

Anything that leaves the struct with x==3 and y one larger than it was before, by the time aFunction() has returned to the caller.

Of course not. It still has to assign x = 3.

That doesn't matter --- because the code is now inside the function, not outside.

Yes, to the point you trust your compiler vendor to not make a complete mess of their job.

--
Hans-Bernhard Broeker (broeker@physik.rwth-aachen.de)
Even if all the snow were burnt, ashes would remain.
Reply to
Hans-Bernhard Broeker

You have to declare a new volatile point to cast to volatile:

#define ToVInt(x) (*((volatile int *) &(x)))

void aFunction( aStruct* param ) { ToVInt(param->x) = 3; if( ToVInt(param->x) == 3 ) { param->y ++; } }

It might generate better code (depending on your compiler/target/options), since the compiler can use direct addressing instead of using a pointer.

I think any function would work here for "barrier", as long as you can be sure it is not optimised away (write it as a single "ret" assembly instruction in some other file, for example). On more advanced cpus, you might also want it to do cache flushes, or pipeline synchronisation.

Reply to
David Brown

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.