'volatile' semantics

Is there a difference between

typedef struct SBob { int a; int b; int c; } SBob;

volatile extern Sbob gBob;

and

typedef struct SBob { volatile int a; volatile int b; volatile int c; } SBob;

extern Sbob gBob;

?

Assuming that there's a peripheral out there named Bob, of course.

--
Tim Wescott
Control system and signal processing consulting
 Click to see the full signature
Reply to
Tim
Loading thread data ...

I have never seen any compiler screw this up.

I have seen gcc "forget" volatile with this one.

Leo Havmøller.

Reply to
Leo Havmøller

Hi Tim

Yes - there ought to a difference:

volatile "forces" the reading of the variable directly from its source function/address every time the value is needed.

The source e.g. could be an internal register of a separate chip connected via some bus or poit-to-point.

So you can not rely on a local variable value copy - it might have been updated several times - or the reading itself have side-effect - e.g. the side-effect might be a fresh sampling of som signal in the other chip and some other-chip-local book-keeping.

-

Volatile variable:

formatting link
Quote: "... In C, and consequently C++, the volatile keyword was intended to[1]

  • allow access to memory mapped devices
  • allow uses of variables between setjmp and longjmp
  • allow uses of sig_atomic_t variables in signal handlers. ..."

br,

Glenn

Reply to
Glenn

Hi Tim

The obvious - it ought to be the same.

Please note that there is also the group: nntp://comp.lang.c (and nntp://comp.lang.c++ that also has volatile)

br,

Glenn

Reply to
Glenn

Theoretically, there should not be much difference. But the first version is better style, I think. It makes life a lot easier if you want non-volatile local copies of the structure - you can add or remove the "volatile" with a simple cast. It also means you are thinking of "Bob" as a volatile peripheral, rather than bits of Bob being volatile peripherals.

A key point to getting "volatile" right is to understand that there is no such thing as a volatile object - it is /accesses/ to the object that are volatile. Marking an object as volatile just says all accesses should, by default, be volatile - that's often convenient. At other times, it can make more sense for the object to be declared without "volatile" - and explicitly use a volatile access. The following macro is very convenient for that (for gcc, and compilers that support gcc extensions):

#define volatileAccess(v) *((volatile typeof((v)) *) &(v))

Reply to
David Brown

=20

=20

=20

OK, it's been over 10 years since I've done any serious C programming, so t= ake this with a grain of salt ;-)...

Going through pointers to entire structures the volatile property should be= preserved in either case. However, I think differences might occur when de= fining pointers to individual members of the structure. In the first case, = with the entire structure volatile, you might be able to declare pointers t= o the structure members (without casting) as pointers to ints. Accesses to = those members through the pointers will then not be treated as volatile. In= the second case, with each individual structure member as volatile, you mu= st declare pointers to the members as pointers to volatile ints. Accesses t= hrough those pointers will then be treated as volatile.=20

So if you do not use pointers to individual structure members, I think both= methods are equivalent. If you might have pointers to individual structure= members, I recommend the second approach with each member volatile.

-- Marc

Reply to
Marc Guardiani

(If you can, please try to fix your newsgroup client - it's breaking the thread references. It also has the line width wrong, though that's easy to fix.)

Correct.

I don't think that's the case - you would need a cast to remove the "volatile" qualifier that is inherited from the volatile struct. But I would want to try it before relying on it.

Reply to
David Brown

Google Groups is broken, but it's all I have at work. sigh.

-- Marc

Reply to
Marc Guardiani
[Most of the regulars probably know all of this, but it may be worth adding for anyone who stumbles across this thread via e.g. Google.]

The only semantics defined by the standard relate to setjmp/longjmp and signal handling. Anything else relies upon the compiler writers making the same assumptions as the programmer.

In practical terms, it's reasonably safe to assume that each read or write of a volatile-qualified lvalue will result in a load or store instruction being emitted at the "appropriate" point in the object code (with the corollary that this will force the appropriate points to exist, rather than being optimised away).

A volatile qualifier won't protect you against any optimisations being performed by the CPU itself (caching, out-of-order execution, etc). That has to be dealt with via MTRRs, memory barriers, etc.

Reply to
Nobody

Or the PowerPC instruction that happens to have my Most Favorite Mnemonic in the World: EIEIO*. I think someone won a bet when that got included in the instruction set.

  • Enforce In-order Execution of Input/Output.
--
Tim Wescott
Wescott Design Services
 Click to see the full signature
Reply to
Tim Wescott

And people think that IBM don't have a sense of humour...

Reply to
David Brown

It's good that you point this out - many people misunderstand this, and think "volatile" is more magical than it really is.

Another common misconception is the belief that "volatile" implies "atomic". People write code like this, for an 8-bit processor:

volatile int16_t ticks;

void timerInterrupt(void) { ticks++; }

void pauseHundredTicks(void) { int16_t start = ticks; while ((ticks - start) < 100) ; }

The other big misconception I have seen is the belief that other memory accesses are ordered with respect to "volatile" accesses. It is correct that volatile accesses are strictly ordered with respect to each other, but no ordering is implied with respect to other accesses or calculations. So this is unsafe:

extern volatile bool testPin;

void speedTest(void) { testPin = 1; uint16_t sum = 0; for (uint16_t i = 0; i < 100; i++) { sum += i; } testPin = 0; }

The idea here is that you can use an oscilloscope to see how long the calculation takes. But the compiler is actually free to move all or part of the calculation before setting "testPin = 1" or after "testPin =

0". (Of course, it is also free to eliminate the calculation entirely if sum is not used, or replace it with "uint16_t sum = 4950" - but we'll assume that dead code elimination is turned off.)
Reply to
David Brown

t

David had to explain to me why it was funny (I don't have an English childhood background) :D .

It only enforces in order execution (as the name implies...), on certain inherently "in order" cores it is just a NOP. It does not protect against caching, that must be either in the page descriptor or explicitly done (e.g. dcbf or something).

Dimiter

------------------------------------------------------ Dimiter Popoff Transgalactic Instruments

formatting link

------------------------------------------------------

formatting link

Reply to
Didi

"David Brown" escribió en el mensaje de noticias: snipped-for-privacy@lyse.net...

[...]

Would this do the trick?:

extern volatile bool testPin;

void speedTest(void) { testPin = 1; volatile uint16_t sum = 0; for (uint16_t i = 0; i < 100; i++) { sum += i; } testPin = 0; }

Reply to
Ignacio G. T.

Could you please cite chapter and verse for that? As I read 5.1.2.3 paragraph 2 and 6.7.3 paragraph 6, what you're saying here regarding the order of execution is simply wrong.

Indeed, even in the absence of the volatile type qualifier, the assignments to testPin are guaranteed to occur on "either side" of the for loop (leaving aside the mis-placed declaration of sum).

--
Rich Webb     Norfolk, VA
Reply to
Rich Webb

Yes, that would work - but the timing would be different (as "sum" would have to be written out to memory every loop, rather than kept in a register).

But the principle is right - you make sure the loop depends on something volatile, and that the result is used in some volatile way:

void speedTest(void) { static volatile v; v = 0; testPin = 1; uint16_t initVal = v; uint16_t sum = 0; for (uint16_t i = initVal; i < 100; i++) { sum += i; } v = sum; testPin = 0; }

Reply to
David Brown

I don't have a C standard in front of me here for reference.

But the key point is that C is described in terms of running on a C "virtual machine", and the compiler must generate code that /acts as though/ it follows the rules. There are only certain points in the program that are "visible" from the outside - these are program entry, program exit, calls to external functions unknown to the compiler (such as library functions), entry points from the outside (relevant to libraries), and all "volatile" accesses. Between any two such points, the compiler's code can do anything it wants - just as long as the visible results (i.e., code outputs) at each such point are identical to those it would have if it applied a direct and naive implementation of the C code.

This means that the compiler can move non-volatile accesses back and forth across volatile accesses. If it can pre-calculate the result of a loop, then it is free to do so. If it wants to add some extra code, it can do that too (for example for profiling).

It is often assumed that the compiler has to follow sequence points, so that code before a sequence point is completed before code after the sequence point. That's not the case - it only has to /appear/ that way from the outside. However, if you are doing something that the compiler doesn't know everything about - such as calling an "extern" function when the compiler can't see the definition - then the compiler has to assume the worst case in terms of what that function might do. This will often force the compiler to fully synchronise around the function call.

Reply to
David Brown

Almost right. The certain points are sequence points and your description is close. The sequence points (summarized from Appx C) are:

-- the call to a function (any function, not just those unknown to the compiler) after all function arguments have been evaluated

-- the first operand of &&, ||, ?:, and comma

-- the end of a declaration

-- the end of a full expression. Basically before a ; or the expressions in if, while, switch, return, and for (each of the three in for)

-- just before a library function returns

-- after each formatted I/O conversion specifier

-- before and after each call to a comparison function (qsort() etc, not the comparison operators).

s/visible results/side effects/

That it can not do. There is a sequence point at the end of each expression and at that point all side effects must have taken place. Modifying an object ('sum' in the snippet) counts as a side effect.

--
Rich Webb     Norfolk, VA
Reply to
Rich Webb

Cue "the" list :-)

(Please do provide links to additional documents.)

formatting link
formatting link
formatting link

Regards.

Reply to
Noob

True. But all the cache twiddling in the world won't help you if the core is changing things around.

Did David recite the song for you, or point you to a good UTube rendition?

formatting link
I admit, the mnemonic does lose some 'chuckle value' if you hadn't enthusiastically sung the song (and been occasionally forced to fake enthusiasm to it) when you were 5.

(And on this farm he had a UART, EIEIO...)

--
Tim Wescott
Wescott Design Services
 Click to see the full signature
Reply to
Tim Wescott

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.