Date: Sat, 10 Oct 2026 09:44:16 +0800
>
> In your code, your objects are /not/ atomic. You do not have atomic
> accesses. Your (hypothetical or real) implementation says that volatile
> accesses have the same semantics as atomic accesses - that does not make
> them "atomic" in accordance with the C++ standard. C++ atomic accesses are
> accesses using the classes and functions from <atomic>.
I have to say the standard didn't clearly define what "atomic" means, which
is LWG2506 <https://cplusplus.github.io/LWG/issue2506>. Furthermore, even
though we make the consensus that the operations defined in <atomic> are
atomic operations, my question is: since the specific semantics for access
through volatile glvalue are equivalent to `
std::atomic_ref<int>(value).load()` for read and `
std::atomic_ref<int>(value).store(1)` for write, and both `
std::atomic_ref<int>::load` and ` std::atomic_ref<int>::store` are atomic
operations defined <atomic>, why are `#1` and `#2`, under these specified
semantics, not considered atomic operations? This is my confusion.
On Fri, Oct 9, 2026 at 6:47 PM David Brown <david.brown_at_[hidden]> wrote:
> I /do/ understand your question, I believe. I simply said "with certain
> conditions" to avoid repeating all the details from the standards.
>
> In your code, your objects are /not/ atomic. You do not have atomic
> accesses. Your (hypothetical or real) implementation says that volatile
> accesses have the same semantics as atomic accesses - that does not make
> them "atomic" in accordance with the C++ standard. C++ atomic accesses
> are accesses using the classes and functions from <atomic>.
>
> The reason the unsynchronised accesses in your code is UB is because the
> standard says it is UB. It is that simple.
>
> The program, compiled with an appropriate toolchain and run on an
> appropriate target, can have fully specified behaviour and be guaranteed
> to run correctly. Yet it is still, as far as the language is concerned,
> UB. Attempting to execute something that is UB can have all kinds of
> effects - the C++ standards do not impose any restrictions or
> requirements. One possible effect is "doing what the implementation
> says will happen for that particular construct" - another is "works as
> you want in every lab test, but squirts mustard on the tie of your
> potential customer during sales demonstrations". If the implementation
> gives real guarantees of the behaviour - or you have other guarantees in
> other ways (such as your knowledge of the timing of the threads) - then
> your program can be correct, full specified, and work as you want with
> the implementation you are using and the target you are using.
>
> And yet it is still UB, because the standard says so.
>
>
> On 09/10/2026 11:28, jim x wrote:
> > Unsynchronised access to the same address (with certain conditions)
> > is a data race, and that is undefined behaviour.
> >
> >
> > It's not true if the accesses are applied to the same atomic object. You
> > don't seem to understand my question. What I was asking is: are these
> > two operations, under the given semantics as shown in this question,
> > atomic operations under [intro.race] p17? If not, what's the reason?
> >
> > On Fri, Oct 9, 2026 at 5:19 PM David Brown <david.brown_at_[hidden]
> > <mailto:david.brown_at_[hidden]>> wrote:
> >
> >
> > I think you are mixing up terms and contexts a little.
> >
> > The C++ standards define behaviour for C++. Unsynchronised access to
> > the same address (with certain conditions) is a data race, and that
> is
> > undefined behaviour. The C++ standards do not say what will happen.
> >
> > That does not mean that a program with two threads writing the same
> > object will crash, or launch nasal daemons. It may work perfectly
> well
> > for the task required. That might be by luck, or by timing, or by
> > guarantees from the hardware, or guarantees from the toolchain
> > implementation. But it is still UB according to the C++ standard -
> the
> > C++ standard does not define the behaviour here.
> >
> > It is /not/ implementation-defined behaviour, because the C++
> standard
> > does not say that it /is/ implementation-defined behaviour. The
> > standard could not reasonably be changed to say that, since
> > implementations in general have no way to define what happens during
> a
> > data race, and saying it is "implementation-defined" means that /all/
> > implementations would have to say exactly how they behave here. The
> > standard could not reasonably be changed to say "the behaviour may be
> > undefined or implementation-defined" because doing so would be
> > pointless
> > - portable C++ code must assume it is UB, while for non-portable code
> > the implementation is always free to give defined behaviours to
> things
> > that are UB in the standards.
> >
> >
> > The program may have well-defined semantics and behaviour on a
> > particular implementation, while still being UB as C++. The
> semantics
> > of the code are determined by the behaviours defined by the standard
> > and
> > additional guarantees from the implementation.
> >
> >
> >
> >
> > On 09/10/2026 09:55, jim x wrote:
> > > So, what behavior of the program do you think? If you think the
> > program
> > > should be undefined behavior because the program contains a data
> > race,
> > > let's step-by-step to go through the rules in [intro.race]
> p17. It's
> > > true that `#1` and `#2` are two potentially concurrent conflicting
> > > actions, and neither happens before the other. The crux is
> > whether they
> > > satisfy the condition: at least one of which is not atomic. If
> > one of
> > > them is non-atomic, then the condition is satisfied; therefore,
> the
> > > program contains a data race. If they are both atomic operations,
> > the
> > > condition is false; no data race is contained. As I commented
> above,
> > > since the standard lets implementations define the semantics of
> the
> > > access, and a hypothetical implementation can define `#1` as `|
> > > std::atomic_ref<int>(value).load()|` and `#2` as `|
> > > std::atomic_ref<int>(value).store(1)|`; i.e., they're atomic
> > operations,
> > > I don't think the crux condition in [intro.race] p17 is true. So,
> > the
> > > program under this specified semantics shouldn't be UB. How do you
> > > explain this point?
> > >
> > > On Fri, Oct 9, 2026 at 3:39 PM David Brown
> > <david.brown_at_[hidden] <mailto:david.brown_at_[hidden]>
> > > <mailto:david.brown_at_[hidden]
> > <mailto:david.brown_at_[hidden]>>> wrote:
> > >
> > >
> > >
> > > On 09/10/2026 09:16, jim x via Std-Discussion wrote:
> > > > If the implementation defines that accesses to
> > volatile variables
> > > > are atomic, then they are atomic.
> > > >
> > > >
> > > > In this case, does the program contain a data race as per
> > > [intro.race]
> > > > p17? If not, since the behavior depends on how the
> > implementation
> > > define
> > > > the semantics, does that mean the behavior of the program
> > should be
> > > > implementation-defined?
> > > >
> > >
> > > "Implementation-defined" in the standards does not just mean
> > that the
> > > implementation has to treat the construct in some particular
> > way. It
> > > means that the standard gives a range of options of how the
> > > implementation should handle the construct, and that the
> > implementation
> > > must document how it handles them.
> > >
> > > If an implementation's manual says that volatile accesses are
> > atomic
> > > (not unreasonable on single-core non-superscaler targets like
> > most
> > > microcontrollers), then the programmer can rely on the
> volatile
> > > accesses
> > > being atomic. But that does not mean unsynchronised volatile
> > accesses
> > > to the same address in different threads is implementation-
> > defined
> > > behaviour, or should be called such in the C++ standards.
> > >
> > >
> >
>
>
> In your code, your objects are /not/ atomic. You do not have atomic
> accesses. Your (hypothetical or real) implementation says that volatile
> accesses have the same semantics as atomic accesses - that does not make
> them "atomic" in accordance with the C++ standard. C++ atomic accesses are
> accesses using the classes and functions from <atomic>.
I have to say the standard didn't clearly define what "atomic" means, which
is LWG2506 <https://cplusplus.github.io/LWG/issue2506>. Furthermore, even
though we make the consensus that the operations defined in <atomic> are
atomic operations, my question is: since the specific semantics for access
through volatile glvalue are equivalent to `
std::atomic_ref<int>(value).load()` for read and `
std::atomic_ref<int>(value).store(1)` for write, and both `
std::atomic_ref<int>::load` and ` std::atomic_ref<int>::store` are atomic
operations defined <atomic>, why are `#1` and `#2`, under these specified
semantics, not considered atomic operations? This is my confusion.
On Fri, Oct 9, 2026 at 6:47 PM David Brown <david.brown_at_[hidden]> wrote:
> I /do/ understand your question, I believe. I simply said "with certain
> conditions" to avoid repeating all the details from the standards.
>
> In your code, your objects are /not/ atomic. You do not have atomic
> accesses. Your (hypothetical or real) implementation says that volatile
> accesses have the same semantics as atomic accesses - that does not make
> them "atomic" in accordance with the C++ standard. C++ atomic accesses
> are accesses using the classes and functions from <atomic>.
>
> The reason the unsynchronised accesses in your code is UB is because the
> standard says it is UB. It is that simple.
>
> The program, compiled with an appropriate toolchain and run on an
> appropriate target, can have fully specified behaviour and be guaranteed
> to run correctly. Yet it is still, as far as the language is concerned,
> UB. Attempting to execute something that is UB can have all kinds of
> effects - the C++ standards do not impose any restrictions or
> requirements. One possible effect is "doing what the implementation
> says will happen for that particular construct" - another is "works as
> you want in every lab test, but squirts mustard on the tie of your
> potential customer during sales demonstrations". If the implementation
> gives real guarantees of the behaviour - or you have other guarantees in
> other ways (such as your knowledge of the timing of the threads) - then
> your program can be correct, full specified, and work as you want with
> the implementation you are using and the target you are using.
>
> And yet it is still UB, because the standard says so.
>
>
> On 09/10/2026 11:28, jim x wrote:
> > Unsynchronised access to the same address (with certain conditions)
> > is a data race, and that is undefined behaviour.
> >
> >
> > It's not true if the accesses are applied to the same atomic object. You
> > don't seem to understand my question. What I was asking is: are these
> > two operations, under the given semantics as shown in this question,
> > atomic operations under [intro.race] p17? If not, what's the reason?
> >
> > On Fri, Oct 9, 2026 at 5:19 PM David Brown <david.brown_at_[hidden]
> > <mailto:david.brown_at_[hidden]>> wrote:
> >
> >
> > I think you are mixing up terms and contexts a little.
> >
> > The C++ standards define behaviour for C++. Unsynchronised access to
> > the same address (with certain conditions) is a data race, and that
> is
> > undefined behaviour. The C++ standards do not say what will happen.
> >
> > That does not mean that a program with two threads writing the same
> > object will crash, or launch nasal daemons. It may work perfectly
> well
> > for the task required. That might be by luck, or by timing, or by
> > guarantees from the hardware, or guarantees from the toolchain
> > implementation. But it is still UB according to the C++ standard -
> the
> > C++ standard does not define the behaviour here.
> >
> > It is /not/ implementation-defined behaviour, because the C++
> standard
> > does not say that it /is/ implementation-defined behaviour. The
> > standard could not reasonably be changed to say that, since
> > implementations in general have no way to define what happens during
> a
> > data race, and saying it is "implementation-defined" means that /all/
> > implementations would have to say exactly how they behave here. The
> > standard could not reasonably be changed to say "the behaviour may be
> > undefined or implementation-defined" because doing so would be
> > pointless
> > - portable C++ code must assume it is UB, while for non-portable code
> > the implementation is always free to give defined behaviours to
> things
> > that are UB in the standards.
> >
> >
> > The program may have well-defined semantics and behaviour on a
> > particular implementation, while still being UB as C++. The
> semantics
> > of the code are determined by the behaviours defined by the standard
> > and
> > additional guarantees from the implementation.
> >
> >
> >
> >
> > On 09/10/2026 09:55, jim x wrote:
> > > So, what behavior of the program do you think? If you think the
> > program
> > > should be undefined behavior because the program contains a data
> > race,
> > > let's step-by-step to go through the rules in [intro.race]
> p17. It's
> > > true that `#1` and `#2` are two potentially concurrent conflicting
> > > actions, and neither happens before the other. The crux is
> > whether they
> > > satisfy the condition: at least one of which is not atomic. If
> > one of
> > > them is non-atomic, then the condition is satisfied; therefore,
> the
> > > program contains a data race. If they are both atomic operations,
> > the
> > > condition is false; no data race is contained. As I commented
> above,
> > > since the standard lets implementations define the semantics of
> the
> > > access, and a hypothetical implementation can define `#1` as `|
> > > std::atomic_ref<int>(value).load()|` and `#2` as `|
> > > std::atomic_ref<int>(value).store(1)|`; i.e., they're atomic
> > operations,
> > > I don't think the crux condition in [intro.race] p17 is true. So,
> > the
> > > program under this specified semantics shouldn't be UB. How do you
> > > explain this point?
> > >
> > > On Fri, Oct 9, 2026 at 3:39 PM David Brown
> > <david.brown_at_[hidden] <mailto:david.brown_at_[hidden]>
> > > <mailto:david.brown_at_[hidden]
> > <mailto:david.brown_at_[hidden]>>> wrote:
> > >
> > >
> > >
> > > On 09/10/2026 09:16, jim x via Std-Discussion wrote:
> > > > If the implementation defines that accesses to
> > volatile variables
> > > > are atomic, then they are atomic.
> > > >
> > > >
> > > > In this case, does the program contain a data race as per
> > > [intro.race]
> > > > p17? If not, since the behavior depends on how the
> > implementation
> > > define
> > > > the semantics, does that mean the behavior of the program
> > should be
> > > > implementation-defined?
> > > >
> > >
> > > "Implementation-defined" in the standards does not just mean
> > that the
> > > implementation has to treat the construct in some particular
> > way. It
> > > means that the standard gives a range of options of how the
> > > implementation should handle the construct, and that the
> > implementation
> > > must document how it handles them.
> > >
> > > If an implementation's manual says that volatile accesses are
> > atomic
> > > (not unreasonable on single-core non-superscaler targets like
> > most
> > > microcontrollers), then the programmer can rely on the
> volatile
> > > accesses
> > > being atomic. But that does not mean unsynchronised volatile
> > accesses
> > > to the same address in different threads is implementation-
> > defined
> > > behaviour, or should be called such in the C++ standards.
> > >
> > >
> >
>
>
Received on 2026-10-10 01:44:34
