volatile variable  - Page 1
Forum section: electronics / general

author: BlogRahul
author_rank: Regular Contributor
timestamp: December 19, 2021, 12:35:40 pm
content: How does the compiler work when I use volatile in my following code? Does the compiler look at both inside and outside the ISR if I don't use it volatile?Code:int x;void main (){x = 0;while (1){if ( x == 1){x++;}}}ISR (){if(flag == 0){flag = 0;x = 1;}}

author: ledtester
author_rank: Super Contributor
timestamp: December 19, 2021, 02:12:40 pm
content: I'm not sure what you mean by looking inside and outside the ISR.The "volatile" qualifier means that the compiler will not perform certain kinds of optimizations when accessing the variable that it normally would. It is a directive to the compiler that the value of the variable can be changed by external factors and so it shouldn't make any assumptions about what that variable contains.Quick example:Code:int x;int y;...x = 3;y = x;...For the assignment to y an optimizing C compiler might simply assign 3 to y. However, if x is marked as volatile it should generate code which actually loads the value stored at x even though the previous statement assigned it to 3. And the compiler should do this regardless of where the access to x is located -- whether it occurs in an ISR or not.Even if you have declared a variable to be volatile you still may need to disable interrupts before accessing it if the value cannot be read atomically. See this page on the use of volatile in the Arduino environment for more details:https://arduinogetstarted.com/reference/arduino-volatile

author: magic
author_rank: Super Contributor
timestamp: December 19, 2021, 05:04:16 pm
content: Without volatile the compiler will "know" that x is 0 in main and may remove the "unnecessary" test for 1 (and x++) inside the loop.

author: golden_labels
author_rank: Super Contributor
timestamp: December 19, 2021, 08:04:06 pm
content: You haven’t specified the language.I am assuming C.Please be clear about the language you are asking about.You haven’t specified the platform, without which it’s impossible to answer that question in a concise manner. C has no concept of ISRs or their interaction with other parts of the code, so it’s implementation-specific.Your code doesn’t even containvolatile.I am assumingthat you meanxto bevolatile.On all platforms, the code is invalid.volatiledoesn’t promise atomicity of reads/stores, so you can’t simply assign to a variable and expect the other side to always see a valid value.volatileis also not affecting atomicity of multiple logically related operations, so you have race conditions in the code. Both of those prove that on any platform the behaviour of this code is undefined.Besides the problems mentioned above, on some platforms that code is still invalid:volatiledoesn’t make any promises about visibility of changes. While on some simplistic architectures a store is automatically visible to all other threads, on many it is not.volatiledoes not indicate to the compiler that reads and stores should be implemented in a way that ensures memory consistency.So whilevolatilemaybe necessary, it’s not sufficient. With a stress on word “may”: in many currently used solutions it is no longer really needed(1)and is used “just in case”. What you are looking for is either platform-specific synchronization — using e.g. mutexes and disabling interrupts — orthe atomic operations API.(2)____(1)With some developers supporting a complete ban on its usage. I do not subscribe to that camp, but you may prefer to be aware how strong the lack of necessity forvolatileis.(2)With a warning that atomics are not fully implemented on some platforms.

author: magic
author_rank: Super Contributor
timestamp: December 20, 2021, 09:07:50 am
content: C standard atomics are a big, stinking trap, you really need to be careful with them.This is AVR code which decreases a volatile char variable by one:Code:00000000 <atomic>:0: 80 91 00 00 lds r24, 0x00004: 8f 5f subi r24, 0x01 ; 16: 80 93 00 00 sts 0x0000, r24a: 08 95 retNow volatile int:Code:00000000 <atomic>:0: 80 91 00 00 lds r24, 0x00004: 90 91 00 00 lds r25, 0x00008: 01 97 sbiw r24, 0x01 ; 1a: 90 93 00 00 sts 0x0000, r25e: 80 93 00 00 sts 0x0000, r2412: 08 95 retThese are not quite correct because they could be interrupted in the middle of the operation, but that can be resolved with cli/sei.For some simpler code than the above, particularly if dealing with char rather than int, disabling interrupts may not even be necessary.And now atomic_int:Code:00000000 <atomic>:0: 0f 93 push r162: 1f 93 push r174: cf 93 push r286: df 93 push r298: 00 d0 rcall .+0 ; 0xa <atomic+0xa>a: cd b7 in r28, 0x3d ; 61c: de b7 in r29, 0x3e ; 62e: 65 e0 ldi r22, 0x05 ; 510: 70 e0 ldi r23, 0x00 ; 012: 80 e0 ldi r24, 0x00 ; 014: 90 e0 ldi r25, 0x00 ; 016: 00 d0 rcall .+0 ; 0x18 <atomic+0x18>18: 9a 83 std Y+2, r25 ; 0x021a: 89 83 std Y+1, r24 ; 0x011c: 00 c0 rjmp .+0 ; 0x1e <atomic+0x1e>1e: 89 81 ldd r24, Y+1 ; 0x0120: 9a 81 ldd r25, Y+2 ; 0x0222: 48 2f mov r20, r2424: 59 2f mov r21, r2526: 41 50 subi r20, 0x01 ; 128: 51 09 sbc r21, r12a: 05 e0 ldi r16, 0x05 ; 52c: 10 e0 ldi r17, 0x00 ; 02e: 25 e0 ldi r18, 0x05 ; 530: 30 e0 ldi r19, 0x00 ; 032: 6c 2f mov r22, r2834: 7d 2f mov r23, r2936: 6f 5f subi r22, 0xFF ; 25538: 7f 4f sbci r23, 0xFF ; 2553a: 80 e0 ldi r24, 0x00 ; 03c: 90 e0 ldi r25, 0x00 ; 03e: 00 d0 rcall .+0 ; 0x40 <atomic+0x40>40: 88 23 and r24, r2442: 01 f0 breq .+0 ; 0x44 <atomic+0x44>44: 0f 90 pop r046: 0f 90 pop r048: df 91 pop r294a: cf 91 pop r284c: 1f 91 pop r174e: 0f 91 pop r1650: 08 95 retYou need to test for the ATOMIC_xxx_LOCK_FREE macro and stay the hell away from types for which it's not defined.

author: BlogRahul
author_rank: Regular Contributor
timestamp: December 20, 2021, 09:40:13 am
content: Quote from: magic on December 19, 2021, 05:04:16 pmOkay It means without volatile, the compiler doesn't look at the value of variable x inside ISRWith volatile, the compiler looks at the value of variable x in main and inside ISRCode:volatile int x;void main (){x = 0;while (1){if ( x == 1){x++;}}}ISR (){if(flag == 0){flag = 0;x = 1;}}

author: Ian.M
author_rank: Super Contributor
timestamp: December 20, 2021, 09:55:40 am
content: Errr.... NOPE.Remember, C compilers compile each source file individually*.While compiling main() the compiler doesn't 'look at' the ISR which may even be in a separate compilation unit (source file), which would make it impossible for the compiler to 'look at' it, as it has no way to find the source file containing the ISR.All volatile does is tell the compiler *NOT* to optimize away any reads or writes to the variable that appear to have no lasting effect, and not to cache its value in scratchpad RAM or a register, and not to make any code flow optimizations that depend on assuming the variable's state.* Apart from some non-ANSI/ISO standard compliant ones that treat all source files in a project as a single compilation unit. Such are best regarded as pseudo-C compilers!

author: magic
author_rank: Super Contributor
timestamp: December 20, 2021, 10:06:05 am
content: It compiles each function individually, even in a single file. Within each function, if you set a variable to some value, it will assume that the variable stays that way and it will not check for external factors (hardware peripherals or ISRs) changing the variable behind the function's back.In your example:main: x is set to zero, which means it obviously cannot be one and the code inside the loop may be removedISR: the conditional is only entered when flag is zero, which means that setting the flag to zero may be omitted

author: SiliconWizard
author_rank: Super Contributor
timestamp: December 20, 2021, 05:57:25 pm
content: Quote from: BlogRahul on December 20, 2021, 09:40:13 amNope as said already. It's actually much simpler than this, and sort of the opposite. With volatile, the compiler doesn't try to analyze the usage of the corresponding variable, and will generate an access to it in any case.The reason your example code might optimize out part of the code in main() relative to 'x' is the following:- Purely from the main() perspective, 'x' is always 0. So it can optimize out anything relative to 'x'.- Yes, the ISR() function can modify 'x' oustide of main(), but the compiler has no means of knowing ISR() will ever get called. It can't analyze the call chain. That's something pretty typical when defining ISRs or thread functions.This is a typical setup that will require the use of the 'volatile' qualifier. But I admit this is something that can be confusing with C.Remember that C will almost always favor efficiency over "correctness", and that some additions (such as the volatile qualifier) are there to manually instruct the compiler when the intent of the programmer can't be clearly conveyed without them. Just the way it works. If you want a language that favors correctness over efficiency in all cases, pick another one.

author: golden_labels
author_rank: Super Contributor
timestamp: December 21, 2021, 03:23:02 am
content: Quote from: BlogRahul on December 20, 2021, 09:40:13 amNo. It means the compiler assumes the normal meaning of the code, as interpreted from the perspective of that code. In the following fragment, isxever modified, if it’s not 1?Code:while (1) {if (1 == x) {++x;}}Clearly not, there is no single operation that would modify anything. The code is exactly equivalent to:Code:if (1 == x) {x = 2;}while (1) {}That’s what the compiler sees withoutxbeing marked asvolatile.A side note: since there is an infinite loop in this code, the actual meaning of the code is undefined and the compiler may do anything it wishes: including never even generating any loop or simply “make demons fly out of your nose.”Quote from: BlogRahul on December 20, 2021, 09:40:13 amNo. It means that the compiler knows the snippets of code shown above are not equivalent, becausexmay be “magically” modified despite it seems it can not. So it must assume that every read fromxmay produce a different value, even if it never sees any stores intoxin this fragment.Quote from: SiliconWizard on December 20, 2021, 05:57:25 pmThat is not exactly true. It’s a bit nitpicky, but in the case ofvolatilesuch details are important and missing them is what brings pain.Withvolatilethe compiler assumes that any read from the variable may produce a different value, despite no modification to that variable can be seen. Butvolatiledoes not guarantee by itself generation of an access to the actual storage.The difference is slight, but not understanding it has grave consequences. The presence ofvolatiletells the compiler that some natural assumptions about the meaning of code can’t be made, which leads to different interpretation of the described logic andin consequencemissing some optimizations. But “generate an access” is a much stronger requirement. On simple platforms like small microcontrollers merely skipping an optimization does accidentally generate an access, but that is never true on modern, more complex architectures. Which include even any modern consumer CPU. With multilayer caches, complex CPU-level optimization, and multiple cores or processors it is required to use the right synchronization methods to ensure memory consistency. That includes explicit memory barriers, instructions offering such guarantees as side effects etc.volatilenever introduces those. And it is usually not even needed if they are used. For example on any platform supported by pthreads using synchronization primitives alone induces the desired behavior. So is using atomics API of C.And that’s only about reading a variable! I’m not even touching things like the order of execution.Also note thatvolatileis not the only situation that prevents the compiler from making assumptions about reads.charpointers behaviour in the context of strict aliasing is another such example.Quote from: SiliconWizard on December 20, 2021, 05:57:25 pmIn this case correctness is not sacrificed at all. The behaviour withoutvolatileis perfectly correct. The addition ofvolatileis not making that code more correct: it changes its semantics. Do not confuse “correct” with “what a programmer had on their mind”. While C is known to sacrifice safety and clarity for some types of performance gains, this is not the case here.

author: SiliconWizard
author_rank: Super Contributor
timestamp: December 21, 2021, 06:40:15 pm
content: I knew this was going to be fun...Quote from: golden_labels on December 21, 2021, 03:23:02 amYou obviously missed the point here, and are using circular logic. Yes it is correct as far as C is concerned. No doubt about it. But that's the whole point. C's take on this *does* favor efficiency over correctness. Now we'll probably have to define "correctness" in programming. It will take a whole book. Or more like several. But I'm strictly speaking about correctness in *translating the intent of the programmer* here. That's all a programming language is supposed to do. It's not supposed to design software for you.Quote from: golden_labels on December 21, 2021, 03:23:02 amThis is exactly how I define correctness indeed, as far asthe programming language and tools are concerned. The language can't second-guess what the programmer really wants to do - its role is ONLY to make them able to express that in a clear and non-ambiguous way. Of course correctness is EXACTLY what the programmer had on their mind,as far as a programming language is concerned. So ideally, a programming language should strive to make this as easy and unambiguous as possible, and with the smallest number of funky rules possible. As much as I like C (and use it almost exclusively when it comes to compiled languages), it's definitely not very good at that.There are quite a few examples of that in C, but to take this very example here, this is definitely a choice in C that is, at best, questionable. It makes it possible for a compiler to optimize out access to an object *without knowing if it really can do that safely*, and puts the burden of the proof, so to speak, on the programmer's shoulders. That's a typical choice. A much safer choice would be to make it impossible *unless it can be proven safe after full analysis of the source code, even across compilation units*, which is something C definitely does not mandate. In that, it clearly chose not to favor correctness. Hope the point is clear.

author: magic
author_rank: Super Contributor
timestamp: December 21, 2021, 11:34:01 pm
content: That would preclude optimization of functions like qsort() unless you can prove that they will never be called on an array which something else is monkeying with. If you could prove that, you could also statically prove absence of a lot of bugs.And what about compiling a library for other programs to use? Then you can't prove anything.And that's just the single core case. On multicore if you want to enforce full memory consistency you may as well just give up and run one threadAnother fun example:You are compiling some big project like an OS kernel.You notice it's doing some very shady stuff, downloading data from untrusted networks, executing code from diskNot quite sure if the kernel is entirely secure and if a rogue program couldn't overwrite some kernel data structure.Better safe than sorry - no optimizations for you, dirty kernelAll exploits devised by hackers must work intuitively and predictably