Does re-writing the same value to a memory location count as modifying the memory? (in the context of multi-threading)

194 views Asked by At

(Assume: int x{ 6 } and 2 evaluations write x = 6 at the same time)

--

CPP reference says on Memory model: Threads and data races:

When an evaluation of an expression writes to a memory location and another evaluation reads or modifies the same memory location, the expressions are said to conflict. A program that has two conflicting evaluations has a data race unless:

  • both evaluations execute on the same thread or in the same signal handler, or

  • both conflicting evaluations are atomic operations (see std::atomic), or

  • one of the conflicting evaluations happens-before another (see std::memory_order).

If a data race occurs, the behavior of the program is undefined.

The reference says: "another evaluation modifies"; It does not say "another evaluation writes".

--

C++ standard says on 6.9.2.2 Data races:

  1. Two expression evaluations conflict if one of them modifies a memory location ([intro.memory]) and the other one reads or modifies the same memory location.

--

Does re-writing the same value to a memory location count as modifying the memory?

2

There are 2 answers

4
user17732522 On BEST ANSWER

Does writing the same value to a memory location count as modifying the memory?

Yes, for example simple assignment to a scalar object (which is what a memory location actually is with exceptions for bit fields) is defined to modify the object regardless of the value that the object is being changed to. See [expr.ass]/2:

  1. In simple assignment (=), the object referred to by the left operand is modified ([defns.access]) by replacing its value with the result of the right operand.

You find similar wording for all other expressions that affect the value of a scalar object.


Regarding terminology, the standard usually doesn't use "write", it uses "modify" with the same meaning instead. See e.g. [defns.access].

2
Jan Schultke On

The short answer is that modifying means that any write access to an object takes place. Even if this doesn't change any bits in memory, it does modify the value in the abstract C++ sense.

You can run into data race by modifying memory in the abstract C++ sense, even if this modification doesn't change any bits in memory.

The long answer

To get a clearer picture, refer to the C++11 standard, [intro.multithread] p21:

Note: I'm citing C++11 because this is a question.

The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.

The relevant wording is conflict, defined in [intro.multithread] p4:

Two expression evaluations conflict if one of them modifies ([intro.memory]) a memory location and the other one accesses or modifies the same memory location.

Unfortunately, this is where things get unclear, because "modify" isn't actually defined in the standard; it's not defined in [intro.defs].

The most plausible interpretation

Edit 3470 clarified what accessing means in a number of places. To access means to read or modify a value. The current definition of confict also uses this "read or modify" terminology, in [intro.races] (working draft).

The way that "access" and "read or modify" are used in the standard usually doesn't require values to compare unequal before/after for a modification to take place.

  • you violate strict aliasing if you modify something through the wrong type, even if no bits in memory are changed, and
  • by the same logic, you modify a value even if you don't change any bits in memory, and can run into a data race.

Further thoughts on values and modifications

Also note that in the working draft (although not in C++11), assigning always modifies the value as per [expr.ass] p2 (working draft):

In simple assignment (=), the object referred to by the left operand is modified ([defns.access]) by replacing its value with the result of the right operand.

It's not required that the new value compares unequal to the old value. The object is always modified.

You can change bits without any observable change in value

0 == 0; // guaranteed true, because zero compares equal to zero
int x = 0;
x = 0; // if int is padded, then this might modify padding bits of int
       // even though the values compare equal before and after, bits are changed

You can change values without changing any storage bits

Also note that value typically refers to the state of an object. Two values are distinct, even if they compare equal:

int x; // x has no value, but all of the bits in the storage could be zero here
std::construct_at(&x); // x now has value zero, and while this does change its value
                       // (it was indeterminate before), it might not have any affect
                       // on the bits of its storage

As you can see, changing bits in memory is a separate concept from modifying a value. Assignment always changes the value.