共用体
共用体は、Cプログラミング言語から継承された低レベルの機能で、複数のメンバーが同じメモリ領域を共有できるようにする。
共用体は構造体とよく似ているが、主な違いは次の通りだ。
- 共用体は、
union
キーワードで定義される。 union
のメンバは独立しておらず、同じメモリ領域を共有する。
構造体と同様に、共用体もメンバー関数を持つことができる。
以下の例は、32ビット環境と64ビット環境でコンパイルした場合、異なる結果になる。混乱を避けるため、この章の例をコンパイルする際には、-m32
コンパイラスイッチを使用しよう。そうしないと、後で説明するアライメントの違いにより、私の結果と異なる結果になる可能性がある。
当然ながら、struct
オブジェクトは、すべてのメンバーを格納するために必要なサイズになる:
int
は4バイト、double
は8バイトであるため、struct
のサイズはそれらのサイズの合計になる:
12
一方、同じメンバーを持つunion
のサイズは、最も大きなメンバーのサイズと同じだけになる:
4バイトのint
と8バイトのdouble
は同じ領域を共有する。その結果、union
の全体のサイズは、その最大のメンバーと同じになる:
8
共用体はメモリを節約する機能ではない。複数のデータを同じメモリ位置に格納することは不可能。共用体の目的は、異なる型のデータを異なるタイミングで同じ領域を使用することだ。一度に確実に使用できるメンバーは1つだけだ。ただし、この方法は異なるプラットフォームでは移植性がない場合があるが、union
のメンバーを使用して他のメンバーのフラグメントにアクセスすることは可能だ。
以下の例の1つは、typeid
を利用して、現在有効なメンバー以外のメンバーへのアクセスを禁止している。
以下の図は、上記のunion
の8バイトがメンバー間で共有される方法を示している:
0 1 2 3 4 5 6 7 ───┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬─── │<─── 4 bytes for int ───> │ │<─────────────── 8 bytes for double ────────────────>│ ───┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴───
8バイトすべてがdouble
メンバーに使用されるか、最初の4バイトがint
メンバーに使用され、残りの4バイトは未使用となる。
共用体には、必要な数のメンバーを含めることができる。すべてのメンバーは、同じメモリ位置を共有する。
すべてのメンバーに同じメモリ位置が使用されることは、予想外の結果をもたらすことがある。例えば、union
オブジェクトをそのint
メンバーで初期化し、そのdouble
メンバーにアクセスすると、次のような結果になる。
int
メンバーを値42で初期化すると、最初の4バイトだけが設定され、double
メンバーに予期しない影響が出る。
2.07508e-322
マイクロプロセッサのエンディアンに応じて、4バイトはメモリ内で 0|0|0|42、42|0|0|0、またはその他の順序で配置される場合がある。そのため、double
メンバーの値は、プラットフォームによって異なって表示される場合がある。
匿名共用体
匿名共用体は、ユーザー定義型のどのメンバーが同じ領域を共有するかを指定する。
S
の最後の2つのメンバーは同じ領域を共有する。したがって、struct
のサイズは、2つのint
の合計となる:first
に必要な4バイトと、second
とthird
で共有される4バイト:
8
他のメンバーの分解
共用体は、他の型の変数の個々のバイトにアクセスするために使うことができる。例えば、IPv4アドレスの4バイトに個別にアクセスするのが簡単になる。
IPv4アドレスの32ビット値と固定長配列は、union
の2つのメンバーとして定義できる。
そのunion
のメンバーは、以下の図のように同じメモリ領域を共有する:
0 1 2 3
───┬──────────┬──────────┬──────────┬──────────┬───
│ <──── 32 bits of the IPv4 address ────> │
│ bytes[0] │ bytes[1] │ bytes[2] │ bytes[3] │
───┴──────────┴──────────┴──────────┴──────────┴───
例えば、このunion
のオブジェクトが0xc0a80102(ドット形式の192.168.1.2に対応する値)で初期化されると、bytes
配列の要素は自動的に4つのオクテットの値になる。
リトルエンディアンシステムで実行すると、オクテットはドット表記の逆の順序で表示される。
[2, 1, 168, 192]
オクテットの逆の順序は、union
の異なるメンバーにアクセスすると、予測できない結果が生じるもう1つの例だ。これは、union
の動作は、そのunion
が1つのメンバーを通じてのみ使用されている場合にのみ保証されるためだ。union
が初期化に使用されたメンバー以外のメンバーの値については、何の保証もない。
この章とは直接関係はないが、core.bitop
モジュールにあるbswap
は、エンディアンの問題に対処するのに役立つ。bswap
は、パラメータのバイトをスワップしてからそのパラメータを返す。また、std.system
モジュールにあるendian
の値を利用すると、前のIPv4アドレスのオクテットを、バイトをスワップしてから期待される順序で出力することができる。
出力:
[192, 168, 1, 2]
IpAddress
型は単純な例として考えてほしい。一般的には、単純なプログラム以外の場合は、専用のネットワークモジュールを検討したほうがいいだろう。
例
通信プロトコル
TCP/IPなどの一部のプロトコルでは、プロトコルパケットの特定の部分の意味は、同じパケット内の特定の値によって決まる。通常、連続するバイトの意味を決定するのは、パケットのヘッダーにあるフィールドだ。共用体は、このようなプロトコルパケットを表現するために使うことができる。
以下の設計は、2種類のプロトコルパケットを表している。
上記のstruct
は、type
メンバーを使用して、union
のaParts
またはbParts
のどちらを使用するかを決定できる。
識別された共用体
共用体は、通常のunion
に型安全性を追加したデータ構造だ。union
とは異なり、現在有効なメンバー以外のメンバーにはアクセスできない。
以下は、int
とdouble
の2つの型のみをサポートする、単純な共用体型だ。データを格納するunion
に加えて、2つの メンバーのうちどちらが有効であるかを識別するための TypeInfo
メンバーを保持して、2つのunion
メンバーのうちどちらが有効かを判断する。
これは単なる例だ。プログラムでは、std.variant
モジュールにあるAlgebraic
およびVariant
を使用することを検討しよう。さらに、このコードでは、テンプレートやミックスインなどのDの他の機能を利用して、コードの重複を減らすことができる。
格納されるデータに関係なく、Discriminated
型は1つだけだ。(別のテンプレートソリューションでは、データ型をテンプレートパラメータとして受け取ることもできる。その場合、テンプレートの各インスタンスは異なる型になる。)そのため、Discriminated
オブジェクトの配列を作成することができ、要素が異なる型のコレクションを効果的に実現できる。ただし、ユーザーは、アクセスする前に有効なメンバーを知っている必要がある。例えば、次の関数は、Discriminated
のtype
プロパティを使用して、有効なデータの実際の型を決定する。
'int'の取り扱い | 1 |
---|---|
'double'の取り扱い | 2.5 |