論理演算のワナ - 短絡評価
C#を使って、以下のようなプログラムを書いてみます。
ソースコード.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ShortCircuitEvaluation
{
class Program
{
static void Main(string[] args)
{
System.Diagnostics.Debug.WriteLine("Case 1");
if (Program.True() && Program.False())
{
System.Diagnostics.Debug.WriteLine("True() and False() is true");
}
else
{
System.Diagnostics.Debug.WriteLine("True() and False() is false");
}
System.Diagnostics.Debug.WriteLine("Case 2");
if (Program.False() && Program.True())
{
System.Diagnostics.Debug.WriteLine("False() and True() is true");
}
else
{
System.Diagnostics.Debug.WriteLine("False() and True() is false");
}
System.Diagnostics.Debug.WriteLine("Case 3");
if (Program.True() || Program.False())
{
System.Diagnostics.Debug.WriteLine("True() or False() is true");
}
else
{
System.Diagnostics.Debug.WriteLine("True() or False() is false");
}
System.Diagnostics.Debug.WriteLine("Case 4");
if (Program.False() || Program.True())
{
System.Diagnostics.Debug.WriteLine("False() or True() is true");
}
else
{
System.Diagnostics.Debug.WriteLine("False() or True() is false");
}
}
static bool True()
{
System.Diagnostics.Debug.WriteLine("exec func True()");
return true;
}
static bool False()
{
System.Diagnostics.Debug.WriteLine("exec func False()");
return false;
}
}
}
これを実行すると、以下のような結果になります。
実行結果.
Case 1
exec func True()
exec func False()
True() and False() is false
Case 2
exec func False()
False() and True() is false
Case 3
exec func True()
True() or False() is true
Case 4
exec func False()
exec func True()
False() or True() is true
いずれの Case も最終結果に間違いはなく、期待した通りに動作しているようです。
ところが、Case 2 と Case 3 ではそれぞれTrue()関数、False()関数が呼び出されていません。
これは、論理演算子「&&」と「||」の短絡評価(Short Circuit Evaluation)によるものです。
短絡評価では、論理演算の左辺値によって結果が定まる場合、右辺値を評価しません。
Case 2 では、左辺値がfalseですので、右辺値に関係なく論理積(and)の結果がfalseに定まります。
そのため、右辺値を評価しない、すなわち、True()関数が呼び出されないのです。
ここでもし、うっかり右辺の関数が必ず呼び出されることを期待してプログラムを書いてしまうと、後で思わぬ結果になってしまう可能性があります。
これが、「 論理演算のワナ」です。
「常に意識していればこんなワナに引っかかることはない」と思われるかもしれませんが、プログラミング言語によって標準の論理演算子が短絡評価を行うか否かが違うため意外と厄介なのです。
複数のプログラミング言語を使っている場合には、特に注意が必要となります。
例えば、Visual Basic (VB) の論理演算子「And」と「Or」は、短絡評価を行いません。
C#を使って、
if(obj != null && obj.Property == xx)...
と記述しているところを、うっかりVBで、
If Not obj Is Nothing And onj.Property = xx Then ...
などと記述してしまうとしょっぱいランタイムエラーに泣かされることになるわけです。
もしVBで短絡評価を行いたい場合は、「AndAlso」と「OrElse」を用います。
逆に、もしC#で短絡評価を行わない場合は、ビット演算子「&」と「|」を用います。
ビット演算子は、bool型に対しては短絡評価を行わない論理演算を実行します。
これは、C言語、C++、Javaなどのプログラミング言語でも同様のようです。
プログラムは思った通りに動くのではなく、書いた通りに動きます。
とすれば、是非とも思った通りに書きたいものですね。