C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[5]: クラスや構造体のnewをする前のインスタンスフィールドの値


(過去ログ 144 を表示中)

[トピック内 10 記事 (1 - 10 表示)]  << 0 >>

■84240 / inTopicNo.1)  クラスや構造体のnewをする前のインスタンスフィールドの値
  
□投稿者/ Tomo (9回)-(2017/06/02(Fri) 20:37:54)

分類:[.NET 全般] 

例えば下記コードがあったとします

例えば、下記のようなクラスがあったとします。

public class Sample
{
public int i;
public string s;
public bool b;
}


上記コードをどこかで利用する時
Sample sample = new Sample();
sample.i = 100;
といった感じで、利用すると思うのですが

これはnewをした時にフィールドが初期化され
Sampleクラスの
i に 0
s に null
b に false
が入るわけなんですが

newをする前は各種フィールドにどのような
値が格納されているのでしょうか?
nullでもない、何か別の値が格納されていたりするのでしょうか?

また、構造体の場合、newをする前は
インスタンスフィールドにどのような値が格納されるのでしょうか?










引用返信 編集キー/
■84241 / inTopicNo.2)  Re[1]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ Hongliang (548回)-(2017/06/02(Fri) 21:08:14)
何か値が格納されているとして、その値に触ることができるタイミングは存在しないと思いますが、この質問に至った背景を説明いただけるでしょうか。

ちなみに構造体は配列やフィールドなどでnewを経由せずともインスタンスが構築されますが、このときは構造体の全フィールドが0/nullになります。
引用返信 編集キー/
■84242 / inTopicNo.3)  Re[1]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ 魔界の仮面弁士 (1308回)-(2017/06/03(Sat) 09:47:51)
No84240 (Tomo さん) に返信
> newをする前は各種フィールドにどのような
> 値が格納されているのでしょうか?
> nullでもない、何か別の値が格納されていたりするのでしょうか?

ECMA-335 を斜め読みしてみましたが、
不確定値になりえるのはローカル変数ぐらいのようですね。
フィールド変数の場合は、default(T) で初期化されるか、
初期値が書き込まれるかのいずれかです。


そのローカル変数についても、C# のコンパイラは init フラグを付与するため、
リフレクション等で動的生成でもしない限り、基本的にはゼロが書き込まれた状態になります。

フィールド変数については、たとえば
 public int i = 123;
というインスタンスフィールドだとしても、123 がセットされる前に
0 がセットされた状態で受け渡されるようです。これは newobj 命令(0x73)の仕様だそうで。

構造体に対して用いられる initobj 命令(0xfe 0x15)も同様。

static int などの静的フィールドの場合は、そのクラスに静的コンストラクタが
明示的に実装されているか、それとも beforefieldinit フラグによるものかで
初期化のタイミングが異なる程度で、これもやはりゼロが書き込まれた状態になるようです。


一応、こんな感じの IL を書いて試してみました。




.assembly extern 'mscorlib' {}
.assembly 'Orator' {}

// === Sample クラスの宣言 ===
// このクラスには static なメンバーが無いため、
// BeforeFieldInit フラグ(0x00100000)の有無は特に意味を持ちません
.class public sequential autochar /* beforefieldinit */ 'Sample'
{
  // フィールド宣言と FieldInit メタデータの指定
  // i と k には HasDefault フラグ(0x8000)で既定値も付与してみました。
  // といっても、これは本来、literal 定数フィールドや既定値パラメータ用なので、
  // ただのフィールドに指定しても実際には使われないのですが。
  .field public int32 'i' = int8(-1)
  .field public int32 'j'
  .field public int32 'k' = int8(23)
  .field public initonly int32 'l'

  // Sample クラスのコンストラクタ
  .method public hidebysig specialname rtspecialname
    instance void '.ctor'() cil managed 
  {
    // 変数宣言
    .locals init ( int32 'i', int32 'j', int32 'k' , int32 'l' )

    // フィールドに値を書き込む前に、各フィールドの値を変数に退避しておきます
    ldarg.0 ldfld int32 'Sample'::'i' stloc.0
    ldarg.0 ldfld int32 'Sample'::'j' stloc.1
    ldarg.0 ldfld int32 'Sample'::'k' stloc.2
    ldarg.0 ldfld int32 'Sample'::'l' stloc.3

    // this.i に初期値 -1 を書き込み
    ldarg.0 ldc.i4.m1 stfld int32 'Sample'::'i'

    // this.j に初期値 7 を書き込み
    ldarg.0 ldc.i4.7  stfld int32 'Sample'::'j'

    // this.k には書き込まない
    nop

    // this.l に -123 を書き込み
    ldarg.0 ldc.i4.s -123 stfld int32 'Sample'::'l'

    // 書き込む前のフィールド値と書き込み後のフィールド値を
    // Console.WriteLine で出力します
    ldstr "** before: i=({0} => {1})"
    ldloc.0 box int32
    ldarg.0 ldfld int32 'Sample'::'i' box int32
    call void [mscorlib]'System.Console'::'Write'(string, object, object)
    ldstr ", j=({0} => {1})"
    ldloc.1 box int32
    ldarg.0 ldfld int32 'Sample'::'j' box int32
    call void [mscorlib]'System.Console'::'Write'(string, object, object)
    ldstr ", k=({0} => {1})"
    ldloc.2 box int32
    ldarg.0 ldfld int32 'Sample'::'k' box int32
    call void [mscorlib]'System.Console'::'Write'(string, object, object)
    ldstr ", l=({0} => {1})"
    ldloc.3 box int32
    ldarg.0 ldfld int32 'Sample'::'l' box int32
    call void [mscorlib]'System.Console'::'WriteLine'(string, object, object)

    ret
  }
}

// === グローバル 静的 フィールド ===
.field public static float64 'globalValue'


// === エントリポイント ===
.method static public void 'Main'() cil managed
{
  .entrypoint

  // 無名変数宣言
  // C# コンパイラは、ゼロクリアされるよう init フラグを必ず付与しますが、
  // ここではあえて init を外しています。このため、この変数の初期値は不定となり
  // 実行されるたびに異なる値になる可能性があります
  .locals /* init */ ( uint64, uint32, bool, int64, class Sample )

  // 未初期化のグローバル静的フィールド globalValue (double 型)を表示
  ldstr "static Double globalField={0};"
  ldsfld float64 'globalValue' box float64
  call void [mscorlib]'System.Console'::'WriteLine'(string, object)

  // 未初期化の 第0変数(ulong型)を表示
  ldstr "UInt64 v0={0};"
  ldloc.0 box uint64
  call void [mscorlib]'System.Console'::'WriteLine'(string, object)

  // 未初期化の 第1変数(uint型)を表示
  ldstr "UInt32 v1={0};"
  ldloc.1 box uint32
  call void [mscorlib]'System.Console'::'WriteLine'(string, object)

  // 未初期化の 第2変数(bool型)を表示
  ldstr "Boolean v2={0}"
  ldloc.2 box bool
  call void [mscorlib]'System.Console'::'WriteLine'(string, object)

  // 未初期化の 第3変数(long型)を表示
  ldstr "Int64 v3={0};"
  ldloc.s 3 box int64
  call void [mscorlib]'System.Console'::'WriteLine'(string, object)


  // 第4変数(Sample型)は未初期化だと NullReferenceException になるので
  // まずは new Sample() してインスタンス化します
  newobj instance void 'Sample'::'.ctor'() stloc.s 4

  // 第4変数(Sample)の フィールド i, j を表示
  ldstr "** after: i={0}, j={1}"
  ldloc.s 4 ldfld int32 'Sample'::'i' box int32
  ldloc.s 4 ldfld int32 'Sample'::'j' box int32
  call void [mscorlib]'System.Console'::'Write'(string, object, object)
  ldstr ", k={0}, l={1}"
  ldloc.s 4 ldfld int32 'Sample'::'k' box int32
  ldloc.s 4 ldfld int32 'Sample'::'l' box int32
  call void [mscorlib]'System.Console'::'WriteLine'(string, object, object)

  ldstr "*** done ***"
  call void [mscorlib]'System.Console'::'WriteLine'(string)

  ret
}

引用返信 編集キー/
■84245 / inTopicNo.4)  Re[2]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ ぶなっぷ (108回)-(2017/06/05(Mon) 11:09:55)
> newをする前は各種フィールドにどのような
> 値が格納されているのでしょうか?

うーん、どう答えるべきか難しい質問ですが。。。

まず、「newをする前」というのは、まさにクラスオブジェクト存在しない
状態なので、「何もない」状態です。
何もないのだから、各フィールドは「存在しない」としか言えないです。

そんなわけで、
> i に 0
> s に null
> b に false
というのは、正確には「newをした直後」の話になります。

そして、「newをした直後」に何が入るかですが、
他の人がおっしゃっている通りです。

平たく言うなら、
ユーザ定義型(struct, class)なら、コンストラクタによって初期化された値、
組み込み型やコンストラクタによって初期化されなかったメンバ変数は
コンパイラの規定値となります。
(例) init型の既定値なら、default(int)で取得可能

引用返信 編集キー/
■84246 / inTopicNo.5)  Re[2]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ furu (99回)-(2017/06/05(Mon) 12:07:42)
2017/06/05(Mon) 12:09:11 編集(投稿者)
No84242 (魔界の仮面弁士 さん) に返信
> フィールド変数については、たとえば
>  public int i = 123;
> というインスタンスフィールドだとしても、123 がセットされる前に
> 0 がセットされた状態で受け渡されるようです。これは newobj 命令(0x73)の仕様だそうで。

セットされる前の0が見られる例です。

    static class A
    {
        public static int a = 1 + B.b;
    }
    static class B
    {
        public static int b = 2 + A.a;
    }


A.を先に使用すると
  A.a → 3
  B.b → 2
B.を先に使用すると
  A.a → 1
  B.b → 3

量子化学の不確定性原理みたいですが
不具合の元なので、ビルドでエラーにしてほしいところです。

引用返信 編集キー/
■84247 / inTopicNo.6)  Re[3]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ 774RR (530回)-(2017/06/05(Mon) 13:03:28)
new する前というのは構造体でもクラスでもない、「ただメモリがあるだけ」なのでゴミ、つまり不定値。
new することによって「単なるメモリ」がクラスオブジェクトになるわけだけど、
C# では(少なくとも C# ソースレベルでは) new の実行真っ最中の
単なるメモリからオブジェクトになりかけな何か(まだオブジェクトじゃない)には触れないようになっている。

なので、気にする必要は無いというか、気にしようとしても扱うことができないというか。

引用返信 編集キー/
■84253 / inTopicNo.7)  Re[2]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ 魔界の仮面弁士 (1309回)-(2017/06/05(Mon) 16:21:05)
質問の意図が分からないので、元質問者(Tomo さん)の再登場待ちかな…。


No84242 (魔界の仮面弁士) に追記
> 不確定値になりえるのはローカル変数ぐらいのようですね。
> そのローカル変数についても、C# のコンパイラは init フラグを付与するため、
> リフレクション等で動的生成でもしない限り、

ローカル変数で初期値がゼロとならないパターンしては、
System.Reflection.Emit.DynamicMethod クラスでメソッドを動的作成する際に
InitLocals プロパティ に false をセットしていた場合など。


で、COM Interop や P/Invoke はまた別の話。
COM オブジェクトなどのように、C# で作成されたもので無いオブジェクトにおいては、
public メンバーが未初期化な non-zero を返してくる可能性もありえなくはないかな…。



No84246 (furu さん) に返信
>> public int i = 123;
>>というインスタンスフィールドだとしても、123 がセットされる前に
>>0 がセットされた状態で受け渡されるようです。
> セットされる前の0が見られる例です。

この例は、インスタンスフィールドではなく静的フィールドの例ですね。
ECMA-335 I.8.9.5 によれば:

・静的フィールドに対しては、初期値として常に 0 がセットされる
・静的フィールドに初めてアクセスする前に、
 静的コンストラクタが実行されることが保証されている。
・静的メソッド、仮想メソッド、インスタンスコンストラクタの呼び出し前に
 静的コンストラクタが呼び出されるのは、beforefieldinit で
 マークされていない型に限られる。

と定義されているようです。beforefieldinit については下記参照。

https://msdn.microsoft.com/ja-jp/library/ms182275.aspx
https://msdn.microsoft.com/ja-jp/library/ms182346.aspx
http://csharper.blog57.fc2.com/blog-category-3.html
http://d.hatena.ne.jp/akiramei/20061028/p2
引用返信 編集キー/
■84255 / inTopicNo.8)  Re[3]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ furu (100回)-(2017/06/05(Mon) 17:30:49)
No84253 (魔界の仮面弁士 さん) に返信
> この例は、インスタンスフィールドではなく静的フィールドの例ですね。

魔界の仮面弁士さん、いつもながら恐れ入ります。

インスタンスフィールドで頑張ってみましたが
System.TypeInitializationExceptionで落ちました。
new実行中にインスタンスフィールド見たのがバレたようです。

    static A a = new A();
    class A
    {
        public int a = B.b + 7;
    }
    static class B
    {
        public static int b;
        static B()
        {
            b = 1 + a.a;
        }
    }

引用返信 編集キー/
■84259 / inTopicNo.9)  Re[4]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ 魔界の仮面弁士 (1310回)-(2017/06/05(Mon) 19:32:40)
No84255 (furu さん) に返信
> インスタンスフィールドで頑張ってみましたが

volatile 指定も無しですね。


> System.TypeInitializationExceptionで落ちました。
> new実行中にインスタンスフィールド見たのがバレたようです。

これは静的コンストラクタの中で NullReferenceException が発生したからです。
すなわち、初期値が null(ゼロ)であることの証明と言えるかも。


> static B()
> {
>   b = 1 + a.a;
> }

A をシングルトンとして実装しておくか、あるいは
 b = 1 + ((a == null) ? 0 : a.a);
などとして、null だった場合の処理順を定めておかないと。
引用返信 編集キー/
■84275 / inTopicNo.10)  Re[5]: クラスや構造体のnewをする前のインスタンスフィールドの値
□投稿者/ Tomo (10回)-(2017/06/07(Wed) 09:58:24)
みなさん、回答ありがとうございます
おかげさまで、疑問点は解決できました。
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -