-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Full name of submitter (unless configured in github; will be published with the issue): Jim X
[intro.execution] p7 says
Reading an object designated by a volatile glvalue ([basic.lval]), modifying an object, producing an injected declaration ([expr.const]), calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution or translation environment. Evaluation of an expression (or a subexpression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects. When a call to a library I/O function returns or an access through a volatile glvalue is evaluated, the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by the volatile access may not have completed yet.
Consider this example:
#include <thread>
#include <atomic>
#include <stream>
int main(){
std::ofstream outFile("example.txt", std::ios::out | std::ios::trunc);
std::atomic<bool> flag = false;
auto t1 = std::thread([&](){
outFile << "ab"; // #1
flag.store(true, std::memory_order::release); // #2
});
auto t2 = std::thread([&](){
while (!flag.load(std::memory_order::acquire)); // #3
outFile << "cd"; // #4
});
t1.join();
t2.join();
}Because #2 synchronizes with #3, then #1 happens before #4. According to [intro.abstract] p8
The following specify the observable behavior of the program:
- [...]
- Data is delivered to the host environment to be written into files (See also: ISO/IEC 9899:2024, 7.23.3).
So, writing data to files is observable behavior. Are data guaranteed to be delivered to the file example.txt in the order "abcd"? I don't find a relevant rule in both subclauses [intro.races] and [input.output] that specifies the order of delivery of data to files depends on the happen-before relationship.
[intro.races] only states how memory visibility is defined according to happen-before. Both reading an object designated by a volatile glvalue and modifying an object can be categories to side effects on memory locations, [intro.races] does define that. However, the side effects produced by the call to a library I/O function might not be about memory visibility. [input.output] only mentions when a data race happens. [intro.execution] p7 only says that when the calling of a function at #1 returns, the side effect is considered complete. However, it lacks how the completion is related to the expression that is sequenced after it, let alone how the completion is ordered relative to #3 that inter-threaded happens after it.
In the whole standard, there is only one informal note specifying this
intro.races#note-8
[Note 8: Informally, if A strongly happens before B, then A appears to be evaluated before B in all contexts.
— end note]
However, this is an informal note, and I didn't find a formal rule corresponding to this note.
Suggested Resolution
The English meaning of happen before means one thing has done before the other. However, happen-before as the term of the C++ standard, we didn't give it that meaning. IIUC, the intended meaning should be that: if A happens before B, then A will finish before B starts. This meaning is not embodied in the formal rules. We only describe memory visibility in terms of happen-before, but don't for other kinds of side effects or observable behaviors.
We should define how the order of side effects produced by I/O functions is defined by happen-before.