25

構造体に DateTime フィールドが含まれている場合、LayoutKind.Sequential の動作が異なるのはなぜですか?

次のコードを検討してください (「安全でない」を有効にしてコンパイルする必要があるコンソール アプリ)。

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication3
{
    static class Program
    {
        static void Main()
        {
            Inner test = new Inner();

            unsafe
            {
                Console.WriteLine("Address of struct   = " + ((int)&test).ToString("X"));
                Console.WriteLine("Address of First    = " + ((int)&test.First).ToString("X"));
                Console.WriteLine("Address of NotFirst = " + ((int)&test.NotFirst).ToString("X"));
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct Inner
    {
        public byte First;
        public double NotFirst;
        public DateTime WTF;
    }
}

上記のコードを実行すると、次のような出力が得られます。

構造体のアドレス = 40F2CC
First のアドレス = 40F2D4 NotFirst
のアドレス = 40F2CC

First のアドレスは構造体のアドレスと同じではないことに注意してください。ただし、NotFirstのアドレス構造体のアドレスと同じです。

ここで、構造体の "DateTime WTF" フィールドをコメントアウトして、もう一度実行します。今回は、次のような出力が得られます。

構造体のアドレス = 15F2E0
First のアドレス = 15F2E0 NotFirst
のアドレス = 15F2E8

現在、「First」構造体と同じアドレスを持っています。

LayoutKind.Sequential の使用を考えると、この動作は驚くべきものだと思います。誰でも説明できますか?Com DATETIME 型を使用する C/C++ 構造体との相互運用を行う場合、この動作には影響がありますか?

[編集] 注: Marshal.StructureToPtr() を使用して構造体をマーシャリングすると、データ正しい順序でマーシャリングされ、「First」フィールドが最初になることを確認しました。これは、相互運用性で問題なく動作することを示唆しているようです。内部レイアウトが変更される理由は謎ですが、もちろん、内部レイアウトは指定されていないため、コンパイラは好きなことを行うことができます。

[EDIT2]構造体宣言から「安全でない」を削除しました(私が行っていたいくつかのテストから残っていました)。

[EDIT3] この質問の元のソースは、MSDN C# フォーラムからのものです。

http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/fb84bf1d-d9b3-4e91-823e-988257504b30

4

6 に答える 6

7

レイアウト規則の仕様をもっと注意深く読んでください。レイアウト ルールは、オブジェクトがアンマネージ メモリに公開されている場合にのみレイアウトを管理します。これは、オブジェクトが実際にエクスポートされるまで、コンパイラが自由にフィールドを配置できることを意味します。驚いたことに、これは FixedLayout にも当てはまります。

Ian Ringrose は、コンパイラの効率性の問題について正しく、ここで選択されている最終的なレイアウトを説明してますが、コンパイラがレイアウト仕様を無視している理由とは何の関係もありません。

DateTime には Auto レイアウトがあると何人かが指摘しています。それがあなたの驚きの究極の源ですが、その理由は少しあいまいです. 自動レイアウトのドキュメントには、「[自動] レイアウトで定義されたオブジェクトは、マネージ コードの外部に公開することはできません。公開しようとすると、例外が生成されます」と記載されています。また、DateTime は値型であることにも注意してください。Auto レイアウトを持つ値型を構造に組み込むことで、含まれているものを決して公開しないことをうっかり約束してしまいました。構造をアンマネージ コードに変換します (これを行うと DateTime が公開され、例外が生成されるため)。レイアウト規則はアンマネージ メモリ内のオブジェクトのみを管理し、オブジェクトをアンマネージ メモリに公開することはできないため、コンパイラはレイアウトの選択に制約されず、必要なことを自由に実行できます。この場合、構造のパッキングと配置を改善するために、自動レイアウト ポリシーに戻ります。

そこには!それは明らかではありませんでした!

ちなみに、これらはすべて静的コンパイル時に認識できます。実際、コンパイラ、レイアウト ディレクティブを無視できるかどうかを判断するために、それを認識しています。それを認識したので、コンパイラからのここでの警告は適切であるように思われます。あなたは実際には何も悪いことをしていませんが、何の効果もないことを書いたときに教えてもらえると助かります。

固定レイアウトを推奨するここでのさまざまなコメントは一般的に良いアドバイスですが、この場合、DateTime フィールドを含めることでコンパイラがレイアウトを尊重することをまったく免除されるため、必ずしも効果があるとは限りません。さらに悪いことに、コンパイラはレイアウトを尊重する必要はありませんが、レイアウトを尊重することは自由です。これは、CLR の後続のバージョンが、これに関して自由に異なる動作をすることを意味します。

私の見解では、レイアウトの扱いは CLI の設計上の欠陥です。ユーザーがレイアウトを指定するとき、コンパイラーはそれらを取り囲んではなりません。物事を単純に保ち、コンパイラーに指示どおりに実行させる方がよいでしょう。特にレイアウトに関してはそうです。ご存知のように、「賢い」は 4 文字の単語です。

于 2014-05-13T13:08:24.863 に答える
3

いくつかの要因

  • 整列されている場合、ダブルははるかに高速です
  • 打たれた場所に「穴」がない場合、CPU キャッシュはより適切に機能する可能性があります。

そのため、C# コンパイラには、構造体の「<em>最適な」レイアウトを取得するために使用する文書化されていないルールがいくつかあります。これらのルールでは、構造体の合計サイズ、および/または構造体に別の構造体が含まれているかどうかなどが考慮される場合があります 。構造体のレイアウトを知る必要がある場合は、コンパイラーに決定させるのではなく、自分で指定する必要があります。

ただし、 LayoutKind.Sequential は、コンパイラがフィールドの順序を変更するのを止めます。

于 2010-11-09T10:53:40.700 に答える
3

私自身の質問に答えるには(アドバイスに従って):

質問: 「Com DATETIME 型を使用する C/C++ 構造体と相互運用する場合、この動作は何らかの影響を及ぼしますか?」

回答: いいえ、マーシャリングの使用時にレイアウトが尊重されるためです。(私はこれを経験的に確認しました。)

質問「誰か説明してくれませんか?」

回答: これについてはまだわかりませんが、構造体の内部表現が定義されていないため、コンパイラは好きなことを行うことができます。

于 2010-11-09T14:28:44.313 に答える
2

管理された構造内にあるため、アドレスを確認しています。マーシャル属性は、管理構造内のフィールドの配置を保証しません。

ネイティブ構造に正しくマーシャリングする理由は、データがマーシャリング値によって設定された属性を使用してネイティブ メモリにコピーされるためです。

したがって、マネージド構造の配置は、ネイティブ構造の配置に影響を与えません。属性のみがネイティブ構造の配置に影響します。

マーシャル属性でセットアップされたフィールドがネイティブ データと同じ方法でマネージド データに格納された場合、Marshal.StructureToPtr には意味がなく、単純にデータをバイト コピーします。

于 2011-11-08T20:57:12.630 に答える
1

C / C ++と相互運用する場合、私は常にStructLayoutに固有です。Sequentialの代わりに、Explicitを使用し、FieldOffsetを使用して各位置を指定します。さらに、Pack変数を追加します。

[StructLayout(LayoutKind.Explicit, Pack=1, CharSet=CharSet.Unicode)]
public struct Inner
{
    [FieldOffset(0)]
    public byte First;
    [FieldOffset(1)]
    public double NotFirst;
    [FieldOffset(9)]
    public DateTime WTF;
}

とにかくDateTimeをマーシャリングすることはできず、文字列のみにマーシャリングすることはできないようです(マーシャルのDateTimeをビングルにする)。

Pack変数は、ワードサイズが異なるさまざまなシステムでコンパイルされる可能性のあるC++コードで特に重要です。

また、安全でないコードを使用しているときに表示される可能性のあるアドレスも無視します。マーシャリングが正しい限り、コンパイラが何をするかは実際には問題ではありません。

于 2011-02-14T21:13:28.150 に答える