C#如何防止算术溢出损坏类内部状态及编译器重排风险?
结论先行
三种实现中Increment存在字段部分更新的状态损坏风险,Increment2和Increment3都是符合要求的安全实现,你担心的异常场景都不会发生。
1. 关于Increment2的指令重排风险
C# 语言规范明确规定:单线程场景下,程序的执行顺序必须保证和代码编写逻辑的可观测行为完全一致。你编写的逻辑是先在checked块内完成两个加法计算、得到x2和y2两个临时变量之后,才对类的字段x、y赋值。任何合法的编译器/JIT优化都不能把字段赋值操作提前到checked块执行完成前,因为这种重排会直接改变程序的可观测结果(如果y+dy溢出抛异常,x已经被修改的结果和代码本身的逻辑完全不符),这类优化是被规范严格禁止的,完全不用担心。
2. 开启全局算术溢出检查的影响
开启项目的<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>选项,和手动给代码块加checked关键字,唯一的区别只是把整个项目的默认算术计算上下文从unchecked改为checked,完全不会影响执行顺序的相关规则。就指令重排这个问题而言,两种写法没有任何差异。
3. 元组解构版本Increment3的安全性
Increment3和Increment2的安全性完全等价,不存在安全性更低的问题。C#中元组构造的规则是:先计算完括号内所有元素的表达式值,再完成元组的实例化,最后才会执行解构赋值操作。你给整个元组构造表达式加了checked修饰,只要两个加法操作中有任意一个触发溢出,就会直接抛出OverflowException,根本不会走到后续的字段赋值步骤,不会出现部分更新字段的状态损坏问题,写法反而更简洁。
内容的提问来源于stack exchange,提问作者Theodor Zoulias

