1

I am struggling with the correct use of the volatile keyword when implementing a circular buffer. The buffer is written in an ISR and read in the main program. I am running a bare-metal microcontroller, so no multithreading.

My current implementation looks something like this:

volatile char circ_buf[n] = {0}; volatile size_t head_index = 0; volatile size_t tail_index = 0; void buffer_input(const char c) { if((head_index+ 1) % n != tail_index) { //Only if buffer is not full circ_buf[head_index] = c; head_index = (head_index + 1) % n; } } char buffer_read(void) { if (tail_index != head_index) { char c = circ_buf[tail_index]; tail_index = (tail_index + 1) % n; return c; } //Lets assume 0 will never be content of the buffer for this example return 0; } 

buffer_input is called from an ISR, buffer_read from the main program.

This implementation works, but I have read somewhere that making the buffer array itself volatile is not needed, because the content is only accessed via the volatile indices. Is that true? The content is accessed both in the interrupt and main program, so in my understanding the buffer should be volatile too?

6
  • 1
    If it were unnecessary, that would only be because it makes no difference to the code generation, so you may as well keep it and stop worrying about "correctness". It is not "incorrect" whether it is unnecessary or not. It is at worst benign. I'd be more concerned about the atomicity of size_t. Commented Aug 15, 2023 at 13:00
  • @Clifford thanks! I am actually using atomic_size_t types from the stdatomic.h header, but i thought including them would make the example more confusing. Commented Aug 15, 2023 at 13:19
  • Do you really need size_t for index? Commented Aug 15, 2023 at 13:24
  • @SupportUkraine thank you for your comments, i edited the code. I am simplifying a piece of code from a project here, the mistakes you mentioned occured while simplifying. size_t seems alright for me, since the buffer might get big. Commented Aug 15, 2023 at 13:30
  • Or course, I assume any calls to buffer_read et. al. from main/task level are wrapped in cli/sti calls. Commented Aug 16, 2023 at 0:05

1 Answer 1

3

This implementation works, but I have read somewhere that making the buffer array itself volatile is not needed, because the content is only accessed via the volatile indices. Is that true?

No, because if circ_buf is not qualified with volatile, then the C standard does not require a program to reread an element of the buffer when that element is used for a second or subsequent time, regardless of whether the indices are volatile or not.

On the other hand, yes, but dangerously so. The reason for the rules around volatile is to require a C implementation to reread things that may change (and similarly to write things that must be made visible to the hardware, but that is not a concern when we are only considering the buffer_read routine) and not perform optimizations that can omit the rereads. We can reason that, due to the way the buffer is used and how many elements it contains (if it is sufficiently large), there are no reasonable optimizations a compiler could make that would allow it to cache buffer elements. This is a hazardous way to proceed and would not be used in normal software engineering, but I include it to make the answer complete.

If x is volatile, then int t0 = x, t1 = x; is required to read x twice. If x were not volatile, a compiler would easily optimize this to read x once. But given how buffer_read steps through the buffer, it is not feasible for the compiler to read, say, buffer[0] once and hold onto a copy to be reused after the reads step through the entire buffer and return to the beginning. The compiler must reread buffer[0] because it does not have a practical way to avoid it. So, in practice, if you omit volatile from circ_buf, you will not see the program malfunction from this.

Nonetheless, it is correct for to have volatile. Without it, the meaning of the C source code as specified by the C standard is not the desired meaning for the program, that the latest value of circ_buf[tail_index] as updated by the ISR be read from memory.

Sign up to request clarification or add additional context in comments.

2 Comments

exen int y + x + x; will read x twice
Thank you! That makes sense, this also explains why similar code without the volatile qualifier for the buffer didn't fail.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.