DOBON.NET DOBON.NETプログラミング掲示板過去ログ

StructLayoutを使用した場合のPaddingの埋まり方について

環境/言語:[XP、C#、.NET Framwork2.0]
分類:[.NET]

お世話になっております。

StructLayoutを使用して8byteアラインメントでのPaddingの
埋まり方に関してご教授お願いします。

以下のように記述した場合
@:4byte
A:12byte
B:12byte
となるのですが、期待していたByte数は
@:8byte
A:16byte
B:48byte
です。
ちなみに下記の"SubTest3"のみの場合は16byteとなり、期待していた
Byte数になります。
Pack=8に設定してあるので、8byteで区切られると思っていたのですが、
違うのでしょうか?

アドバイス、ご教授など、宜しくお願いします。

-------------------
【Main】
    static void Main()
    {
        Test1 t1 = new Test1();
        Console.WriteLine("Test1 Size : {0}", Marshal.SizeOf(t1));
        Test2 t2 = new Test2();
        Console.WriteLine("Test2 Size : {0}", Marshal.SizeOf(t2));
        Test3 t3 = new Test3();
        Console.WriteLine("Test3 Size : {0}", Marshal.SizeOf(t3));
    }    
-------------------
@
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public class Test1
{
    private int _x;
    public int X
    {
        get { return _x; }
        set { _x = value; }
    }
}
-------------------
A
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public class Test2
{
    private int _x;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    private char[] _y = new char[6]];
    
    public int X
    {
        get { return _x; }
        set { _x = value; }
    }
    public char[] Y
    {
        get { return _y; }
        set { _y = value; }
    }
}
-------------------
B
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public class Test3
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    private SubTest3[] _arraySubTest;
    
    public Test3t()
    {
        _arraySubTest = new SubTest3[3];
        for (int i = 0; i < 3; i++)
        {
            _arraySubTest[i] = new SubTest3();
        }
    }
    public SubTest3[] ArraySubTest
    {
        get { return _arraySubTest; }
        set { _arraySubTest = value; }
    }
}

[StructLayout(LayoutKind.Sequential, Pack = 8)]
public class SubTest3
{
    private int _x;
    private double _y;
    
    public int X
    {
        get { return _x; }
        set { _x = value; }
    }
    
    public double Y
    {
        get { return _y; }
        set { _y = value; }
    }
}
(1)(2) に関しては、まあそんなもんです。
基本的にその構造体内に存在するメンバの中で一番大きい型に合わせられます。
Pack はその値以上の大きさの型をどうするかって話に関係します。

(3) はまたちょっと違った問題を含みますね。
C# の class は参照型であるというのは今更ですが、参照型だけにマーシャリングにおいてはポインタとして扱われます。それは LayoutKind.Sequential でマークしていようと関係ありません。

と言う事で、前スレッドでも述べたとおり、できる限り事前にきっちり手で計ってマーシャリングなぞに頼らないようにしましょう。
返信ありがとうございます。

> > (3) はまたちょっと違った問題を含みますね。
> C# の class は参照型であるというのは今更ですが、参照型だけにマーシャリングにおいてはポインタとして扱われます。それは LayoutKind.Sequential でマークしていようと関係ありません。
ということは、ネストになっているクラスのサイズを得ることはできないとでしょう
か?

> > と言う事で、前スレッドでも述べたとおり、できる限り事前にきっちり手で計ってマーシャリングなぞに頼らないようにしましょう。
変数の追加等があった場合になるべくPaddingを考慮しなくていいようにしたいと
思っています。
手で計った場合は、修正がはいったクラスのPaddingを見直す必要は必ずでてくると
思っているのですが。。。修正がはいった場合、容易に修正できる方法はありますで
しょうか?

アドバイス、ご教授宜しくお願いします。
2006/11/03(Fri) 02:14:28 編集(投稿者)

■No18086に返信(おかさんの記事)
>>> と言う事で、前スレッドでも述べたとおり、できる限り事前にきっちり手で計ってマーシャリングなぞに頼らないようにしましょう。
> 変数の追加等があった場合になるべくPaddingを考慮しなくていいようにしたいと
> 思っています。
> 手で計った場合は、修正がはいったクラスのPaddingを見直す必要は必ずでてくると
> 思っているのですが。。。修正がはいった場合、容易に修正できる方法はありますで
> しょうか?

1つの方法は,その修正作業を自動化してしまうことです.
この手の作業の多くの場合は「C/C++ のヘッダファイルの仕様にあわせる」だったりすることが多いですが,だったらそのヘッダファイルから C# 用のコードと,正しく動作しているかのテストケースを作成する仕組みを作る方が確実かと思います.
ヘッダファイルの解析が面倒というのであれば,もっとシンプルな定義ファイルから C/C++ のヘッダファイル及び C# のアクセスコードを両方自動生成という方法もあるでしょう.
(まあそもそもヘッダファイルを解析しなくても,適当に C++ コンパイラでコンパイルして,シンボル情報からメモリ上の位置を確定する手など色々方法はありますけど)
例えば以下は,XML から C# のソースコードを生成している例です.
http://vsug.jp/tabid/63/forumid/45/postid/6234/view/topic/Default.aspx#6280
http://www.dwahan.net/nyaruru/hatena/codegentest.xml
返信ありがとうございます。

> (まあそもそもヘッダファイルを解析しなくても,適当に C++ コンパイラでコンパイルして,シンボル情報からメモリ上の位置を確定する手など色々方法はありますけど)
知識が足りなくて意味がよく理解できないのですが、勉強します。
教えて頂いたやり方を勉強してみます。

ところで、C言語でコンパイルオプションで設定したアラインメントとC#の
StructLayoutを使用したアラインメントはPaddingの埋まり方は異なるのでしょうか?
試した感じだと違うように思うのですが。。。
2006/11/03(Fri) 15:59:25 編集(投稿者)
2006/11/03(Fri) 15:25:28 編集(投稿者)
2006/11/03(Fri) 15:24:09 編集(投稿者)

■No18094に返信(おかさんの記事)
> ところで、C言語でコンパイルオプションで設定したアラインメントとC#の
> StructLayoutを使用したアラインメントはPaddingの埋まり方は異なるのでしょうか?
> 試した感じだと違うように思うのですが。。。

StructLayout は C# という言語の仕様ではなくて,型やフィールドに属性を付けておくことで CLR が実行時にレイアウト構造を調整してくれるという機能です.
C/C++ コンパイラなんかだと,ターゲットとするプラットフォーム (x86 とか x86-64) とかをコンパイル時に決めて,それにあわせて構造体のフィールド配置も決定されますが,.NET の場合は,実行時にどのプラットフォーム用に起動したかに依存して決定されます.
その辺りからして,同じものと思っていると失敗するかもしれません.

実際従来の C++ では sizeof(T) はコンパイル時に確定しますが,C++/CLI で .NET の型に対して行う sizeof(T) はコンパイル時に決定しないという違いがあります.

# まあ事前にフィールド位置を決定したければ,StructLayout.Explicit で FieldOffset 属性を各フィールドに設定して回ればいいんじゃないかと思いますが.

http://www.atmarkit.co.jp/fdotnet/dotnettips/026w32struct/w32struct.html

(追記)
もしかして,Visual C++ で言うところの #pragma pack ではなく,__declspec(align(#)) を期待してます?
http://msdn2.microsoft.com/en-us/library/2e70t5y1.aspx
http://msdn2.microsoft.com/en-us/library/83ythb65.aspx
http://www.microsoft.com/japan/msdn/vs/vc/vcconWindowsDataAlignmentOnIPFX86X86-64.aspx
■No18086に返信(おかさんの記事)
> 返信ありがとうございます。
>
>>> (3) はまたちょっと違った問題を含みますね。
>>C# の class は参照型であるというのは今更ですが、参照型だけにマーシャリングにおいてはポインタとして扱われます。それは LayoutKind.Sequential でマークしていようと関係ありません。
> ということは、ネストになっているクラスのサイズを得ることはできないとでしょう
> か?

値型のネストと参照型のネストでは状況が大きく異なります.
C++ でも,メンバ変数として T field; をもつか,T* pField; を持つかでサイズは変わりますよね?
C# では,参照型は T field; と書かれていてもポインタで参照されます.「ネストになっているクラス」と言われている部分で,C++ と同じものを想定されているとしたら,まずその辺の理解から始める必要があるかと思います.

フィールドとして埋め込まれた配列については,C# 2.0 なら fixed キーワードを使用して unsafe 構造体に埋め込むことはできなくはないです.
http://msdn2.microsoft.com/ja-jp/library/zycewsya.aspx
返信ありがとうございます。

>C/C++ コンパイラなんかだと,ターゲットとするプラットフォーム (x86 とか x86-64) とかをコンパイル時に決めて,それにあわせて構造体のフィールド配置も決定されますが,.NET の場合は,実行時にどのプラットフォーム用に起動したかに依存して決定されます.
>その辺りからして,同じものと思っていると失敗するかもしれません.
環境によって異なるのですね。。。

>C# では,参照型は T field; と書かれていてもポインタで参照されます.「ネストになっているクラス」と言われている部分で,C++ と同じものを想定されているとしたら,まずその辺の理解から始める必要があるかと思います.
教えて頂いたリンクを中心に勉強してみます。

正直、知識が足りなくて???の箇所が多々あるのですが、
教えて頂いたリンクを中心によんでみて、その後解決しない
ものがあれば再度質問させていただくかもしれませんが、一
旦解決としておきます。

ありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板