-2

As a general concept Global variable ("value") used in ISR function should be declared as volatile to avoid compiler optimization. but my doubt is a global variable is used in sub-function "ISR-SUB" which is invoked in ISR, whether the global variable used sub-function which is invoked in ISR also needs to be declared as volatile ?

unsigned int STATUS;  // -----> needs to be declared as volatile ?

void ISR-SUB()
{
  STATUS = 1; -->accessed in sub function invoked in ISR which will be optimized or not
}

void ISR ()
{
  ISR-SUB();
}

void main()
{

  /* interrupt occurred and ISR called */

  if (1 == STATUS)
  {
    code part
  }
}
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
Gopi M
  • 89
  • 2
  • 9

3 Answers3

3

Of course you do.
volatile isn't a prerogative of an ISR, it has a specific definition in the C11 standard:

An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects.
Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3.

Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.
What constitutes an access to an object that has volatile-qualified type is implementation-defined.

So whenever you control flows in a manner that cannot be deduced from the sources (like when an interrupt occurs), the compiler cannot know that a variable may have changed meanwhile.
You have to use volatile to tell it that such variable is subject to change at any moment.


If that's too abstract, consider this toy code for the AVR microcontroller:

unsigned char STATUS;

void ISR_SUB()
{
  STATUS = 0x80;
}

void ISR ()
{
  ISR_SUB();
}

int main()
{
  unsigned char i=1;

  while (STATUS & 0x80)
  {
    STATUS |= i;
  }

  return 0;
}

This gets compiled into this assembly code

main:
        lds r24,STATUS             ;r24 = STATUS 
        sbrs r24,7                 ;Skip next inst if bit7 of r27 is set
        rjmp .L4                   ;Jump to the end
.L6:
        ori r24,lo8(1)             ;OR r24 with 1
        sbrc r24,7                 ;Do the test again, break loop if bit7 set
        rjmp .L6                   ;Jump back to the loop

        sts STATUS,r24             ;STATUS = r24
.L4:
        ldi r24,lo8(0)
        ldi r25,hi8(0)
        ret

As you can see, the variable STATUS is read once and updated in the register r24, so the loop will never end!
Now look at what happens when we use volatile

main:
        rjmp .L8
.L6:
        lds r24,STATUS           ;Now status is load in r24 at each iteration ...
        ori r24,lo8(1)           ;... updated and ...
        sts STATUS,r24           ;... stored back
.L8:
        lds r24,STATUS
        sbrc r24,7
        rjmp .L6
        ldi r24,lo8(0)
        ldi r25,hi8(0)
        ret

This time STATUS is read and updated at every iteration, as requested.

Note on synchronisation

Many thanks to @Olaf for pointing out the necessity of this section.

I made no claims above that volatile is a sufficient condition for whatever the OP is trying to implement (there is simply not enough context to made any claim).
The way this answer is to be interpreted is that volatile is a necessary condition (as the simple counter-example above shows).

The code shown, as said, is a toy example meant to show a simple issue that can arise without volatile.
It is not meant to be working code because actually I will not work.

When dealing with concurrent flows of execution synchronisation is mandatory and for C this can be achieved using the stdatomic.h header and functions.

Being this a uC question, stdatomic.h may not be present or no synchronisation may be required (this is rare).
Just to avoid any misunderstanding: if you have stdatomic.h then use it (it will eventually compile to nothing but it made the code portable).

The example above contains a RMW operation (the |=) that is not atomic can thus cancel the update made by the ISR.


So yes, you do need (at least) volatile.

Margaret Bloom
  • 41,768
  • 5
  • 78
  • 124
  • Explanation is just awesome. – Gopi M Jan 06 '17 at 11:35
  • 1
    @GopiM ... and it is wrong! `volatile` does not guarantee atomicity. It does not "tell it that such variable is subject to change at any moment", but that it is accessed outside the control flow known to the compiler. If you want to make sure modifications are atomic (i.e. the value is consistent), you have to use additional means. If your implementation provides, you can use `stdatomic.h`, otherwise you have to use platform-dependent features like interrupt enable/disable intrinsics, etc. – too honest for this site Jan 09 '17 at 13:13
  • @Olaf There is not a single reference to atomicity in my answer. What the heck are you talking about? It's all about side effects... – Margaret Bloom Jan 09 '17 at 13:43
  • 1
    "There is not a single reference to atomicity in my answer." - Yes and that's exactly the problem. Your answer implies that `volatile` is sufficient and guarantees all relevant aspects. Which is just not true. – too honest for this site Jan 09 '17 at 13:45
  • @Olaf, The OP is not asking about atomicity, they are asking if it is **necessary** to use `volatile`. I made not claims about it being (or not being) a **sufficient** condition for whatever the OP is trying to implement (and that is *not present* in the post). You are reading something that it's not there IMO but I agree with you that a word on it is worth saying. So in the end, thanks for the remark [still I don't think my answer is wrong :)] – Margaret Bloom Jan 09 '17 at 13:58
  • @Olaf First I have to do some learning about atomicity but thank you very much for your explanation. it will be useful in future. At this point of time I think, using volatile keyword is enough for my code. now it is working fine. – Gopi M Jan 09 '17 at 15:04
  • @GopiM As I say in the updated answer, if you have access to `stdatomic.h`, use it. It's not so hard :) while it's hard to spot and debug race conditions. – Margaret Bloom Jan 09 '17 at 15:09
  • 2
    @GopiM: Without more details it is impossible to predict that it is correct or not. However, "working fine" is a very weak argument. One of the problems with race-conditions is they are one of the most complicated things to debug, because most times they cannot be easily enforced. So it might work 100 days without problems and then cause permanent trouble for multiple hours until they disappear for another year. – too honest for this site Jan 09 '17 at 15:12
  • 1
    "as shown above, the compiler is not required to strictly agree with the C abstract machine" - The compiler is **always** required to "agree" with (i.e. implement) the abstract machine, unless you invoke UB! That's what `volatile` is for actually: to tell it something happens outside the known control flow. And `stdatomic` does not provide synchronisation, but only guarantees atomic access/operations. They can (and should) be used to implement synchronisation mechanisms, though. And for MCUs supporting atomic access (e.g. ARMv7M/R/A) it should be available on a modern compiler. – too honest for this site Jan 09 '17 at 15:15
  • @Olaf ok. I will use stdatomic.h in my code. that will make my code more safety. – Gopi M Jan 09 '17 at 15:18
  • @Olaf: From the C11 standard: "*In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced*" and "*Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.*". From which I deduce that non `volatile` are not required to be evaluated strictly in said sense (yet still evaluated **accordingly** to the AM) Is that not correct? – Margaret Bloom Jan 09 '17 at 15:20
  • @MargaretBloom: The abstract machine (AM) does not define a sequence, but the semantics. Thus reordering instructions is not even part of it and cannot be used as basis for "agreeing". And we hopefully can agree the compiler has to follow the semantics as defined by the language, which means the compiler has to agree with the AM. `volatile` does not change that; it just introduces specific semantics the compiler (as well as the AM) has to follow. All for valid code only, of course (which implies modifying non-volatiles outside control flow invokes UB. – too honest for this site Jan 09 '17 at 15:25
  • (the latter is an alternative view to the `volatile` issue, resp. a missing `volatile` and can be accordingly applied to the other qualifiers like `const`, `restrict` and `_Atomic`.) – too honest for this site Jan 09 '17 at 15:27
  • @Olaf, mmm... so how could I translate these two examples from the standard? "*An implementation might define a one-to-one correspondence between abstract and actual semantics[...] The keyword volatile would then be redundant.*" and "* an implementation might perform various optimizations within each translation unit, such that the actual semantics would agree with the abstract semantics only when making function calls across translation unit boundaries.*" (thanks for your time anyway) – Margaret Bloom Jan 09 '17 at 15:34
  • "abstract semantics" does not refer to the abstract machine. This is basically about implementing the platform's ABI for function calls or using optimised calls to (or even inline) `static` functions. FYI: The latter has become even more weak with LTO. It still has to follow the AM's requirement of the **concrete** (opposed to "abstract") semantics of the language constructs. So you can generate simple code-patterns for every C construct or highly optimise the code (like a modern compiler does). – too honest for this site Jan 09 '17 at 15:47
  • @Olaf ahh Ok, that now makes sense. Thank you very much! – Margaret Bloom Jan 09 '17 at 15:50
2

Yes it does.

The function of volatile is to tell the compiler that the value of the variable may be read or written at any time without its knowledge. With a normal variable the compiler assumes that it has full information about how and when it is changed.

Consider this function to go with your ISR:

void normal_function()
{
    STATUS = 3;
    // ... some more code ...
    if (STATUS != 3)
        // do something
}

If STATUS is not marked volatile then the compiler is allowed to keep the value of STATUS in a register between the two statements that reference it or even to assume that the if statement will never trigger.

A more common pattern would be

while (STATUS != 1)
    // do something

You are expecting that the ISR will set STATUS to 1 and stop the while loop. However the compiler is allowed to read it once, keep that value in a register and never read it again. It may even only test it once.

The complementary problem is

void normal_function()
{
    STATUS = 3;
    while (!hell_frozen)
        // ... lots of code not involving STATUS...
    STATUS = STATUS + 1;
}

Here the compiler is allowed to not actually write the value of STATUS until the end of the function. So long as it keeps track of what its value should be it can put off writing to the memory location. If your ISR was waiting for STATUS to be 3 that would be a problem.

  • `volatile` does not guarantee atomicity. It does not "tell it that such variable is subject to change at any moment", but that it is accessed outside the control flow known to the compiler. If you want to make sure modifications are atomic (i.e. the value is consistent), you have to use additional means. If your implementation provides, you can use `stdatomic.h`, otherwise you have to use platform-dependent features like interrupt enable/disable intrinsics, etc. – too honest for this site Jan 09 '17 at 13:14
  • Atomicity -- ensuring that the value is wholey read and written by each given entity and that, for example, the main thread is not half-way through writing a new value when the ISR reads it -- is a whole new ball-game. With multithreaded code semaphores can be used and the x86 processor can do atomic writes ands reads. One method I use is to ensure that each variable is a single byte and at any given time only one thread has permission to write it. – Richard Urwin Jan 09 '17 at 13:37
  • It is **not** a new aspect, but part of the whole complex for concurrent access. For interrupts it only becomes a bit less complicated, because only one thread of execution can interrupt the other, but not vice-versa. And using a single byte does not help either. Read-modify-write operations on `volatile` are not guaranteed to be atomic either. That's exactly where atomics come into play. In MCUs with single CPU core the additional aspect of coherency, memory ordering, might be less relevant, though. This is not about x86 or any specific architecture, but MCUs in general (typically not x86). – too honest for this site Jan 09 '17 at 13:39
-1

Volatile :

this key word is used generally when we are working in embedded hardware system, because the declared variable may be not modified only by our C code but could be modified by our embedded hardware system

For example , if we choose to map the address of our variable to be a part of the address of our external embedded hardware system then we choose as option for our compiler(GCC for example) to make an optimization on the code. What is going to happen if we poll to the value of this variable not declared as volatile to check if it become 1 or not, in fact our compiler will see that we are checking the value of a variable that have always the same address, so it will copy the initial value of our non volatile variable into a temporary internal register to make easy the check and not to take each time the time to go reading the contain of our hardware address.

But what if our embedded hardware system modify the contain of this hardware variable address in this case we will never figure out because the compiler is reading from the temporary register and not the hardware address of our variable and here we figure out how much is important the key word volatile.

dhokar.w
  • 386
  • 3
  • 6
  • You mix abstraction layers, which is not a good idea. It does not matter what the compiler does, just that it has to implement the abstract machine. And `volatile` per se is not enough of a guarantee. – too honest for this site Jan 09 '17 at 13:16
  • Getting ad hominem shows you don't have an argument. Great attitude. And a single code is certainly a good proof for a general concept (just to state clear: that was meant sarcastic). Sorry, non sequitur! You might want to read the standard and see other implementations before you start offending others with nonsense! – too honest for this site Jan 09 '17 at 20:37
  • There is a difference between knowing the standard and having a prove that this standard is working as expected i mention a prove to check the standard using some compiler feature and this what make difference between programmers !!! – dhokar.w Jan 10 '17 at 06:40