none
static ポインタについて

    質問

  • VS2005,2008のC#のunsafe class内に配置したstatic ポインタ
    unsafe class Class1{
        static Int32 *p;
        static void Main(){
            p=(Int32*)1234;
        }
    }
    こんなコードを書きます。実行するとpには自由に値の出し入れ、*で参照ができます。
    しかしデバッガのウオッチ式では関係のないめちゃくちゃな値が表示されます。
    staticポインタフィールドを関数やプロパティでアクセスするようにすれば見ることは出来ます。ちょっと面倒ですが開発にはそれほど支障ではありませんが出来れば直したいと思います。
    SPも入れているのですが修正されている様子はありませんでした。
    この現象について対処法はないでしょうか?
    2009年7月22日 5:44

回答

  • マネージドコードでネイティブで組んでいた時代のようなコードを書きたい、という理由についてですが、最も大きな動機は仕様上どうしても
    動的メソッド(DynamicMethod)を使いたい
    という理由からです。
    がんばればネイティブCでもソースを生成してコンパイラを起動して、とすればできますが、インプロセス、インメモリ(?)でできるDynamicMethodはどうしても必要な機能と判断したからです。
    初期の段階であれば、その部分(どうしても使いたい部分)だけをマネージコードでライブラリにしてしまい、ネイティブアプリケーションから呼び出すようにする設計もあり得ます。
    使える可能性があるとすれば、C++/CLI や COM でネイティブから呼び出せるようにするクラスライブラリですね。

    ただ、既にある程度、実装が進んでいる状態では取れない選択肢になるので、参考程度に留まります。

    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    • 回答としてマーク 和和和 2009年7月22日 15:02
    2009年7月22日 14:56
    モデレータ

すべての返信

  • なぜ、static でポインタを持っておきたいのでしょうか?
    IntPtr 型で保持して、必要な部分のみポインタにするのでは駄目なんでしょうか?
    要するに、C# で扱うことの少ないポインタは、ごく狭い範囲にしませんか?という提案になります。



    ところで、以前から感じていた疑問ですが、なぜ、マネージコードでネイティブで組んでいた時代のようなコードを書きたい、あるいは書かなければならないのでしょうか?
    今回のコードもサンプルコードとして簡略化しているだけで、実際はかなり複雑で、ポインタでしかできないことなのでしょうか?

    マネージコードはメモリとか、アドレスとか見えにくくなっていると思います。
    それを逆行しなければならないという実装はなにか、おかしな点があるのではないかと感じています。


    # 本筋のデバッガで正しく表示されない現象は再現を確認したものの、不具合報告は見つけられていません。


    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    2009年7月22日 14:13
    モデレータ
  • 返信ありがとうございます。
    static IntPtrでやる件についてですが*ポインタと比較して代入させる場合の速度が遅いことがわかりました。
    原因として、32ビットの場合0x80000000-0xFFFFFFFFの値を代入すると例外が発生します。つまり値の範囲のチェックがなされていると考えられます。
    また参照したい型が確定している時はキャストするよりはそのまま使えたほうが自然だからです。
    マネージドコードでネイティブで組んでいた時代のようなコードを書きたい、という理由についてですが、最も大きな動機は仕様上どうしても
    動的メソッド(DynamicMethod)を使いたい
    という理由からです。
    がんばればネイティブCでもソースを生成してコンパイラを起動して、とすればできますが、インプロセス、インメモリ(?)でできるDynamicMethodはどうしても必要な機能と判断したからです。
    他にもデバッガで使える機能が多くテストしやすい、便利なライブラリがたくさんあるという弱い動機もあります。
    2009年7月22日 14:22
  • static IntPtrでやる件についてですが*ポインタと比較して代入させる場合の速度が遅いことがわかりました。
    原因として、32ビットの場合0x80000000-0xFFFFFFFFの値を代入すると例外が発生します。つまり値の範囲のチェックがなされていると考えられます。
    また参照したい型が確定している時はキャストするよりはそのまま使えたほうが自然だからです。

    なるほど、範囲チェックはあるかもしれません。(未確認)
    マネージコードは基本的に安全な方向に倒そうという思想があるので、配列でも範囲チェックがあるように、似たようなチェックはあると思われます。
    ただ、この速度差を気にするコードで、さらに static といったように広く参照できなければならないという設計は危険な雰囲気を感じます。

    また、ポインタを使うことが「自然」のような表現とお見受けしますが、C# においてポインタを日常的に使うコードは 自然ではない と、少なくとも私は感じています。
    コアなロジックのごく一部だけを、unsafe コードにするのはテクニックとしてありだと思いますが、全体的に unsafe なコードはマネージコードのプログラミングスタイルとしては、外れた道だと思います。

    マイノリティとなってしまうと、思いもよらぬ不具合にぶつかり、さらに既知の問題ではないという事態に遭遇するかもしれません。
    今回の不具合は不便であるといったところに留まりますが、今後も何らかのリスクはあると思います。


    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。

    2009年7月22日 14:50
    モデレータ
  • マネージドコードでネイティブで組んでいた時代のようなコードを書きたい、という理由についてですが、最も大きな動機は仕様上どうしても
    動的メソッド(DynamicMethod)を使いたい
    という理由からです。
    がんばればネイティブCでもソースを生成してコンパイラを起動して、とすればできますが、インプロセス、インメモリ(?)でできるDynamicMethodはどうしても必要な機能と判断したからです。
    初期の段階であれば、その部分(どうしても使いたい部分)だけをマネージコードでライブラリにしてしまい、ネイティブアプリケーションから呼び出すようにする設計もあり得ます。
    使える可能性があるとすれば、C++/CLI や COM でネイティブから呼び出せるようにするクラスライブラリですね。

    ただ、既にある程度、実装が進んでいる状態では取れない選択肢になるので、参考程度に留まります。

    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    • 回答としてマーク 和和和 2009年7月22日 15:02
    2009年7月22日 14:56
    モデレータ
  • 使える可能性があるとすれば、C++/CLI や COM でネイティブから呼び出せるようにするクラスライブラリですね。

    ただ、既にある程度、実装が進んでいる状態では取れない選択肢になるので、参考程度に留まります。

    書き始めたのはVisual Studio 2003のころのなんでManaged C++は非常に使いにくかったです。
    今ならVC++2005,2008はかなり使いやすいので選択肢に入っていまがそれなりに実装が進んだこと、コード補完がC#,VB.NETほどうまく働かないこと、linqが使えないことで色々悩みましたがC#でやることになりました。
    2009年7月22日 15:02
  • オーバーフローとアンダーフローのチェックをされるのが嫌でしたらコンパイルオプションで/checked-か、代入する箇所をuncheckedで囲めばいいのではないでしょうか?
    エラーを出さないだけでチェックはやるかもしれないので速度が早くなるかはわかりませんが。

    #デバッガでおかしな値になる現象の再現方法がわからない…
    2009年7月22日 15:24
  • checkedオプションについてです。
    してもしなくてもオーバーフローチェックがされませんでした。
    もしかしてVS2008はIntPtrのオーバーフローはない?のかもしれません。
    検証不足なのか条件が足りないのかはわかりません。

    static ポインタ不具合再現方法ですが
    using System;
    using System.Collections.Generic;
    using System.Text;
    
    public unsafe class Class1 {
    	public static Int32* static_p;
    	static void Main() {
    		Int32 a;
    		static_p=&a;
    		Int32* auto_p=&a;
    	}
    }
    

    でstatic_pをウオッチすると再現できます。
    auto_pは正しく表示されます。
    2009年7月22日 15:41
  • 私の環境では値は表示されずに「含んでいるクラスについての情報が利用できないため、フィールド 'static_p' の値を収集することはできません。」と表示されているんですよ。

    むちゃくちゃな値というのは、このソースではスタック領域のアドレスを指しているからおかしいんじゃないかと思うんですが
    以下のソースを実行すると普通に違う値を表示しますし。
    using System;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            unsafe static void Main(string[] args)
            {
                for (int n = 0; n < 3; n++)
                {
                    int i1;
                    int i2;
    
                    Class1.TestN(n);
                    i1 = *Class1.p;
                    i2 = Class1.PropertyP;
                    if (i1 != i2 || i2 != 1234)
                    {
                        Console.WriteLine(string.Format("ポインタの先が違う値だよ {0}:{1}\t{2}" , n , i1 , i2));
                    }
                }
                Console.ReadLine();
            }
            
        }
    
        unsafe class Class1
        {
            public static Int32* p;
    
            public static Int32 PropertyP
            {
                get { return *p; }
            }
    
            public static void Test1()
            {
                Int32 i;
                i = 1234;
                p = (Int32*)&i;
             }
    
            public static void TestN(int rec)
            {
                if (rec > 0)
                {
                    TestN(rec - 1);
                    return;
                }
                else
                {
                    Test1();
                    TestX();
                }
            }
    
            public static void TestX()
            {
                Int32 j;
                j = 3210;
            }
        }
    }

    2009年7月22日 23:05
  • すいませんgekkaさんのところと同じく
    「含んでいるクラスについての情報が利用できないため、フィールド 'static_p' の値を収集することはできません。」
    と表示されます。
    むちゃくちゃな値が表示されるソースはコードが多く原因がわかりません。それがスタック上の変数のポインタでもAPIが返したポインタでも無関係と思われる値がデバッガに表示されます。
    その違いの原因は今だわかりません。
    2009年7月23日 0:26
  • むちゃくちゃな値というのは、このソースではスタック領域のアドレスを指しているからおかしいんじゃないかと思うんですが
    本質はそこじゃないと思っています。
    例えば、下記のようにアンマネージメモリを明示的に確保し、そのポインタをクラスの static ポインタ変数に持たせると、デバッガでツールヒントを表示すると、正しいアドレスを表示できません。
    (下記のコードは別途、private static int* s_p; と定義されていることを前提とします)

    IntPtr p = Marshal.AllocCoTaskMem(100);
    s_p = (int*)p.ToPointer();
    *s_p = 1234;
    MessageBox.Show((*s_p).ToString());
    Marshal.FreeCoTaskMem(p);
    私は、デバッガの不具合であると判断しています。
    修正希望をフィードバックとして投げるのは良いと思いますが、実装スタイルとして今のやり方は怖いなと感じています。
    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    2009年7月23日 14:27
    モデレータ
  • むちゃくちゃな値というのは、このソースではスタック領域のアドレスを指しているからおかしいんじゃないかと思うんですが
    本質はそこじゃないと思っています。
    むちゃくちゃな値を表示させるのを再現するつもりで私がサンプルに書いてたソースでの状態ことです。
    (わざとスタック領域のアドレスを指しておかしな値となるサンプルになっています)
    混乱させるような書き方をしてしまい申し訳ありませんでした。


    #いまだ「含んでいるクラスについて~」しか表示できないのでこれ以上の調査はできそうにないです。
    2009年7月23日 15:45
  • #いまだ「含んでいるクラスについて~」しか表示できないのでこれ以上の調査はできそうにないです。
    新規プロジェクトから少しコードを入れてみてどうなるかを見てみました。

    コンソールアプリケーションプロジェクトでは確かに、「含んでいるクラス~」といったメッセージになりますね。
    Windowsフォームアプリケーションプロジェクトで試すと、デバッガでのアドレスの不一致を引き起こせるようです。
    (あくまでデバッガ上だけです。コードで比較しても、アドレスは一致している動きになるはずです)
    この2プロジェクトの差のどのあたりが効いているかは調べていません。

    http://azulean.spaces.live.com/blog/cns!9E1932AF4BE9E15D!204.entry
     → http://azulea.wordpress.com/2009/07/24/static-%e3%83%9d%e3%82%a4%e3%83%b3%e3%82%bf%e3%81%ae%e3%82%a6%e3%82%a9%e3%83%83%e3%83%81%e6%a9%9f%e8%83%bd%e3%81%a7%e3%81%ae%e6%8c%99%e5%8b%95%e3%81%ae%e9%81%95%e3%81%84/
    上記の記事の画像中のウォッチ部分で挙動の差を見て取れるかと思います。
    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    2009年7月23日 16:29
    モデレータ
  • コンソールアプリでおかしなアドレスを表示することが出来るようになりました。

    staticな単純型以外(クラス,構造体,配列)のフィールドがあるとstaticポインタ変数のアドレスがウォッチできるようです。
    無いと「含んでいるクラスについて~」になります。
    WindowsフォームアプリケーションではResource.Designer.csやSetting.Designer.csがあり、そのなかでstaticクラスフィールドを宣言しているのでウォッチできるようになります。
    デバッガがおかしいのかPDB情報がおかしいのかまではわかりませんが。

    おかしなアドレスに実際にどんな値が入っているのかメモリを覗いてみると、常に00 0a 83 6aで始まる メモリを指してます。
    EnhancedMetaFileやGDIといった文字が見えたり…なんらかの管理情報っぽい領域を参照しているような。

    2009/07/24 20:00 訂正
    別のPCで試したらメモリの値は別の値00 0a 33 79でした。同じ文字が見えているので同じ情報をみているとは思います。
    • 編集済み gekkaMVP 2009年7月24日 11:10 間違っていた情報の訂正
    2009年7月24日 3:58
  • デバッガの?バグぽい印象なので、Connect やサポートにタレこんでみてはいかがでしょう?

    2009年7月24日 9:33
    モデレータ