共用体 - みる会図書館


検索対象: 月刊 C MAGAZINE 1992年6月号
188件見つかりました。

1. 月刊 C MAGAZINE 1992年6月号

を返すような処理になる ( もちろんほかの形 式て、情報を返す手法もある。これは一例て、 しかない ) 。 この際 , その値が long 型て、あるのか doub le 型て、あるのか , あるいはそのほかの型の値 て、あるのかというのはお互いに排他て、ある 点に注意したい。すなわち , ある値の「型」 は必ずひとつだけて、あり , long 型て、あると 同時に double 型て、もあるなどという状況は あり得ない。したがって , このようなデー タを表現するのには , あらかじめ定められ た複数の型のうちのひとつを排他的に選択 して , その型の値を記憶することがて、きれ ば十分て、ある。 このように「お互いに排他」な型の値 , す なわち「ある瞬間には異なる複数の型のう ち , どれかひとつだけが必要て、あり , 同時 にふたつ以上が要求されることはない」とい う状況下て、 , これらの値を一括してひとつ の変数に記憶しておきたいという状況は実 際しばしば起きる。 ほかの例としては , 「 ( 1 台の ) 車」を表す 変数を考えよう。車の情報を詳しく管理し たいのだが , 実際には四輪普通乗用車と四 輪軽乗用車 , 自動二輪および原付などによ って管理すべき項目というのは大幅に変化 する。そのため , これらはやはりそれぞれ 別の型としてあらかじめ定義しておき , 実 際の車種に合わせて型を変えて記憶するの が妥当て、ある。この場合も , 1 台の車が同時 に四輪車てありかっ二輪車てあるということ は ( 超合金変形マシンて、もないかぎり ないだろう。すわなちそれらの型はひとつ の変数に関しては排他的に使用されること がわかる。 以上のように , メンバがお互いに重なり あってメモリ上に割り付けられていても , それらが排他的な形て、のみ使用されるなら ば , 記憶内容が不用意に破壊されるという 状況は起きず , そのようなケースては共用 体を用いても問題は生じないことがわかる。 ところて , そのようなケースに構造体を 用いることはて、きないのかという疑問が生 じるだろう。 結論からいえば , 別に構造体て、も問題は とくに共用体て、なければならない理 由というのはない。共用体が有利なのは , メモリ使用効率て、ある。構造体を利用した 場合には , 現在使われているメンバのほか のメンバには有効な値が設定されないまま , メモリがまったくムダになってしまう。 れに対して共用体て、はメンバは極力メモリ を共有するように割り当てられるため , ム ダを最小限に抑えることがてきる。 ただし , このケースて、はムダといっても , たかが数バイトてある。ほとんどの場合こ れは誤差の範囲て、あり , この節約を理由に 共用体の使用を正当化するのは難しいだろ う。あえて理由をあげればプログラマの「気 持ち」がすっきりするという程度だろうか。 共用体によるメモリ使用効率の向上が明 白な意味を持つのは , 構造体 , あるいは共 用体を多数個記憶する必要が生じた場合て、 ある。端的な例としてはデータベースて、あ る。全体て、 10 万件のデータベースがあった として , その 1 レコードにつき 10 バイト省略 て、きたとしたら , 全体て、 IM バイトの節約に なる。もし全体の件数が 100 万件になれば , 差は 10M バイトに達する。これは決して無 視て、きる違いて、はないだろう。 構造体との併用 , ・タイプタグ 共用体の変数に対して , 現在代入されて いるのがどの型の値て、あったかを管理する のはプログラマの責任て、ある。これがあや ふやになってしまうと正しい値を参照する ことがて、きない。このため , 共用体は構造 体の中に入れ子の形にして用いられる場合 が多い。外側の構造体のメンバに「現在どの 型のデータが共用体に記憶されているか」を 記憶しておくわけて、ある。具体例として Li sp 系言語のデータ構造を用いて解説してみ よう。 more Lisp 言語て、は S 式と呼ばれる二進木をデー タ構造の基本にしている。 Lisp の伝統的な インプリメンテーシゴンて、は , 二進木のノ ードはセルと呼び , car と cdr というふたっ の子供 ( サブツリー ) へのポインタを有して いるだけて、ある。そして葉に当たる部分に は通常アトム (atom) と呼ばれるデータがや ってくる 0Fig. 7 にその例を示すが , 図中の A, B, および NIL というのがアトムて、あ る。それ以外の四角い箱がセルて、ある。 以前の Lisp て、はアトムを表現するのには 特別な工夫をして , car, cdr のフィールド を有するセルだけて、済ませていた。 ところが最近のインプリメンテーション て、はときと場合に応じてノードの内部構造 を変えて , その中に値を直接保持するのが 普通て、ある。そして , 同じノードがときと してセルにもなり , アトムにもなるように している ( アトムは葉に相当する部分にしか 現れないから , 本来はノードと呼んて、はい けないのだが , 行きがかり上そのように呼 ぶ ) 。しかも , Lisp て、はとくに型宣言をしな いかぎりアトムはどのような型の値て、も保 持て、きる。 このためアトムの中には整数や実数 , 文 字列やリストなど各種の型の値を記憶する ためのメンバを宣言する必要がある。しか も , セルというのは非常にたくさん使われ るものて、あるし , 通常主記憶上に存在する ため , やはり少して、もメモリを節約したい このためノードの定義には共用体を使うの が普通て、ある。 List 2 にフリーソフトウェアとして配布さ れている David MichaeI Betz 作の XLISP 2.1 の typedef によるセル定義をあげておく。 比較的大きな構造体 / 共用体の宣言て、ある が , その骨格は List 3 のようになっている。 すなわち , 外側に structnode という構造 体の宣言があり , そのメンバは char n tYP e, char n flags, および uni ninf0 n inf0 こて、先頭のメンノヾの char の三つてある。 n_type というのが実際に union ninf0 n inf ANSI C ー more 115

2. 月刊 C MAGAZINE 1992年6月号

more セット 0 から割り付けられる。そしてその後 のメンバは , 直前のメンバと重ならな。いよ うな適切なアライメントを保ったオフセッ ト位置から割り付けられる。 それに対して , 共用体ではすべてのメンバがオフセッ ト 0 から割り付けられる。 このことは , 共用体のおのおののメンノヾ は , そのメンバ用に割り付けられているメ モリ領域の内の少なくとも一部分をほかの メンバと共有しているということを意味し ている。 たとえば次の構造体宣言と共用体宣言を 考えてみよう。 struct f00 { double Fig. 3 union bar のメモリ割り付け ( 例 ) 0 1 っ 4 っ 0 4 5 6 7 char W short z long y double union bar { double X ・ long Y ; short Z ・ Char X N long short Char Fig. 4 構造体と共用体に対してその全体のサイズに関する不等式 union bar { double sizeof( 構造体 ) ~ sizeof( メン JXi) sizeof( 共用体 ) ~ max(sizeof ( メンノヾ i) ) long short Char こて、 , 各データ型の大きさは Table 1 に 示すサイズて、あると仮定する。おそらく現 時点て、このサイズが最も一般的て、あると思 われる。 この場合 , たとえば構造体 f00 と共用体 b ar の各メンバは Fig. 2,Fig. 3 のようにメモ リに割り付けられる ( 構造体の場合 , コンパ イラのアライメントボリシーによって必ず しも割り付け方は一様て、はない ) 。 なお , 共用体て、も , 構造体と同様に , す べてのメンバがうまくアライメントて、きる ように , 共用体オプジェクト全体がアライ メントされるのはいうまてもない このメモリ割り付け方法の違いから , 構 造体および共用体それ全体の size 。 f に関し Fig. 5 union baz のメモリ割り付け ( 例 ) short z char bC3] pad ( アライメント調整用 ) ANSI C ー more 113

3. 月刊 C MAGAZINE 1992年6月号

保証が得られないことになる。 common initial sequence さて , 前節の冒頭て、「共用体のあるメンバ に対して値を格納し , そのメンバとは型が 異なる別なメンバの値を参照するようなコ ーディングは通常は誤りて、ある」と述べた。 て、は「通常て、はない」ケースて、は「誤りて、はな い」ことがあるのだろうか。 実は , そのようなケースがあることが定 められているのて、ある。その特別の場合と は「 common initial sequence 」と呼ばれてい る。日本語て、は「 ( メンバの ) 共通先頭部並び」 とて、も訳すべきだろうか ( なお余談だが , K& R 第 2 版の邦訳て、は A8.3 て、これを「共通初期 化部分」と訳している。 initial を「初期化」と 訳したために , A8.3 の最後のパラグラフは 何のことかわからない文章になってしまっ た ) 。 List 9 の例を見てもらったほうがわか りやすいだろう。 List 9 て、まず注目すべきは switch 文の制御 式 (controlling expression) のところて、用い ている共用体のタイプタグの参照て、ある。 switch (). sa. tag a) { が , この値が 1 の場合には共用体のメンバ sb ー含まれるメンバ tag a て、ある。ところ こて、参照しているのは共用体のメンバ sa に うテストをする場合には , 原則論からは u が すなわち , 最初の u. sb. tab b 残る。 ングするようにしても , 建て前上の問題は るいは if を使って List 10 のようにコーディ トすることにしても状況に大差はない。あ itch 文を使うかぎり , どのタイプタグをテス 共用体のメンバに属しているのて、ある。 sw のタイプタグは本当に必要な値とは異なる る。つまり switch 文て、テストしている場合 値が 2 の場合には同様に u. sc. c を参照してい に含まれるメンバ b て、ある u. sb. b を参照し , more ある。 わかっていなければならないということて、 現在保持しているメンバは sb て、あることが 代入はおそらく問題ないだろうという予想 うものてある。そう解釈すれば , これらの リに割り付けられる」ことを利用しようとい る構造体て、あったとしても同じ形式て、メモ 体の先頭から並んて、いれば , それらは異な の精神は「同じ型のメンバが同じ順序て構造 しかし , common initial sequence の本来 うのは ANSI 違反なのかもしれない イプタグに代入を行ってから値の代入を行 数 bar や baz のように共通先頭並びの別のタ いのかとも思われる。そうすると List 9 の関 検分 (inspect) て、ある以上 , 代入は許されな 分のどれを検分してもかまわない」て、ある。 ひとつを保持している場合にかぎり共通部 て、共用体オプジェクトが「それらの構造体の こて、 ANSI が規定しているのは , あくま の場合には同一の幅を有する場合 ) 。 している場合 ( およびビットフィールド メンバが整合する (compatible) 型を有 ひとつ以上のメンバに関してそれらの に「共通先頭部並び」を共有する。 なお , ふたつの構造体は次のような場合 検分してもかまわない。 ③複数の構造体に共通の先頭部分のどれを 体のひとつを保持している場合には , ②かっ共用体オプジェクトがそれらの構造 共有する複数の構造体を含んでいて , ①共用体が , それぞれが共通先頭部並びを 定を設けている (ANSI 3.3.2.3 ) 。 すぎるため , ANSIC て、は次のような例外規 しかし , この制約はあまりに杓子定規に ならないという結論が導かれることになる。 用体て、はその外部にタイプタグを置かねば これが真てあれば , List 9 に示したような共 ーディングは誤っていることになる。もし したがって , ここて、示した if 文のようなコ が立つ。 FuII Screen Editor 日本語版 第一 ef プロクラミング・エテイター日卒第新 Full Screen Ed 員 ( 消費税は含ます ) 定価 48 , 000 円 プログラミング・エテイタ ANSI C ー more 121 く資料請求番号 121 〉 TEL. 0425 ー 23 ー 4469 FAX. 0425-27- 引 27 立川事業所ューザ・サポート係 ・お問合せは東京都立川市曙町ト絽ー 2 ー清ビル 日ホシステム槽式会社 新発売 / ムヒ 新機 新機能 新機能 対応機種」一 3m0 、 J -3300 シリーズ価格 19 , 800 円 ( 税別 ) ない人でも OK / 簡単操作の能力開発ソフトウェアです。 格試験等に最適です。「ダイナブック速読」は DOS を知ら で能力がアップします。情報収集、受験、資 読書速度が 3 ~ 5 倍になり、右脳の刺激 ダイナブック速読 ただ今、とっても便利な習′ f マクロ集を公開中 ザ・サポート係までこ・連絡下さい。 方、セット販売希望の方、またサイトライセンス希望の方はユー もいたしております。・お急ぎの方、上記以外のメディア希望の ・英語 DOS では使用できませんが、 B 「 ief 英語版とのセット販売 ・ 5 ・ 2DD 、 3.5 ・ 2DD デュアルメディア・ MS ー DOS3 」以降に対応 東芝、 NEC 、 AX 協議会、旧 M 、富士通、 DOS/V 06 機種対応パッケージ C Users Journa l) アンドゥだ一 - ーおもわず間違うのを期待してしまいそうだ。 (The に不満を覚えたら、これしかない。 ( Oh / FM ) ・まるで夢のような 300 回のアンドウは大きな特徴。 ( 日経バイト ) ・既存のエデイタ ない。 (Jerry Pournelle/BYTE) ・ソフト開発には強力なツール、 ・プログラミング向きェデイタを探している人は、もう探す必要は アメリカでも日本でも高い評価 ョン ) 、メニュー ( オプション ) etc. 数を拡張、リジューム機能、 ctrl キー・アサイン ( オプシ キー操作のファイルへのセープとリプレイ、処理可能行 ・その他の主な新機能 切れても安心です。 容をファイルに自動保存します。だから編集中に電源が Brief は、一定時問、キーを操作しないと最新の編集内 ・安全編集 ます。 ますので、やり直しの面倒を気にせず、安心して編集でき 各ファイルこ・とに 3 圓までの操作を元にもどすことができ ・ 300 回ものアンドゥ 構文を自動生成します。 C 、 Ada 、 Basic 、 COBOL 、 FORTRAN 、 MODULA2 、 Pascal で く構文の自動生成〉 ・テンプレート機能 トやウォッチ・ドッグもセットできます。 ソースコードでデバッグできます。もちろんプレークポイン ・マクロのスクリーン・デパッガ 簡単でカスタマイズも思いのままです。 使い慣れている C 言語でマクロが組めますから、習得が ・ C 言語で組めるマクロ

4. 月刊 C MAGAZINE 1992年6月号

て、タグ型を integer にした ( 本誌は「 C マガジン」 て、あって「 Pascal マガジン」て、はないのだか ら それから PascaI の可変部を持つレコード 型においても , タグフィールドを省略して しまって , 可変部のみから構成されるレコ ード型 , つまり C 言語の union と同一の構造 を作ることは可能になっている。その場合 には定義の際に case の後に型名だけを記述 すればよい さて , PascaI のような構文を備えている と何が嬉しいのだろうか。ひとつ確実なの は , タイプタグの重要性というか必要性を 構文レベルて、強力に推奨する ( 省略可能だか ら「強制」て、はない ) ため , 共用体にまつわる トラブルが減ることが予想されることて、あ る。そして , その気になれば言語の実行時 ルーチンがタイプタグの値と実際にアクセ スされるメンバの型の一致を検証すること も可能になる。 C 形式て、はそれは難しい もうひとつ , 些細なことだが記述が少し だけ簡略化されるのもメリットて、ある。た とえばタイプタグの値を確認して , その値 が 0 て、あったなら整数型の変数 m にメンバ n の 値を代入するというシーケンスについて , C 言語と PascaI て、のコーディングの断片を考 えよう (List 5 ) 。 この場合 , メンバ n のアクセスが C 言語て、 は s. u. n て、あり , PascaI て、は s. n て、ある点に注 目したい。 C 言語て、はこのようにタグフィー ルドっきて、共用体を使用しようとすると , 一般に構造体の内部に共用体が含まれる構 造になってしまう。このため , その共用体 のメンノヾへのアクセスは , S. U. M という形にならざるを得ない。ここて、 S は構 造体変数の名前て、あり , U はその構造体のメ ンバて、あるところの共用体の名前 , そして M は共用体のメンバ名て、ある。一方 , Pasc al の場合には可変部のフィールド名は直接構 造体に含まれていると解釈される。そのた めムダな U に相当するものを書かなくてよ 無名共用体の例 List 1 : / * 無名共用体 ( ANSI C にはありません ) * / 2 : typedef struct 3 : int valtype; union { 4 : int n; 5 : 6 : Char 8 : } f00 ; a [ 10 ] ; 0 union によるオーバレイ・・・・・ float の内部表現を表示する List 1 : #include く stdio. h> 3 : int main(void) union { 5 : 6 : float Char 8 : long 9 : 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : } byt[sizeof(float)]; int i,• int j; for ( i ニ 1 ; i く 10 ; + + i ) { printf( ” %12. 6f ・ for (j = 0 ; j く sizeof(). byt); + + j ) printf(" % 02X " , u. bytCj]); printf( ” : XId}n", u. n) ; return 0 ; union を使わずに float の内部表現を表示する List 1 : #include く stdio. h> 3 : int main(void) 5 : float 6 : int i; int j; 8 : unsigned Char 9 : (uns igned char *)&f; P 11 : 12 : for ( i = 1 ; i く 10 ; + + i) { 13 : printf( ” %12.6f : 14 : for (j = 0 ; j く sizeof(float); 15 : printf( ” % 02X " , p[j]) ; 16 : printf(" : XldYn ” , *(long *)p) ; 18 : 19 : 20 : } return 0 ; 118 C MAGAZINE 1992 6

5. 月刊 C MAGAZINE 1992年6月号

引 mo Fig. 8 List 7 の LSI C -86 ve 「 3.3 による実行結果 1 .000000 00 00 80 3F 1065353216 1084227584 5 ℃ 00000 00 00 AO 40 9.000000 00 00 10 41 1091 567616 1095761920 1 3 ℃ 00000 00 00 50 41 1099431936 17.000000 00 00 88 41 00 00 A8 41 1 101529088 21 ℃ 00000 1 103626240 25.000000 00 00 C8 41 00 00 E8 41 1 105723392 29 ℃ 00000 33 ℃ 00000 00 00 04 42 1 107558400 もっとも , このため JIS-Pascal の文法の 己述が難解になってしまっているという指 摘があり , 論理的には C 言語のように単純に 入れ子にしたほうがすっきりする。ところ が , 見た目と論理というのは必ずしも一致 してくれないのが世の常て、あって , やはり C 言語のこの書き方はわずらわしく感じられ その問題の使い方とは , 一般にオーバレ そのまま integral (integral は char, すべての C 十十や一部の C 言語処理系て、はこの点が int, および enum の総称 ) として取り出した イ (overlay) と呼ばれるものて、ある。 List 7 ANSI C に対して拡張されている。すなわち 「無名共用体 (anonymous union) 」という機 値」を得たいのて、ある。このような目的には を見てほしい。この例て、は , 共用体の各メ オーバレイというのがひとつの有効な手段 能て、あり , これを用いる場合 , List 6 に示す ンバが重なり合ってメモリ上に割り付けら て、ある。 ように , struct の内側の union には , その un れることを積極的に利用している。すなわ LSI C ー 86 ver 3.3 による List 7 の実行結 ち , float の変数 f と char の配列 b, および 10 ion 自身に対する名前を与えない。そうし 果を Fig. 8 に示すが , 1 行の左端に float の変 ng の変数 n を同じメモリに割り付けること て , この場合のメンバ n のアクセスには , s 数の値が実数形式て、表示され , ついて、それ て、 , f の値をバイト単位てるを通じてアクセス が f00 型のオプジェクトて、あるとすると s. n と が内部的にはどのようなビットパターンと て、きるようにし , さらに f の値を long の整数 書くことがて、き , PascaI と同じになる。 して記憶されているのか , いわゆる内部表 の機能は ANSI C には含まれていないのて、 , としてもアクセスて、きるようにしているの 現形式がバイト単位て、表示される。さらに 残念ながら C 言語を使うかぎりわずらわしく て、ある。 同じビットパターンをそのまま long として ても中間の共用体名を省略することはて、き これはいわば , C 言語の型システムの裏を 解釈したらどのような値になるかが右端に かいているのて、ある。 Pascal て、も同様のこ ただし , マクロを利用して逃れるこ 表示される。 とがて、きる。実際 , C 言語よりもはるかに型 とは可能て、ある。 List 7 の実行結果は明らかに処理系や使 にうるさく , かつ自由なキャストや , 任意 なお , C 十十の無名共用体はここて、示した 用している CPU のアーキテクチャ , あるい の変数のアドレス値を取ってポインタにす 用途以外の意味も持っているのだが , C 十十 は , 場合によっては浮動小数点演算ハード る操作などが許されない標準 PascaI におい に対するこれ以上の深入りは避ける。 ウェアがあるのかどうかなどの環境にも左 ては , オーバレイが型システムの制約を逃れ ーバレイ るための唯一の手段て、あった。通常 , C 言語 右されることになる。 ・・・型システムの裏をかく なお , List 7 の動作説明は , 暗黙のうちに て、 float の変数の値を long として取り出すた float と long のサイズが同じてあることを仮 めに ( long ) て、キャストしたり , long 型の変 前節まて、において , 共用体の基本的な用 数に値を代入したりしても , それは「 float の 定していることを注意しておきたい。 float 途はおのおののメンバが排他的に使用され と long のサイズが異なる環境て、は , このプ 値を ( なるべくその値を保つように ) 型変換 る場合て、あると述べた。共用体のあるメン ログラムの動作は若干変わる。 sizeof(floa して long にした値」が得られるだけて、ある。 バに対して値を格納し , そのメンバとは型 t) >sizeof(long) の場合には float の一部だ 普通はそれが望ましい動作て、あり , 何も が異なる別なメンバの値を参照するような けが long として解釈され , sizeof(float) <s 問題はない。だが , ときとして「内部表現を コーディングは通常は誤りて、ある。ところ 知りたい」というような要求が生まれること izeof(long) の場合には long として解釈する が , あえてそのような使い方を用いるケー メモリ内容の一部には初期化されていない がある。そのような場合にはデータを 1 ビッ スがあることは触れておくべきだろう。 ト単位て、取り出す必要がある。これにはビ あらかじめ注意しておくが , ここて、述べ バイトデータが含まれていることになる。 ット演算を用いることがて、きよう。ところ このように共用体を用いてオーバレイを実 るような共用体の使い方をした場合には , がビット演算の演算子い , & , は integra 現すると , ビットパターンを保ったままて その結果は処理系に強く依存する。移植性 を高くしたいならばこのような使い方は避 1 にしか適用て、きない (ANSI 3.3.10 ~ 3.3. まったく違う型としてアクセスする ( 解釈す る ) 作業をエレガントに記述することがて、き 12 ) 。そのため , 「 float のビットパターンを けるべきてあろう。 1 三ロ ANSI C ー more 119

6. 月刊 C MAGAZINE 1992年6月号

共用体と構造体 N 引 mo 化 第 13 回 きたあきら 今回は構造体と密接な関係をもつ共用体を取り上げ , 両 者の関係やプログラミング上の注意点に関して述べる。 Fig. 1 共用体とは 前回の構造体の構文定義て uni 。 n というの が出てきた。その部分を再び掲載する (Fig. 1 ) 。これが構造体および共用体の宣言のも っとも外側のレベルて、ある。キーワード str uct を用いて定義するのが構造体 (structure) て、あり , union を用いて定義するのが共用体 構造体宣言の構文定義 struct—or—union—specifier : struct-o 「 -union identifier opt { st 「 uct—declaration-list } struct—or—union identifier struct¯o 「—union . struct unlon struct f00 のメモリ割り付け ( 例 ) て、ある。 TabIe 1 各型の一般的なサイズ 4 月号て、述べたように , 先頭のメンバはオフ 付け方にある。構造体の場合 , ' 92 年 2 月号 , 構造体と共用体の違いは各メンバの割り て、ある。 機能を混同してしまう初心者もいるくらい がそっくりてあることが災いして , 両者の ったく努力を要しない。実際あまりに構文 用体を使うのは , 少なくとも構文的にはま のため構造体を完全に理解していれば , 共 メンバの指定方法に関しても差はない の仕方 , 変数宣言の仕方やアクセス方法 , ールドまて、含めてすべて同じて、ある。定義 のほかの構文は , タグの扱いやビットフィ union というキーワードが異なるだけて、 , そ 定義から明らかなように , 両者は struct と Fig. 2 0 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 12 : 14 : struct f00 double long short Char X ・ Y ; Z ・ W ・ double x long y Char W 型名 sizeof(double) sizeof(long) sizeof(short) sizeof(char) 112 C MAGAZINE 1992 6 サイズ 8 バイト 4 バイト 2 バイト 1 バイト

7. 月刊 C MAGAZINE 1992年6月号

て , Fig. 4 のようなことがいえる。すなわ ち ,struct 全体のサイズは各メンバそれぞれ のサイズの総和を下回ることはない。等号 が成り立つのは , 構造体内部にアライメン ト調整のための隙間 ( 穴 ) が存在しないとき て、ある。 一方 , 共用体の場合にはそれぞれのメン バのうち , 最大のサイズを有するものと同 じサイズを下回ることはない。この場合も 同様に共用体の末尾部分にアライメントの 調整の隙間が存在するかもしれないのて、 , 不等号はそのような場合のためて、ある。共 用体の末尾に隙間が生じ得る典型的な例は 次のような宣言て、ある。 union baz { short a ; b [ 3 ] ; Char 共用体のサンプルプログラム List 1 : #include く stdio. h 〉 2 : 3 : int main(void) 6 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : 14 : 16 : } volatile union zot { long float y; u. x = 1 ; u. Y ニ 1.0 ; printf( ” u. x ニ Xd}n" u. x) ; u. x ニ 1 ; printf("u. y = Xf}n", u. y) ; return 0 ; が独立した変数て、あり , それらをひとまと めにして管理するための言語機能て、あった。 この場合 , Table 1 のように short のサイ これに対して共用体の場合 , 各メンバはそ ズが 2 バイトて、あるとして , かっコンパイラ れぞれ同じメモリアドレスから割り付けら が共用体全体のサイズを偶数にアライメン れるため , その存在はお互いに独立て、はな トするような場合には , この unionbaz のサ 。たとえば , List 1 のようなプログラムを って暗黙のうちに異なる値に変わってしま い イズは 4 バイトになるだろう (Fig. 5 ) 。もち 考えてみよう ( volatile は最適化の結果 , 思 う。実際に手元の MS ー C 6.0 て、試してみたと ろん , コンパイラによっては末尾の pad は付 わぬ結果になってしまうのを予防するため ころ ,Fig. 6 の結果を得た。ちょっと考える 加されず , 全体のサイズは 3 バイトのままて、 に念のため付加している ) 。 と , これて、は共用体は安心して使えないて、 ある場合もあり得る。 List 1 を実行させたとき ,printf の呼び出 はないかということになる。 共用体の変数に対してメンバづけを行わ しによって出力される u. x の値はおそらく 1 て、 しかし , 実際にはこれによって共用体の ずに , 共用体全体として代入などを行うこ はないだろうし , u. y の値もおそらく 1.0 て、は 有用性が損われるということはない。これ とは可能て、ある。また関数の引数に共用体 ないだろう。具体的にどのような値になる は本来そのおのおののメンバが排他的にし 全体を渡すこともてきる。構造体て、もこれ かは処理系依存て、あるが , 要するに u. y への か存在しないようなケースに用いるための らの操作は可能なのて、あるから , これは当 1.0 の代入によって u. x が割り付けられていた データ構造なのて、ある。 然のことといえる。 メモリ領域への書き込みが行われるため , たとえば , コンパイラの前段フェーズて、 K & R 時代の古い C 言語て、は共用体変数の u. x がその時点て、保持していた値は上書きさ ある字句解析部を考えてみよう。字句解析 初期化は許されなかったが , ANSIC て、は初 れてしまい , u. x の値は 1 て、はなくなる。 部の一般的な機能は , ソースファイルを読 期化も許される。その場合 , どのメンバに 同様に , u. x への 1 の再度の代入によって u. み込んて、 , 文字の並びを所定のトークン単 対して初期化を行うかが問題となるのだが , y が割り付けられているメモリ領域の一部が 位に区切るとともに , 各トークンの値を何 これは「先頭に宣言したメンバ」て、あると定 書き変わるため , u. y の値はもはや 1.0 て、はな らかの内部表現に置き換えて次段のフェー められている (ANSI 3.5.7 ) 。 くなってしまうのて、ある。 ズて、ある構文解析部に送るというものて、あ 上書きした値のビットパターンがたまた 体の利点 ま元々の値のビットパターンと同じて、あっ この場合 , たとえば数値定数を読み込ん たという希有な場合を除けば , u. x および u. だ場合に , それが整定数て、あれば long 型の 構造体というのは , 各メンバがそれぞれ y の値はお互いに他方のメンバへの代入によ 値を , 浮動小数定数て、あれば double 型の値 Fig. 6 List 1 の実行結果 u. x : 0 u. y ニ 0 ℃ 00000 114 C MAGAZINE 1992 6

8. 月刊 C MAGAZINE 1992年6月号

Common initial sequence の例 List ところて、少々注意しておきたいのだが , C 言語においては , ビットパターンを保った ままて、違う型としてアクセスするためには 共用体が必要不可欠だというわけて、はない C 言語て、は & によるポインタの生成と強力な キャストが存在するために , 実際には共用 体がなくても同様の動作をするプログラム を作ることは容易て、ある。具体例を List 8 に 示す。 このように , 単項の & によってメモリ上に 存在する任意のオプジェクトへのポインタ が生成て、きること , すなわちそのアドレス を取り出せることと , 強力て、 , ある意味て、は 無節操なポインタのキャストが許されるこ とが非常に重要な C の特徴て、ある。 これらの特徴によってわざわざ共用体を 用いなくても , 任意のオプジェクトの内容 を , それがメモリ上に存在するかぎり好き 勝手な型としてアクセスすることが可能て、 ある。もっとも CPU によっては , ワードア ライメントの制約を受けて , 奇数番地から 始まるワードをアクセスすることはて、きな いために実行時にエラーになったりするだ ろうが , それは CPU のアーキテクチャ上の 制約て、あって , C 言語としての制約て、はな い。その事実は , このような記述は移植性 の悪いコーディングて、あるということを示 しているにすぎない なお , 共用体のオプジェクトは , それ全 体はすべてのメンバがアライメントの制約 を満たしているようにメモリ上に割り付け られる。このため , たとえ本来のデータ型 とは違う型のメンバを通じてアクセスを行 ったとしても , ワードアライメントのミス て、実行時にエラーになることはない この点は共用体のほうが安心て、きるとは いえる。しかし , それは共用体のオプジェ クトが正規の形て、メモリ上に割り付けてい る場合にかぎられる点には注意が必要て、あ る。任意のポインタ値を「共用体へのポイン タ」へとキャストして用いる場合などはこの 1 : #include 2 : 3 : union { struct { 4 : 5 : int tag—a; 6 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : 14 : 15 : } u ; 16 : 17 : void foo(void) 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 28 : 29 : void bar(long n) 31 : 32 : 33 : } 34 : 35 : void baz(double x) 36 : { 37 : 38 : 39 : } 40 : 41 : int main(void) 42 : { bar(l の ; 43 : foo(); 44 : baz(). 71828 ) ; 45 : foo(); 46 : return 0 ; 48 : } く stdio. h 〉 } sa; struct { int tag_b; long b; ) sb; struct { int tag_c ; double c; switch (). sa. tag—a) { case 1 : printf( ” u. sb. b = %ld*n ” , u. sb. b) ; break; case 2 : printf( ” u. sc. c = Xf}n", u. sc. c) ; break; u. sa. tag—a u. sb. b ニ n; u. sa. tag—a F 2 ; u. SC. C ニ X ; if を用いたコーティング List if (). sb. tag—b printf("u. sb. b = XId*n ” , u. sb. b) ; else if (). sc. tag_c ニ printf("u. sc. c ニ Xf#n ” , u. sc. c) ; 120 C MAGAZINE 1992 6

9. 月刊 C MAGAZINE 1992年6月号

0 の中にどの型の値が格納されているのかを 示している。この例て、は unionninfo のメン バがそれぞれまた構造体て、あるため , 一見 非常に複雑に見えるが , 以上の知識を元に 観察すれば , 解読するのはさしたる労力を 要さないだろう。 先に示した List 1 の例て、は , 共用体のメン バに代入されている値の型を無視してアク セスしたために予期しない値を得てしまっ た 0List 3 に示したように共用体の外側を取 り囲む構造体を宣言し , その構造体のメン バとして共用体に現在格納されている値の 型に関する情報を記憶させるようにしてお けば , その値を参照して正しい型を持っ共 用体メンバをアクセスて、きるため , この問 題は回避て、きる。もちろん共用体が保持し ている値の型に関する情報は独立した変数 に記憶してもかまわないのて、あるが , 共用 体を含む構造体を用いることて、 , スマート に管理て、きるのて、ある。 この例の n type のように実際に格納され ている値の型に関する情報のことを「タイプ タグ」 , あるいは単に「タグ ( tag ) 」と呼ぶ場 合がある。これは C 言語の文法上の名前て、は なく , 一般的な用語て、ある。 C 言語の文法上 て、はとくにそのための機能が用意されてい るわけて、はないのて、 , 特別な名前も用意さ れていない。ただし , C 言語て、はたまたま s truct や union, および enum に対して命名す るために用いる識別子のことも「タグ」と呼 ぶのて、 , 両者を混同してしまう可能性があ る。だから C 言語の場合には「タイプタグ」と 呼ぶほうがいいだろう。 元来 tag とは「荷札」という意味て、ある。タ イプタグは共用体の中にどのような型の値 が入っているかを示す荷札ということて、あ る。もちろん , タイプタグの値と , 実際に 共用体の中に格納されている値の対応づけ を維持する作業は , C 言語においてはプログ ラマが自分の責任において行わなければな らない。おそらく大多数のほかの言語て、も それは同じて、ある。それを怠ると異なった Fig. 7 リスト ( ( A ) B ) に対するニ進木表現 car cdr XLISP 2.1 のセルのテータ構造 NIL List 1 : / * node structure * / 2 : typedef struct node { 3 : / * type of node * / char n_type ; 4 : / * flag bits * / char n_flags; union ninfO { / * value * / 5 : struct xsubr { / * subr/fsubr node * / 6 : struct node *(*xs—subr) ( ) ; / * function pointer * / 7 : 8 : / * offset into funtab * / int xs offset; ) n xsubr; 9 : struct xcons ( / * cons node * / 10 : 11 : / * the car pointer * / struct node *xc_car; 12 : / * the cdr pointer * / struct node *XC Cdr; } n xcons : 13 : struct xfixnum { / * fixnum node * / 14 : / * fixnum value * / 15 : FIXTYPE xf_fixnum; } n xfixnum; 16 : struct xflonum { / * flonum node * / 17 : 18 : / * flonum value * / FLOTYPE xf_flonum; } n xflonum; 19 : struct xchar { 20 : / * character node * / / * character code * / 21 : int xc_chcode ; } n-xchar; 22 : struct xstring { 23 : / * string node * / 24 : int xs_length; / * string length * / 25 : uns igned char *xs_string; / * string pointer * / } n_xstring; 26 : struct xstream { / * stream node * / 28 : FILE *xs_fp; / * the file pointer * / 29 : int xs_savech; / * lookahead character * / } n_XS tream ; 30 : 31 : struct xvector { / * vector/object/symbol/structure node * / 32 : int XV Size; / * vector Size * / 33 : struct node **xv data; / * vector data * / } n xvector; 34 : } n_info; 35 : 36 : } *LVAL; List 2 の骨格 List 1 : typedef struct node { / * ノードの型を記憶するためのメンバ * / 2 : char n_type ; char n—flags ; 3 : union ninfO { 4 : こで各ノードの型に応じたセル内部構造の定義が行われる。 * 各メンバはそれぞれが struct である。 6 : } n info; 8 : } *LVAL; 116 C MAGAZINE 1992 6

10. 月刊 C MAGAZINE 1992年6月号

more 型て、あるとしてメモリ上のビットパターン を解釈してしまい , バグとなる。 タイプタグというのは , 共用体にとって 必須て、はないものの , 密接な関係がある。 このためほかの言語て、は共用体 ( に相当する 機能 ) の定義時にタイプタグを同時に宣言し てしまえるような構文を備えている場合も ある。 いつも引き合いに出す PascaI て、は , C 言語 の構造体に相当するレコード (record) 型と いうものが存在する。 Pascal における共用 体に相当するものは , このレコード型の変 形として存在し , 「可変部を持っレコード」 と呼ばれる。またタイプタグのことを Pasc al て、は「タグフィールド」と呼ぶ。 可変部を持っレコード型の定義を用いる と List 4 のような記述がて、きる ocase—of は C 言語て、いえば switch に相当する機能て、ある が , この部分て、宣言されるのがタグフィー ルドて、ある。 List 4 て、は valtype がタグフィ ールドて、あり , タグフィールド自身は整数 型 (integer) て、あるとしている。 valtype が 0 の場合には integer 型の n というメンバ (Pas cal 用語て、はフィールド ) が有効て、あり , va ltype が 1 の場合には array [ 0...9 ] of cha r, すなわち char の 10 要素の配列て、ある a と いうメンバが有効て、あるという定義て、ある。 念のため , Pascal は苦手という人のため に ,List 4 て、は同時に C 言語て、類似の型を宣 言するとどうなるかを示しておいた。 ところて、 , pascal て、はタグフィールドそ のものの型 ( タグ型 ) としては , Pascal て、いう 順序型 ( 大ざっぱにいえば c 言語の integral に 相当する概念て、ある ) て、あれば何て、も用いる ことはて、きる。ただし , JIS-PascaI て、は「選 択定数の値全体がタグ型の値の集合と等し くなければならない」という制限がある (Pa scal には JIS 規格が存在する。ただし実際上 は ISO 規格と同一てある ) 。このため , 厳密 には List 4 の pascal の例は JIS-Pascal< は こて、は説明を複雑にし 文法違反て、ある。 ないためと C 旨衄との対応がとりやすいの 「 4 ロロ Pascal, および C 言語でのサンプルプログラム List pasc でのタグフィールドと可変部を持つレコード型の型定義の例 type record case valtype : integer Of ( n : integer ) ; ( a : array [ 0..9 ] 0f char ) f00 end ; 類似の型を C で typedef 宣言の例 typedef struct ( int valtype; union { int n; char a [ 10 ] ; } f00 ; C , および Pasc 引でのコーティングの断片 List C でのコーティングの断片 typedef struct { int valtype; union { int n; char a [ 10 ] ; } f00 ; f00 S,• int m; void bar() if (). valtype ー m ニ S. u. n; pasc 引でのコーティングの断片 type f00 record case valtype : integer Of integer ) ; ( a : array [ 0..9 ] 0f char ) ; end ; var f00 ; S m : integer; procedu re bar ; begin if s. valtype = 0 then end; ANSI C ー more 117