Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The use of volatile is typical here. It allows the ring buffer to be used from interrupts, as long as you have one reader and one writer at a time.

I haven't checked the code for correctness, but in a typical ring buffer implementation intended to be used in interrupts, you would make the read and write pos volatile.

To write, you put the value in the array, and then advance the write position. To read, you copy a value out, and then advance the read position. Volatile ensures that if a read is interrupted by a write or vice versa, the entire operation is still atomic. Without volatile, the compiler has more freedom to reorder memory access.



> The use of volatile is typical here.

This is definitely true. C++ programmers typically do this.

> It allows the ring buffer to be used from interrupts

Unfortunately "allows" here is telling us about the programmers not the hardware. The programmers see this and figure eh, I don't really understand this but somebody wrote "volatile" so I guess they knew what they were doing.

> Volatile ensures that if a read is interrupted by a write or vice versa, the entire operation is still atomic

If you want atomic operations you need to use atomic operations not volatile ones. C++ 98 doesn't provide standardized atomics, so you would need to find out on each target what (if anything) you're required to do to get atomic behaviour.

The volatile keyword turns accesses into explicit memory reads and writes. This is what you need if you're a device driver, because your "memory" access might really not be to RAM at all. If the compiler elides a series of repeating writes to the CGA card because they don't seem to be needed after analysing the program, the effect is that the screen is blank and the program's purpose was not fulfilled.

This keyword does not mean "I want a Sequentially Consistent memory model across all my code, except somehow still very fast". That's not a thing.

Volatile accesses for things that are clearly just RAM are a code smell. As a result the volatile keyword in C and C++ is usually a code smell.


Right. Another reason not to confine ourselves to C++98.

While the abstract machine is not allowed to reorder volatile writes, the compiler is NOT obliged to emit instructions forcing the actual hardware not to reorder writes. Thus, regular cache behavior can turn your carefully ordered sequence of volatile writes into a bunch of local cache operations followed by a single writeback bus transaction.

If you are coding to a microcontroller, its cache hardware might be simple enough that this can't happen. Or, you might be able (and need!) to initialize a memory controller, at startup, to give a chosen memory address range simpler write semantics, e.g. "write-through".

But atomics are the cleanest way to express things at the source level.


> Unfortunately "allows" here is telling us about the programmers not the hardware. The programmers see this and figure eh, I don't really understand this but somebody wrote "volatile" so I guess they knew what they were doing.

So... you're saying that whether a piece of code is correct is something that's more complicated than just noticing that it has the word "volatile" written somewhere? This is super obvious.

> C++ 98 doesn't provide standardized atomics, so you would need to find out on each target what (if anything) you're required to do to get atomic behaviour.

Yes.

But you don't need all of your operations to be atomic in order to get atomic behavior for an operation. It turns out that for a common ring buffer, with one producer and one consumer, you only need the read/write position to be read/written atomically, and operations on buffer data must be ordered wrt. operations on the read/write positions.

This is commonly achieved with "volatile" on single-core systems.

> This keyword does not mean "I want a Sequentially Consistent memory model across all my code, except somehow still very fast". That's not a thing.

You get ordering of the volatile operations with respect to other volatile operations, from the perspective of one CPU core. That is sometimes all you need.

The point that "some people don't understand what volatile means" is not germane.

> Volatile accesses for things that are clearly just RAM are a code smell. As a result the volatile keyword in C and C++ is usually a code smell.

This is a quite extreme viewpoint. I can't agree with it.

Volatile is, yes, overused and abused. Or at least it was. However, if you want to write a ring buffer and use it on a single-core processor in an embedded environment, you can write the whole thing in old-school C90 or C++98, and the only real question you have about your environment is whether the operations on read/write pointers will tear.

It is rare, at the very least, to find a CPU where reads and writes to an int will tear.


> It turns out that for a common ring buffer, with one producer and one consumer, you only need the read/write position to be read/written atomically, and operations on buffer data must be ordered wrt. operations on the read/write positions.

But again, putting a volatile modifier on the read/write position did not achieve this.

Most of what you thought you wanted "volatile" for here is either just always the behaviour for aligned primitive types anyway (on platforms like x86 with a relatively strong consistency guarantee at a low level) or still unsafe even after you sprinkled volatile around (on ARM platforms with relaxed memory access rules, unless your compiler specifically gives different behaviour for volatile)

> This is a quite extreme viewpoint. I can't agree with it.

It's the conventional viewpoint by now, even the C++ Core Guidelines explicitly tell you not to do this. e.g. CP200

But if you want an extreme viewpoint not yet widely accepted by say the C++ Standards Committee, try this:

Neither volatile nor atomic should exist as type qualifiers. A "volatile 32-bit integer" isn't a thing, and neither is an "atomic 32-bit integer". The operations exist and they're important in low-level programming, but the types are a fiction‡.

Rust's model here is much closer. You can do volatile memory access in Rust, but you can't have "volatile" types, they're not a thing. You must explicitly call an (inlined) function to read whatever primitive data you need out of a memory address or write it to the address. As a result, programmers who need volatile memory access are far more likely to remain clear about what they're doing and why, not just sprinkling "volatile" keywords and hoping it'll do what they meant.

‡ If you want some real C++ horror, why is volatile constexpr a thing? Literally the committee's excuse is maybe this could be useful. They don't have a concrete example of use, but hey, why not throw things we don't need into the standard, it's not as though C++ is a bloated language with far too many edge cases already...


Unfortunately the compiler can still reorder nonvolatile operations across volatile ones, so unless the buffer is also volatile, it is not as useful as one would expect.


Right. Volatile very rarely means what peoole using it imagine it means. It very much does not mean Do What I Mean.

E.g. compilers routinely elide writes to stack variables declared volatile—or even atomic—that they "know" are not aliased. So e.g. if an interrupt routine might look at things in your stack frame, you need to use asm to force it not to fool with writes there. Volatile and atomic don't help you, there, because the stack frame is special.

LTO builds can expose what had been invisible operations in other TUs (".o" files) to the optimizer. So that might demand especial care.


To be pedantic, technically they can't elide, reorder or coalesce a accesses to volatile objects any more they can do any other form of I/O, even if it is a local variable whose address is never taken.

Of course how accesses on the abstract machine maps to the actual hardware is implementation defined, although most will document to translate them to plain movs.

And implementations have bugs of course; here [1] gcc removing a volatile access to an otherwise unused volatile parameter is considered a bug, even when the parameter is actually passed via register (in this case I would say the standard is underspecified).

You are absolutely correct regarding non-volatile atomics though.

[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71793


It is notable that this report is against gcc-5, not assigned, and has not been looked at in five years.

There certainly are people who think volatile should mean something for stack variables, but the compiler people very much Do Not Care. So, by the technical wording of Standards, volatile means the same for all variables, in actual compilers (with certain exceptions) it does not.


Well, Richard Biener certainly count as a compiler person (they are heavily involved with the GCC middle end), and they seem to care, although not enough to fix it as the issue if fairly academical in this case.


This is why volatile function parameters are deprecated in C++20. It's basically nonsensical to begin with.

It sounds like the author of the code expected that the argument would get a memory location on the stack somewhere, but this behavior isn't actually mandated by the standard. If a variable is placed in a register, then "reading" it is a no-op.


Interesting that they are deprecated because volatile locals have specific guarantees with regard to longjmp.


Assuming pre-C++20 semantics, which were anyway compiler specific.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p115...


With one exception, the relevant details aren't really compiler-specific, and C++20 keeps the relevant parts... if you perform two operations on volatile values, the operations can't be reordered, because the spec says so.

> Accesses through volatile glvalues are evaluated strictly according to the rules of the abstract machine.

Not the best, clearest, most useful part of the C++ spec but the core concept is there... if you perform operation A on a volatile object, then perform operation B on a volatile object, the emitted code must perform A before B. To my knowledge, this part of the C++ standard hasn't even changed wording in C++20, and isn't deprecated either.

The compiler-specific parts are things like:

1. Is this a compiler fence? (If no, the buffer in a ring buffer must also be volatile.)

2. Is this a memory fence?

3. Is there some platform-specific way in which operations on volatile objects are different?

The part you do need to know is if the operation will tear.


Thanks for putting it better than my short remark.

As addition, some of the complaints regarding the new changes might be addressed in C++23.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p213...


Do you know which of these proposed changes is actually happening? I don't think all of them are, right?


You can find the C++ draft by searching for "n4861", if you're not the kind of person who wants to pay for (or has institutional access to) the final version of the spec.

The draft lists ++ / += of volatile deprecated, lists volatile function parameters and return types as deprecated, but does not mention deprecation of volatile member functions (or I didn't find it).

Keep in mind that the standard does change between draft and finalization, and I've been bitten by this before (one draft of C is missing library functions present in the final standard).


> the standard does change between draft and finalization

Interesting. This topic turned up 2 months ago [0] and I was assured that the differences between the last draft and the final document were guaranteed to be insubstantial things like formatting tweaks. You're saying this is definitely not the case in practice?

[0] https://news.ycombinator.com/item?id=26684368


The latex sources of the C++ standard are on github

https://github.com/cplusplus/draft/tree/c+%2B20

I assume that the C++20 branch actually contains the final version, but you'll have to generate the pdf yourself.


Thanks!


But the ”writing” bool in the example in the README is not declared volatile. A bit weird...


Oh I see. Didn't realize they're assuming 1 reader/1 writer. Thanks!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: