I was playing around with my intcode computer implementation (from advent of code 2019) and found that when I implemented the switch (to select which operation to execute) it was taking the wrong value.
The following code demonstrates this.
InstructionPointer
had a value of 2
, opcode
had the value of 6
which means that OpcodeJumpIfFalse
will be used. Function Jif()
is called just fine, and it returns a value, in my case it was returning 0
. Jif()
also modified the value of InstructionPointer
, changing its value to 9
. The InstructionPointer
would the be increased by 0
(return value of Jif()
) and I would expect its value to be 9
, but its value would go back to being 2
.
InstructionPointer += opcode switch
{
OpcodeAdd => Add(),
OpcodeMultiply => Mul(),
OpcodeInput => Inp(),
OpcodeOutput => Out(),
OpcodeJumpIfTrue => Jit(),
OpcodeJumpIfFalse => Jif(),
OpcodeLessThan => Let(),
OpcodeEquals => Equ(),
OpcodeAdjustRelativeBase => Arb(),
OpcodeHalt => End(),
_ => throw new ArgumentOutOfRangeException()
};
Minimal example which shows same behavior:
int j = 2;
int i = 1;
int A()
{
j = 10;
return 0;
}
j += i switch
{
1 => A(),
_ => throw new Exception()
};
Console.WriteLine(j);
I did notice that Resharper (in the minimal example) is telling me the assignment is unused in A()
.
My question is, why does this happen? Is the value of j
"captured" before the switch? Is this expected behavior?
For now, I have changed my code to use a temporary variable, which resolves the issue, but I would still like to know what is going on here.
Remember that the compound assignment operator a += b
is the same as a = a + b
(except that a
is evaluated only once), see §7.17.2 of the spec.
Here's a slightly simpler example which doesn't use a switch, and has the same effect:
int j = 2;
int A()
{
j = 10;
return 0;
}
j = j + A();
If you don't want to think in terms of local functions, you can also write this as a class:
class C
{
private int j;
public void Test()
{
j = 2;
j = j + A();
}
private int A()
{
j = 10;
return 0;
}
}
The compiler will:
j + A()
:
j
, which is 2
, onto the stackA()
, which sets j
to 10
and returns 0
A()
, which is 0
, onto the stack2 + 0
j
If you write the assignment the other way around, as j = A() + j
, then its final value is 10
. If you follow the same sequence of steps as above, you'll see why.
This general approach -- changing a variable as a side-effect of an expression which also changes that variable -- is a bad idea. It leads to very hard-to-understand code.