none
PowerShellのスクリプト変数のCopyOnWriteバグ? RRS feed

  • 質問

  • 次のソースを実行する。

    $gv=9686

    $funcs = @(
    {
        echo "GlobalOnRead"
        $lv = $gv
        Write-Output $lv
    }
    ,
    {
        echo "CopyOnWrite"
        $lv = $gv
        $lv += 5050 - 9686
        Write-Output $lv
    }
    ,
    {
        echo "LocalOnWrite"
        $lv = $gv
        $gv += 5050 - 9686
        Write-Output ([String]$lv + ":" + $gv)
    }
    )

    for($i = 0; $i -lt $funcs.length; $i++)
    {
        Write-Output $funcs[$i].invoke()
    }

    結果

    GlobalOnRead
    9686
    CopyOnWrite
    5050
    LocalOnWrite
    9686:-4636

    本来は次になるべきでは

    GlobalOnRead
    9686
    CopyOnWrite
    5050
    LocalOnWrite
    9686:5050

    原因はCopyOnWriteの実装に誤りがあり、ローカル変数領域を確保後に、グローバル変数をコピーしていないのではないかと思います。

    2016年12月21日 21:37

回答

  • コメントを追加します。

    上記の動作はPowerShell 3.0での仕様変更によるものでした。

    リリースノートに以下の変更が明記されていました。

    Read/Modify/Write operators no longer use dynamic scoping for the Read operation. Also, compound equality operators (including +=, -=, *=, %=, ++, --) do not use dynamic scoping.  The variable is always in the current scope.  

    コード例としては、

    https://github.com/nightroman/PowerShellTraps/tree/master/Basic/Compound-assignment-operators

    の方のサンプルを見ていただくとわかりやすいかと思います。

    2016年12月22日 3:37

すべての返信

  • おはようございます。

    PowerShellのバージョンによってはこの挙動がちょっと異なる様で、PowerShell 2.0だと期待した動作をしますね。

    PowerShell 3.0から動作が変わっている様ですが、これが仕様変更なのかスコープのバグなのかはちょっとわかりません。

    現状、新しいPowerShellでは最後のLocalOnWriteのスクリプトブロックについて、

    $gv += 5050 - 9686

    の部分はスコープを明示していないため、ローカルスコープ

    $gv += 5050 - 9686
    # ↓
    $local:gv += 5050 - 9686
    # ↓
    $local:gv = $local:gv + 5050 - 9686

    として扱われています。

    このため-4636を返してます。

    なお、

    $lv = $gv

    については、この時点でローカルな$gvは未定義なため、上位スコープの変数を参照して9686の値を取得しています。

    期待する動作をさせるためには、

    $gv = $gv + 5050 - 9686

    と+=演算子を展開するしかない様です。

    ※バージョンによる挙動の差異を発見したのでコメントを編集しました。





    2016年12月22日 0:31
  • コメントを追加します。

    上記の動作はPowerShell 3.0での仕様変更によるものでした。

    リリースノートに以下の変更が明記されていました。

    Read/Modify/Write operators no longer use dynamic scoping for the Read operation. Also, compound equality operators (including +=, -=, *=, %=, ++, --) do not use dynamic scoping.  The variable is always in the current scope.  

    コード例としては、

    https://github.com/nightroman/PowerShellTraps/tree/master/Basic/Compound-assignment-operators

    の方のサンプルを見ていただくとわかりやすいかと思います。

    2016年12月22日 3:37
  • 返信ありがとうございます

    Test-2.ps1 を見る限り、動作が安定していないようですね。

    デフォルトをグローバル変数の値とするか、未定義のためエラーとするかしたほうがバグが少ないように思いますね。

    2016年12月25日 13:03