I have a class that has two int
fields x
and y
, and a method Increment
that increments both of these fields by dx
and dy
respectively. I would like to prevent the state of my class to become corrupted by silent arithmetic overflows (which would result to x
or y
or both becoming negative), so I am explicitly incrementing the fields in a checked
block:
class MyClass
{
private int x;
private int y;
public void Increment(int dx, int dy)
{
checked { x += dx; y += dy; }
}
}
This should ensure that in case of arithmetic overflow the caller will receive an OverflowException
, and the state of my class will remain intact. But then I realized that an arithmetic overflow could occur in the increment of y
, after the x
has already successfully incremented, resulting to a different type of state corruption, which is not less disruptive than the first. So I changed the implementation of the Increment
method like this:
public void Increment2(int dx, int dy)
{
int x2, y2;
checked { x2 = x + dx; y2 = y + dy; }
x = x2; y = y2;
}
This seems like a logical solution to the problem, but now I am concerned that the compiler could "optimize" my carefully crafted implementation, and reorder the instructions in a way that would allow the x
assignment to occur before the y + dy
addition, resulting again to state corruption. I would like to ask if, according to the C# specification, this undesirable scenario is possible.
I am also considering removing the checked
keyword, and instead compiling my project with the "Check for arithmetic overflow" option enabled (<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
). Could this make any difference, regarding a possible reordering of the instructions inside the Increment
method?
Update: a more succinct arithmetic-overflow-safe implementation is possible by using tuple deconstruction. Is this version any different (less safe) than the verbose implementation?
public void Increment3(int dx, int dy)
{
(x, y) = checked((x + dx, y + dy));
}
Clarification: The MyClass
is intended to be used in a single-threaded application. Thread-safety is not a concern (I know that it's not thread-safe, but it doesn't matter).
TL;DR; This is perfectly safe to do within a single thread.
The CLI, which implements your code in native machine language, is simply not allowed to reorder instructions in such a fashion as to have a visible side-effect, at least as far as observations from a single thread are concerned. It is forbidden by the specification.
Let's take a look at ECMA-335, the specification for the CLR and CLI, (my bold)
So exceptions must occur in the order specified in the IL code compiled by C#, therefore if an overflow occurs in a
checked
context, the exception must be thrown before any observation of the next instructions. (In anunchecked
context, there is no such guarantee, because there is no exception, however the difference is not observable on a single thread.)Note that this does not mean that the two additions cannot occur before the stores into local variables or before the two overflow checks, because the locals cannot be observed once an exception is thrown. In a fully optimized build, the locals will probably be stored in CPU registers, and wiped in the event of an exception.
The CPU is also free to reorder internally, so long as the same guarantees apply.
There is one exception to all of this, barring the mentioned multi-threading allowance:
However the current Microsoft implementation does not provide such a relaxation option anyway.
As to using the tuple-deconstructing syntax, unfortunately the spec for C# 7 has not been released, but this page on Github indicates that it should also be side-effect free.