57


27

構造体をゼロにするmemset()または値の初期化?

Win32 APIプログラミングでは、複数のフィールドを持つCの `struct`を使用するのが一般的です。 通常、意味のある値を持っているのはそのうちの2つだけで、他のすべてはゼロにする必要があります。 これは、次の2つの方法のいずれかで実現できます。

STRUCT theStruct;
memset( &theStruct, 0, sizeof( STRUCT ) );

or

STRUCT theStruct = {};

2番目のバリアントはよりきれいに見えます-ワンライナーで、タイプミスがあり、エラーが発生する可能性のあるパラメーターはありません。

最初のバリアントと比較して欠点はありますか? 使用するバリアントとその理由

8 Answer


79


これらの2つは、意味が異なる_非常に_を構成します。 最初のものは、メモリのバッファを特定の値に設定するための「memset」関数を使用します。 2番目の_オブジェクトの初期化_。 少しのコードで説明しましょう:

_PODタイプのみのメンバーを持つ構造があると仮定しましょう

struct POD_OnlyStruct
{
    int a;
    char b;
};

POD_OnlyStruct t = {};  // OK

POD_OnlyStruct t;
memset(&t, 0, sizeof t);  // OK as well

この場合、「POD_OnlyStruct t = {}」または「POD_OnlyStruct t; memset(&t、0、sizeof t) は大きな違いはありません。ここにある唯一の違いは、 memset`が使用された場合に_alignment_バイトがゼロ値に設定されるためです。 通常はこれらのバイトにアクセスできないため、違いはありません。

一方、質問にC ++のタグを付けたので、POD_とは異なるメンバー_typesを使用した別の例を試してみましょう。

struct TestStruct
{
    int a;
    std::string b;
};

TestStruct t = {};  // OK

{
    TestStruct t1;
    memset(&t1, 0, sizeof t1);  // ruins member 'b' of our struct
}  // Application crashes here
この場合、 TestStruct t = {}`のような式を使用するのが適切であり、 `memset`を使用するとクラッシュします。 `memset`を使用すると、次のようになります。タイプ TestStruct`のオブジェクトが作成され、構造体のメンバーであるため、タイプ `std

string`のオブジェクトが作成されます。 次に、 memset`は、オブジェクト b`があるメモリを特定の値、たとえばゼロに設定します。 これで、TestStructオブジェクトがスコープ外に出ると破棄され、そのメンバーの std :: string b`になると、そのオブジェクトの内部構造がすべて破壊されたため、クラッシュが発生します。 `memset

したがって、現実は、「これらは非常に異なっています」であり、特定の場合に構造全体をゼロに「memset」する必要がある場合がありますが、あなたがしていることを理解し、間違えないようにすることが常に重要です2番目の例では。

私の投票-必要な場合はオブジェクトのみでmemsetを使用し、それ以外の場合は_default_初期化 `x = {}`を使用します。


29


構造体のメンバーによっては、2つのバリアントは必ずしも同等ではありません。 `memset`は構造体をall-bits-zeroに設定しますが、値の初期化はすべてのメンバーを値0に初期化します。 C標準では、これらが整数型に対してのみ同じであることが保証されており、浮動小数点値またはポインターに対しては同じではありません。

また、一部のAPIでは、構造を実際にall-bits-zeroに設定する必要があります。 たとえば、BerkeleyソケットAPIは構造を多相的に使用するため、明らかな値だけでなく、構造全体を実際にゼロに設定することが重要です。 APIのドキュメントには、構造を実際にすべてビットゼロにする必要があるかどうかが記載されている必要がありますが、不足している可能性があります。

しかし、これらのいずれか、または同様のケースが当てはまらない場合、それはあなた次第です。 構造を定義するときは、値の初期化を優先します。これは、意図をより明確に伝えるためです。 もちろん、既存の構造をゼロ化する必要がある場合は、 `memset`が唯一の選択肢です(まあ、各メンバーを手動でゼロに初期化することは別ですが、特に大きな構造の場合は通常行われません)。


9


構造体に次のようなものが含まれている場合:

int a;
char b;
int c;

次に、「b」と「c」の間にパディングのバイトが挿入されます。 memset()はそれらをゼロにしますが、他の方法はそうしませんので、3バイトのゴミがあります(intが32ビットの場合)。 構造体を使用してファイルの読み取り/書き込みを行う場合、これは重要です。


7


あなたが言及したように、それはきれいに見え、エラーが発生しにくいため、値の初期化を使用します。 私はそれをすることにどんな欠点も見ません。

ただし、使用後の構造体をゼロにするために、 `memset`に依存する場合があります。


5


一般的ではありませんが、2番目の方法には、floatをゼロに初期化する利点もあると思います。 memsetを実行すると、確かに


3


一部のコンパイラでは、「STRUCT theStruct = {};」は実行可能ファイル内で「memset(&theStruct、0、sizeof(STRUCT));」に変換されます。 一部のC関数は、ランタイムセットアップを行うために既にリンクされているため、コンパイラはmemset / memcpyなどのこれらのライブラリ関数を使用できます。


2


コンパイル時に実行できるため、値の初期化。 +また、すべてのPODタイプを正しく0に初期化します。

memset()は実行時に行われます。 + memset()の使用は、構造体がPODでない場合に疑われます。 +非int型を正しく(ゼロに)初期化しません。


-1


ポインターメンバーが多数あり、将来さらに追加する可能性がある場合は、memsetを使用すると役立ちます。 適切な `assert(struct→ member)`呼び出しと組み合わせると、初期化するのを忘れた不良ポインターを延期しようとするランダムなクラッシュを回避できます。 しかし、あなたが私ほど忘れっぽくないなら、おそらくメンバー初期化が最高です!

ただし、構造体がパブリックAPIの一部として使用されている場合、要件としてmemsetを使用するクライアントコードを取得する必要があります。 これは将来の校正に役立ちます。新しいメンバーを追加でき、クライアントコードは(おそらく危険な)初期化されていない状態のままではなく、memset呼び出しで自動的にNULLになるためです。 これは、たとえばソケット構造を操作するときに行うことです。