C++ Logo

std-discussion

Advanced search

Re: Is this program undefined-behavior or implementation-defined behavior?

From: David Brown <david.brown_at_[hidden]>
Date: Fri, 9 Oct 2026 12:47:01 +0200
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-09 10:47:06