Author here - Apologies, I'll work on making that better. It could perhaps do with an intermediate step where the expression is broken down into a subexpression tree.
You know what might make it a bit clearer? Showing the assembly code that is associated with each of the boxes. It's hard understanding what each box represents, no-one outside of compiler developers has the right vocabulary for descriptions to work. Something concrete, such as an x86 representation of each boxes actions would make things much clearer.
Assembly code would fail to demonstrate the point. The graphs are meant to demonstrate that, temporally speaking, the basic actions being performed by the abstract machine are partially ordered. A consequence of these actions being temporally partially ordered is that two individual actions may be temporally unordered, or "unsequenced." The undefined behaviour comes, roughly speaking, when a "write" action is unsequenced with a "read" or "write" action on the same memory location. This scenario corresponds graphically to a red box and a red/blue box unconnected by any single-direction chain of arrows.
Anyway, the problem with using assembly is that it imposes an artificial temporal total ordering on the actions being performed. Under temporal total ordering, the possibility of unsequenced actions disappears, and the message is lost.
>no-one outside of compiler developers has the right vocabulary for descriptions to work
There's nothing here specific to implementing compilers. There are a few uses of standardese and graph theory, but they're relatively basic concepts that are important to familiarize yourself with if you want to use tools like C++ effectively.
The good news: there is a simple rule you can use instead of understanding all this. The simple rule? Write simple code.
Don't write multiple assignments in a single statement. Don't overuse the ternary operator (?:). Don't overdo it with assignment inside larger statements like comparisons. These "don'ts" will make your code clearer to humans and you are much, much less likely to wind up with holes in your feet.
Author here - absolutely! This is only for those with an interest in the details of C++ evaluation. Considering the number of times I see these expressions in a purely theoretical context, I assume many people are at least intrigued.
You can have values that don't belong to objects. I consider there to be three types of value computations:
- The value computations of operators, like 5 + 6. These don't use the values of objects. They just use the values of their operands. The operands may be subexpressions that denote objects (like 5 + x), but it is the value computation of those subexpressions that uses the object's value, not the value computation of the operator.
- The value computations of expressions denoting objects where the expression is being used as an lvalue. These also don't use the values of objects - they only care about the object's storage location. I give an example in the article, which is the left operand of =.
- The value computation of expressions denoting objects where the expression is being used as an rvalue. This means lvalue-to-rvalue conversion is performed, which is conceptually "reading an object's value out of memory". This, therefore, uses the value of the object.
9 comments
[ 4.3 ms ] story [ 30.1 ms ] threadAnyway, the problem with using assembly is that it imposes an artificial temporal total ordering on the actions being performed. Under temporal total ordering, the possibility of unsequenced actions disappears, and the message is lost.
>no-one outside of compiler developers has the right vocabulary for descriptions to work
There's nothing here specific to implementing compilers. There are a few uses of standardese and graph theory, but they're relatively basic concepts that are important to familiarize yourself with if you want to use tools like C++ effectively.
Don't write multiple assignments in a single statement. Don't overuse the ternary operator (?:). Don't overdo it with assignment inside larger statements like comparisons. These "don'ts" will make your code clearer to humans and you are much, much less likely to wind up with holes in your feet.
I don't quite understand the difference. Don't all value computations use the values of objects?
- The value computations of operators, like 5 + 6. These don't use the values of objects. They just use the values of their operands. The operands may be subexpressions that denote objects (like 5 + x), but it is the value computation of those subexpressions that uses the object's value, not the value computation of the operator.
- The value computations of expressions denoting objects where the expression is being used as an lvalue. These also don't use the values of objects - they only care about the object's storage location. I give an example in the article, which is the left operand of =.
- The value computation of expressions denoting objects where the expression is being used as an rvalue. This means lvalue-to-rvalue conversion is performed, which is conceptually "reading an object's value out of memory". This, therefore, uses the value of the object.
Objects correspond to locations in memory.
5 + 6 has operands which are rvalues (and thus not objects because you can't take their address).
if x is an int, then in the context of the expr 5+x, it is undergoing lvalue-to-rvalue conversion
Did I get that right?