ポインタ - みる会図書館


検索対象: 月刊 C MAGAZINE 1991年12月号
42件見つかりました。

1. 月刊 C MAGAZINE 1991年12月号

N mo なお , 上て、述べた「キャストて、きるのはス ポインタのビット数のほうが longint よりも するだろう。 , こて注意してほしいことは , 何が①の カラー型の内部にかぎられる」という規則に 多いというようなアーキテクチャもあり得 キャストに属し , 何が②のキャストに属す ない話て、はない は実は例外がある。それは voidt' ある。 vo id はスカラーてはない。しかしながら , 任意 るかは処理系 ( その多くは , 実行環境 = 実行 ただし , ( すてに本連載て、何度か述べたこ のスカラー型の式を void にキャストてきる。 される CPU) に依存するということてある。 とだが ) 定数 NULL は例外て、ある。 NULL は オプジェクトへのポインタ , 関数へのポイ たとえば , char へのポインタを int へのポイ それどころか , スカラー型以外の式を void へとキャストすることもて、きる。実際には ンタへとキャストする行為は , 大部分の処 ンタを含めてすべての型へのポインタに対 v 。 id へのキャストが意味を持つのは , 関数 理系てはビットパターンを変化させない作 して暗黙のうちに変換され , そしてその変 を呼び出して , その関数が返してきた値を 換後も期待したとおりの意味を保ち続ける 業て、ある。 「捨てたい」意志を表現したい場合てある。 しかし , 特殊なアーキテクチャのマシン ことが保証されている。このため , 少なく その場合 , たとえその関数が戻してきた値 上ては両者はビットパターンレベルの変換 とも ANSIC においては NULL をキャストす が構造体て、あっても , void へとキャストて るのはまったく無意味て、ある。 が必要かもしれない。逆に int を float へとキ きる。 ャストした場合には , 先ほども述べたよう しかしながら , これは NULL が定数とし に通常の CPU て、は両データ型は内部表現が て記述されている場合にかぎられる。たと また , void を void へキャストすることも ( 意味があるかどうかは別として ) 理論上は まったく異なるのて、あるが , たまたま両者 えばポインタ変数 p があって , それに NULL 許されるはずてある。しかしながら , もち が代入されていた場合 , その p の値を別なポ は同じ表現になっているというようなマシ ンがあるのは事実てある。 ろん , void の値を void 以外の別の型にキャ インタ型にキャストしたとすると , それが そのまま NULL という値て、あり続けるとい ストするなどということは許されない。 vo だが , プログラミングてもっとも注意が id とはそもそも「値がない」のて、あるから , そ 必要なのは内部表現の変化に伴って情報が う保証はない 移植性を気にする場合には , 関数へのポ れをキャストして別の値を作り出すことは 失われたり , 余計なものが付加されたりす インタとオプジェクトへのポインタ ( すなわ 無から有を生じることになるからてある。 る場合があることてある。 int を short へ , 10 ng を int へと変換した場合には , 当然ながら ち通常の変数へのポインタ ) との間にすら相 毛種類のキャスト 一部のビットが失われる危険がある。逆を 互変換の保証はなされていないことにも注 行えば , 符号の有無に従ってゼロ拡張がさ 意したい。たとえば 80X86 のコンパイラの場 れるか , 符号拡張がされることがある。も これまて , キャストのことを漠然と「型変 ロ , ミディアムモデルとかコンノヾクトモデ ちろんたとえば long と int のサイズが同じと 換」と述べてきたが , コンピュータの内部表 ル , あるいはコンパイラベンダによっては 現という立場から考えると , キャストには いうような処理系て、は , 両者の間のキャス データモデルとかプログラムモデルと呼ぶ トては内部表現は何も変化しないことにな 場合もあるが , それらのメモリモデルては , 次の 2 種類に分類てきる。 ①内部表現には変化がなく , ただ型 関数 ( コードセグメント ) に対するポインタ る。 のビット幅と , オプジェクト ( データセグメ の解釈だけを変えるキャスト もっとも注意が必要なのはポインタ型に ②内部表現を変更するキャスト 関連するキャストてある。まず , int との相 ント ) に対するポインタのビット幅が違うの て、ある。そのような場合には当然ながら相 , こては内部表現 = ビットパターンと思 互変換は移植性を考える場合には必要以上 互のポインタの変換を行うと予期せぬ結果 っていただいてもよい。たとえば int を unsi に期待しないほうがよい。かって K&R lst の時代には ( 暗黙裏に ) int とポインタは相互 gnedint へとキャストした場合 , ビットパタ を得ることになる。 ーンは変化しない。一方 char を int へと変換 に型変換可能て、あるとされていた。しかし とくに , 関数へのポインタは , 不用意な するような場合には , ビットパターンその ANSI てはそのような保証はない。ある型へ 値が誤って設定されてしまうと一発て、暴走 もののサイズが変化 ( 増加 ) する。 のポインタを int へとキャストし , 再びポイ を招く原因となり , たいへん危険て、ある。 ただし , 元の char のデータのビットパタ ンタへとキャストしたときに , 元のポイン このため , 最近の 80X86 用コンパイラの一部 ーンはそのまま保存されるわけてある。さ タとまったく同じ値が復元されるという保 には , そのようなポインタ間のキャストが らに , int を float へとキャストした場合に 証はないのてある。仮に int の代わりに long 行われると警告を発するものがある。コン は , 一般には元のビットパターンとはかけ int を用いたとしても , ポインタとの相互変 パイラの出力をよく理解して使いこなすよ 離れたビットパターンへと内部表現が変化 換に保証がないということは変わらない ANSI C ー more 115

2. 月刊 C MAGAZINE 1991年12月号

変数名が不要て、 , 抽象的な型の情報だけを 指定すれば十分て、ある。これが「抽象宣言子」 の名前のゆえんだろう。 抽象宣言子の構文定義を前回述べた宣言 子 (declarator) の構文定義と比較するとわか るのだが , 抽象宣言子は宣言子から識別子 ( ident ⅲ er ) を取り去った形をしている。 般に , かなり C のコーディングに慣れた人て、 も , 複雑な型を指定する場合には型表記 ( の 抽象宣言子部分 ) を書くのは困難て、ある。そ れよりも一度望む型の変数を宣言するつも りて、宣言を書き下して , そこから変数名を 除去するというプロセスを経たほうがすば やく正確に書くことがて、きるのて、はないか Fig. 2 AN 規格における type ー name の構文定義 と思う。 TabIe 1 に具体例を挙げる。 これらはすべて , 左の列が変数の宣言 ( こ こには関数の宣言はひとつもない ) て、あり , その右側がその変数と同じ型を示す type ー n ame て、ある。右の宣言の型をそれぞれ解釈し てみて , それを表す型表記を直接得るのと , 一度左の変数宣言を思い浮かべてから変数 名を除去するというプロセスを実際に試し どちらが容易かはすぐにわ てみてほしい かると思う。ただし , C の本当のエキスパー トはその作業をほとんど無意識のうちに行 ってしまうため , あたかも直接右の型表記 を考え出すかのように自覚しているかもし れない type—name : specifier-qualifier-list abst 「 uct-declarator abstruct-declarator . po i nte r pointer direct-abstruct-declarator 0 が di rect—a bstru ct-decla 「 atO 「 : ( abst 「 uct-declarator ) 0 が direct-abstruct-declarator ( parameter-type-list 0 が direct-abstruct-declarator [ constant—expression ] 0 が 型表記とは : ・指定子修飾子リストと抽象宣言子を並べたものである 抽象宣言子とは : ・ポインタか 0 が ・省路可能なポインタの後に直接抽象宣言子を並べたものである 直接抽象宣言子とは . ( と省路可能なバラメータタイプリストと ) を並べたものである ・省路可能な直接抽象宣言子と [ と省路可能な定数式と ] を並べたものか ・省路可能な直接抽象宣言子と ・ ( と抽象宣言子と ) を並べたものか more 付加することは別に問題なく許されること 言においては , 次のように冗長なカッコを さてここて , 「 int へのポインタ x 」という宣 int * ( ) けだから , 次のようになるだろう。 記は , こから識別子 f を取り去ればよいわ 「 int へのポインタを返す関数」という型表 int * f ( ) ; 宀言するとこれは次のように書ける。 を与えない , いわゆるオールドスタイルて、 を考えてみよう。引数の型情報 という旦 て、は次に , 「 int へのポインタを返す関数 f 」 int * た次のものになる。 こから識別子 x を取り去っ 記を得るには , したがって , 「 int へのポインタ」という型表 int *X 言て、きる。 う宣言を考えてみよう。これは次のように て、ある。たとえば , 「 int へのポインタ x 」とい 場合には曖昧さが生じてしまうということ 則の宣言子のなかに冗長なカッコがあった て、ある。その問題とは , 識別子を取り除く 析を行うことて、曖昧さがなく解釈てきるの 次に述べる問題を除けば , 簡単な構文解 心配はいらない だが 不安に駆られてしまうかもしれない れたのかがわからないのて、はないかという されていたのか , どこから識別子が省略さ 型」という欄を見ると , どこに識別子が指定 e のほう , つまり右側の「宣言された変数の の配列」を表す型表記てあるが , type-nam ポインタを返す関数へのポインタの 10 要素 ところて、 ,TabIe 1 の最後の例は「 int への TabIe 1 抽象宣言子の構文定義例 宣言された変数の型 ( * ) (int) * [ 10 ] [ 10 ] を思いだそう。 int * (x) ・ そうすると , こから識別子を取り去れ i nt i nt i nt i nt i nt i nt i nt 変数宣言 b [ 10 ] : * e [ 1 0 ] : i nt int i nt i nt i nt i nt i nt ば「 int へのポインタ」という型を表す型表記 にならねばならないのだが , それは実際に は次のようになり , int * ( ) 先に上げた「 int へのポインタを返す関数」と ANSI C ー more 113

3. 月刊 C MAGAZINE 1991年12月号

関数を呼び出すようにしている ( なお , Bor 構を使いやすくし , 効率のよいプログラム landC 十十の C モードて、は , 生成コードに変 を作成て、きるような機能も考えられてきた。 化はなかった ) 。 Turbo C て、の es* インタや MS-C Ver. 6. これまて、も Turbo C の huge;tk インタは , 0 の based7* インタがそれて、ある。 オフセット値を 0 ~ 0xF に正規化すること これらは , far, huge ポインタの 32 ビット て、 , ポインタから正の方向に対して余裕が ポインタを用いることなく , IM バイトのメ て、き , 構造体のアクセスによってセグメン モリ空間をアクセスて、きる。 near ポインタ トをまたぐ危険性は少なかったのだが , C 十十 同様 , もつばらオフセット値を操作するの て、は継承によってクラスが巨大になった場 だが , そのときのセグメントの値を自由に 合や , 配列上にクラスが配置されクラスへ 変更て、きるようにしたものて、ある。 のポインタがセグメント境界に近くなった MS-C の based'* インタを例にしてみよ 合に比べ , 1 . 5 ~ 2 倍程度の高速化を実現す 場合に対処してか , よりいっそう安全な方 フ。 法が取られるようになったようて、ある (Fig. Turbo C て、も , int es * off ; と , es* segment seg int based(seg) *off ; インタを用いれば , 同様の高速化が可能て、 LatticeC て、は , セグメントをまたがない と記述する。 seg という変数がセグメント値 あるが , off のアクセスの直前に , いちいち ように構造体にパディングを当てる機能が を保持し , off という変数がそのセグメント ES レジスタを設定しなければならない点が ある。 に対するオフセットを表す。ポインタ off を 煩わしい。 Turbo C 十十から設けられた s 通常のポインタのように使えば , 自動的に eg ポインタは , 前記 segment 変数と似てい セグメント seg が仮定され , seg : off のアド るが , 32 ビットのアドレスを得るためには レスをアクセスて、きる。 seg, off とも 16 ビッ 適当な near,'tk インタと陽に足し合わせる必 このように高度な処理を行う huge ポイン ト幅て、あり , とくに off をレジスタに割り付 要がある。 タをサポートする一方て、 , セグメントの機 けることて、 , far や huge;* インタを用いる場 char seg * seg char near *Off Fig. 2 Borland C 十十 Ver. 2.0 によるコンノヾイル結果 huge ポインタのテストプログラム List 1 1 : struct hiyohiyo ( int fumifumi[20] ; 2 : 3 : char takoika; 4 : ) src[10]; 5 : 6 : void f00 ( ) 8 : struct hiyohiyo huge *p; 9 : 10 : p->takoika = 0 : 11 : } 3 ) 。 basedfi インタ * (seg 十 Off) なお , このようなポインタを用いる場合 , 確かに IM バイトのメモリ空間をアクセスて、 きるが , セグメントをまたぐ とが不可能 なこと , すなわち , あくまて、 64K バイト以内 の操作にかぎることに注意が必要て、ある。 したがって , VRAM の操作や , EMS バッフ アの操作などに適しているて、あろう。 p = src , mov word ptr [bp-2] , ds mov word ptr [bp-4] , offset DGROUP src p->takoika : 0 : mov dx, word ptr [bp-2] mov ax, word ptr [bp-4] p->takoika ニ 0 , アドレス計算のために内部関数を呼び出す XO 「 CX, CX mov bx, 40 call near ptr N PADD@ ー OV bX, ax ー OV es, dx 0 を代入する Cbx] , 0 mov byte ptr es . EMS, プロテクトメモリの活用 EMS やプロテクトメモリを活用するひと つの方法として , Turbo C 十十 , Borland C 十十のオーバレイマネージャ VROOMM (Virtual Runtime Object-Oriented Mem oryManager) という機構に触れておこう (Fig. 4 ) 。メモリに入り切らない大きなプロ グラムをオーノヾレイモシュールに分割して , 38 C MAGAZINE 1991 12

4. 月刊 C MAGAZINE 1991年12月号

凡整数型とは列挙 , char, short int, int, と i-XList 1 は構造体の変数を別な構造体の いう型表記と区別不能になってしまう。 の問題が生じた原因はいうまて、もなく元の longint からなる。このようすを示すのが F 型へとキャストしようとしているため , コ ig. 3 てある。なお , Fig. 3 て、は省略している 宣言子を構成する場合に冗長なカッコを付 ンパイルエラーになるはずて、ある ( もし工ラ 加してしまった点にある。宣言子から識別 が , 凡整数型のうち列挙を除いた各型はさ ーにならない処理系があれば , それは ANS 子を取り去って抽象宣言子を構成する場合 らにそれぞれ signed/unsigned に分かれてい I から拡張されている ) 。 には , 元の宣言子の冗長なカッコをあらかじ る。 char< は plain char は別扱いて、 plain/s もっとも , 単にこのように異なる型の構 め排除しておく必要がある。そうて、ないと igned/unsigned の 3 種類の char があるが , そ 造体の変数へ ( どうしても ) 代入したければ , 思わぬ型を表すことになってしまうのて、要 れ以外の int 族て、は signed はデフォルトてあ ポインタを通じたキャストといういささか 注意て、ある。 り , たとえば singed int と int はまったく同じ 危ない手段があるにはある。たとえば List 1 て、あると解釈される。 の zot 関数を List 2 のように修正すればよ キャストの適用限界 一般に , スカラー型以外の型にキャスト を適用することはて、きない。またスカラー このように , いったん & 演算子て、アドレス , こて、キャストの限界に関して確認して 型をキャストしてスカラー型以外の型へと を取り , それを目的とする型へのポインタ おこう。本稿の冒頭て℃のキャストは非常に 変換することもて、きない。すなわち , にキャストし , さらに間接参照演算子 * を 強力て、あると述べた。しかしながら万能て、 キャストできるのはスカラー型の内部 適用して望む変数へと代入するというのは にかぎられる はない というよりも , 明らかに無意味な 構造体の場合にかぎらず使うことがてきる。 キャストや結果が極めてマシン依存になる のて、ある。スカラー型以外の型の代表例は これは知る人ぞ知る , 裏技に近いコーディ 集成型 (aggrigate type) と関数型 (function だろうキャストなどは許されていない ングテクニックて、ある。しかし , この技は 一般に相互にキャスト可能な型のクラス type) て、ある。たとえば構造体 (struct) の値 無闇に使うと移植性が確実に低下する。ワ が存在する。それは俗にスカラー型と呼ば をキャストして配列 ( という値 ) にするとか , ードアライメントや , 構造体内部のパッド れるものて、あり , ポインタ型と算術型から あるいはポインタ ( て、ある値 ) に変換すると の問題などが微妙に絡むからて、ある。だが 構成される。算術型はまた凡整数型と浮動 か , その逆とかは許されない。構造体同士 それは移植上の問題なのて、 , ここて、はこれ 小数型から構成されている。 てすら , 規格上キャストは許されない 以上深入りはしない 構造体から構造体への誤ったキャスト Fig. 3 スカラー型の構成 List 列挙型 Char 0 8 +> れⅡ 0 ・ 1 ・ 1 1 0 一 1 り 3 4 - -0 6 7 8 9 0 1 0 な 00 4 - -0 6 凡整数型 short int int long int float double long double オプジェクトへのポインタ 算術型 浮動小数型 スカラー型 構造体のポインタへのキャストを使う List 1 : void zot(void) b = *(struct bar *)&f; 3 : ポインタ型 関数へのポインタ 114 C MAGAZINE 1991 12

5. 月刊 C MAGAZINE 1991年12月号

るわけて、す。 公開導出クラスと公開基本クラス さて , 公開導出クラスは , 公開基本クラ スとの間に特殊な関係が認められています。 それは , 導出クラスが , その基本クラスの ポインタ変数や参照型変数に導出クラスの ポインタなどの値を明示的な型変換を行わ ずに代入することがて、きる点て、す。 Fig. 6 て、 は , クラス Win を基本クラスとして公開導出 クラス Win A を導出し , さらに Win A を基 本クラスとして Win B を導出しています。 て、すからクラス Win のポインタ変数に , クラ ス Win A のポインタが型変換なく代入て、き ます。同様に Win A の参照型変数にクラス Win B が代入されます。また , クラス Win のポインタには , Win B のポインタを型変 換なく代入することが許されます。これは , クラス Win B がクラス W ⅲの一種と考えら れるからて、す。 特殊な関係による問題点もあります。 Fi g. 6 を拡張した , Fig. 7 を見てください。 F ig. 7 て、は , 関数 F が引数としてクラス W ⅲの ポインタを受け取ります。関数 F 内の手続き においては , メンバ関数 print を呼び出して います。このとき , 実際に引数の指してい るクラスは , 何て、あるかがわかりません。 関数 F を呼び出す側て、クラス Win B のポイン タを渡しています。プログラマとしては , 関数 F 中の手続きにより引数として渡された クラス Win B のメンバ関数 print が , 呼び出 されることを望みますが , うまくいきませ ん。このままて、は , 関数 F のなかて、引数とし て渡されたポインタが , どのクラスを指し ているかを判定しなければなりません。そ のためには , クラスのメンバとしてクラス のタイプを識別するデータやメンバ関数が 必要になります。その結果 , クラスは複雑 になりプログラム中の手続きも多くなって しまいます。 仮想関数 128 C MAGAZINE 1991 12 Fig. 7 仮想関数 class Class Class Win * Wi n -A Win-A& Win-B Wi n public: Win-A Win-B W-ap; wb; public public wp = &wa; w-ap = wb; wp = &wb; void F( Win*p ) Class VV ・ in p->print(); public: パイル時 ( 実際はリンク時 ) に決定される静 この呼び出し , 呼び出されの関係は , コン など一般のプログラミング言語において , るという意味て、動的束縛と呼びます。 C 言語 び出しと実際に呼び出される関数が決定す これを , プログラムの実行時に関数の呼 が選ばれるのて、す。 より , プログラムの実行時に呼び出す関数 引数とクラスの結合情報を付加することに す。実際には , コンパイラが , Fig. 7 に示す て関数が識別されるということを意味しま こて、いう自動的とはコンパイラによっ を持っています。 メンバ関数を自動的に識別し呼び出す機能 ンタが指している実際の導出クラスが持っ います。仮想関数は , 基本クラスへのポイ は , 仮想関数と呼ばれる機能が用意されて このような問題を解決するために C 十十て、 void print() い Win { ・ Win-A { ・ ⅵ u 引 void print() い 随するいくっかの機能について解説しまし クラスを使う技法として , クラス階層と付 以上今回は , 複合クラスと同様に複数の ることになります。 ラス Win B のメンバ関数 print が呼び出され は , 自動的に基本クラスから継承されたク 想関数とすることて、 , 関数 F 中の手続きて、 ります。基本クラスのメンバ関数 print を仮 tual をつけることて、その関数が仮想関数とな Fig. 7 下段にあるように , キーワードⅵ r 宣言します。 うな機能を持っ仮想関数は , 基本クラスて、 書くことがて、きるようになります。このよ 構によって型に関して柔軟なプログラムを 的束縛という機構を持ちます。動的束縛機

6. 月刊 C MAGAZINE 1991年12月号

ング添削 プログラ 敗したときに , 無理矢理書き込みなどの操 =p [ 9 ] * ( p 十 9 ) 作を行うと , 予想もしない領域を破壊して しまうことになります。 List 6 のように記述 の図式が成り立ちます。このように , 動的 すべきて、しよう。 に確保した領域を配列のように使えるよう になります。 List 2 のプログラムは , List 5 のようにし 領域の確保に成功したかどうかを必ず なければなりません。しかし , この関数を 判断し , 失敗した場合はそれなりの対 何度も呼び出すと , 実行時工ラーとなるて、 処をせよ しよう。なぜならば確保した領域をまった く解放していないからて、す。 Fig. 2 配列の確保 p [ 0 」 p[1J p[2J pC3] pC4] pC5] pC6] p[7] pC8] pC9] ・まとめ 自分で動的に確保した領域は , 責任を 今回は , 一般的な C の話をしました。次回 持って解放せよ は , 具体的に QuickC と QuickBASIC とのリ ンクのメカニズムなどについて考えること また確保に失敗したときの対策がほどこ されていないのも問題て、す。領域確保に失 にします。 List 2 に手を加えたプログラム TabIe 1 メモリの動的確保・解放を行う関数の概路 calloc List 1 : #include く stdlib. h 〉 2 : 3 : void rank(int *a, int *r, int int m) int i; 5 : 6 : int *COunt; 7 : count = cal 10C ( m + 2 , sizeof(int)) ; 8 : for (i ニ 0 : i ← m; i + + ) count[i] 9 : for (i = 0 ; i く n; i + + ) count[aCi]J + + ; 10 : countCm + 1] 11 : for (i = m; i 〉 0 ; i--) count[i] + = countCi + 1 ] ; 12 : = countCa[i] + 1 ] ; for (i = 0 ; i く n; i + + ) rCi] 14 : } VOid * callOC (size t n, size t size) ・ 大きさが size バイトである n 個のオプジェクトの領域を確保す 解説 る。確保が成功した場合は , 領域のすべてのビットを 0 で初期 化し , その領域の先頭へのホインタを返す。失敗した場合は , NULL を返す malloc 解説 void * malloc (size t size) ・ 大きさが size バイトであるオプジェクトの領域を確保する。確 保が成功した場合は , その領域の先頭へのポインタを返す。 失敗した場合は , NULL を返す List 5 に手を加えたプログラム List Ø * ・ 1 1 人 0 っ 0 4 ′ 0 6 ロー 8 9 0 1 り介 0 4- 0 ^ 0 ー 8 1 よ 1 よ 1 よ、よ , よ、ー 4 , 1 1 ム・ー亠 free 形式 void free (void * p) 解説 f 「 ee は p が指す領域を解放する。ただし p が N ULL のときは何も しない。 p は calloc, malloc, 「 e 訓 oc によって確保された領域 へのポインタでなければならない int i; int *count; count = calloc ( m + 2 , sizeof(int)); = NULL) if (count return( の ; i + + ) count[i] for (i = 0 ; i く = m; i + + ) countCaCi]] + + ; for (i = 0 ; i く n; countCm + 1] ー ) countCi] + = count[i + 1 ] ; for (i ニ m; i > 0 : 1 ー i + + ) rCi] ニ countCaCi] + 1 ] ; for (i ニ 0 ; i く n; free(count) ; return(l) ; 136 C MAGAZINE 1991 12

7. 月刊 C MAGAZINE 1991年12月号

ング添削 プログラ int 型整数の配列を動的に確保する int 型整数を動的に確保する List List 1 : #include く stdio. h> 2 : #include く stdl ib. h 〉 3 : 4 : int main(void) 6 : int *p; 7 : P = 5110C ( 10 , sizeof(int)); 8 : = NULL) 9 : printf ( " 確保に失敗しました” ) ; 10 : else { 11 : / * あたかも p [ 0 ] ~ 矼 9 ] という配列 * / / * が存在するかのように使える ! * / 14 : return( の ; 15 : 16 : } 1 : #include く stdio. h> 2 : #include く stdlib. h 〉 3 : 4 : int main(void) 6 : int *p; 7 : P = calloc ( 1 , sizeof(int)); 8 : if (p = NULL) 9 : printf ( ”確保に失敗しました” ) : 10 : else { 11 : / * あたかも *p という変数が存在 * / 12 : / * するかのように使える ! 13 : 14 : 15 : 16 : } return( の ; して配列を確保するのは , 文法的には不可 一般的にはヒープ領域と呼びます。 能て、す。 もっとも , いくつかの処理系では文法が calloc 関数を用いて int 型のオプジェクト しかし , 単一の変数を動的に確保したと を動的に確保する例を List 3 に示します。 c 拡張されており , そのような宣言を許すも ころて , それほどメリットはありません。 a110C の呼び出しによって確保された領域へ のもあります。さらに ( いつのことかわかり マンティスさんのプログラムの問題点をク のポインタが p に代入されます。もし p が N ませんが ) AN 引規格が改訂されるときには , リアするためには , 「配列』を確保しなけれ ULL て、あれば , 確保てきなかった旨のメッ このような配列宣言は許されるようになる ばなりません。 セージを表示します。 と思います。 TabIe 1 からもわかるよラに , calloc や m 確保に成功した場合のメモリの状態を Fi しかし現存するほとんどの処理系て、は不 a110C は , とくに配列を確保するといった機 g. 1 に示します。ここて、斜線の部分が確保さ 可能て、す。いさぎよくあきらめましよう ! 能は持っていません。しかし List 4 のような れた領域て、す。この領域へのポインタを p に プログラムを用意することによって配列が ■メモリの動的確保 代入します。ポインタに間接参照演算子 * 確保て、きます。 を適用すると , そのポインタの指す領域の List 3 から List 4 へのプログラムの変更点 別名 ( 工イリアス ) となるのて、したね。した 「あきらめる」といっても「通常の配列と は , calloc の第 1 引数の値を 1 から 10 に変えた がって , 確保した領域は , 「あたかも * p と して宣言すること』をあきらめるのて、あっ だけてす。 いう変数があるかのように』アクセスする て , プログラムの開発を止めてしまえとい これだけの変更て、 , 配列を確保すること うことて、はありません。別の方法を使わな ことがて、きます。 がてきます。 Fig. 2 を見ながら考えましよ なお , 動的に確保される領域のことを , ければなりません。 C 言語のオプジェクト は , 自動記憶寿命を持つものと静的記憶寿 Fig. 1 変数の確保 確保された領域へのポインタが p に代入さ 命を持つものがあります ( 「明解 ANSIC 言 れます。この領域の先頭の部分は , * p とな 語入門講座』 7 月号 ) 。 ることは , もうわかっていますね。ここて , さらに動的記憶寿命とも呼ぶべきものが 配列とポインタの類似性を思い出してくだ あります。 ANSI 規格て、は , calloc, ma110 さい。ポインタに対して , [ ] 演算子を適 c などのライプラリが用意されています。 用することにより , あたかも配列のように れらの関数を呼び出すことによって , プロ アクセスすることがてきましたね。 グラム実行時に , 好きな大きさのメモリを したがって , 確保することがてきます。 TabIe 1 に , それ らのライプラリの機能の概略を示します。 * ( p 十 1 ) 単一オプジェクトの確保 配列の確保 =p [ 1 ] プログラミング添削 135

8. 月刊 C MAGAZINE 1991年12月号

明解 AWC 言語 入門講座 ( * x). height せん。したがって , ポインタをやりとりし その hiroko 関数を gstudent 構造体に対応 と奇妙な形のものが出現します。 Fig. 4 を見 なければいけないということも理解してい するように書き換えましよう。ひろ子さん ながら考えましよう。 hirok 。関数が引数と ますよね。 8 月号の hiroko 関数は身長のみを は太った人も嫌いなのて、 , 体重が 80kg を超 して受け取る x は , s を指すポインタて、す。 扱っていたのて、 int へのポインタを受け取り えていたら , その子の体重を 80kg にしてし ポインタに * 演算子を適用することにより , ましたが , 今回は gstudent 構造体へのポイン まうという能力も持っています。 List 6 がそ そのポインタの指すオプジェクトの実体を タを受け取ります。 のプログラムて、す。 表すのて、したね。したがって , * x は s の別 C 言語て、は , 値による呼び出ししかて、きま 11 行目には 名となり S = * X 学生テータのソート の図式が成り立ちます。 s の身長メンバは s. height と表しますから , * x の身長メンバに アクセスするには , 同様に * x. height とすれ ばよいように思えます。しかし . 演算子の優 先順位は * 演算子より高いのて、 ( 本連載 9 月 号参照 ) , * (). height) と解釈され , 文法的 に正しくないものとなります。よって ( * x). height のように必ず ( ) が必要となります。 しかし , ( * x ). height と書かなければなら ないのて、はめんどうてすね。そもそも ( ) の 書き忘れを起こしがちてす。そこは簡潔さ が売りものの C 言語て、すから , その点はぬか りはありません。 (*x). height は x— >height とも記述てきることになっています。ー > 演 算子は矢印の形をしていますから , 通常は アロー演算子と呼びます。 List く stdio. h 〉 1 : #include 2 : #include く string. h 〉 3 : 4 : #define MAX 5 5 : 6 : struct gstudent { char name[20] ; / * 氏名 * / / * 身長 * / height; 8 : i nt / * 体重 * / float weight; 9 : / * 奨学金 * / long schols ; 10 : 11 : } ; 13 : void swap(struct gstudent *X' struct gstudent *y) 14 : { S truct gstudent te 叩 : 15 : temp *y = temp; ミ 22 : void sort(struct gstudent int n) 23 : { int k = n-l; 24 : while (k 〉 = の { 25 : int i, j 26 : for (i = 1 ; i く = k; i + + ) if (data[i-l]. height 〉 data[i]. height) ( 28 : 29 : 1 swap( &dataCi], &dataCj]) ; 30 : 31 : 32 : 33 : 35 : 36 : int main(void) 38 : int i; struct gstudent dat[] = { 39 : 178 , 61.0 , 8000 の , / * 佐藤宏史君 * / { ” SatO ” 40 : { "Sanaka", 175 , 60.5 , 70000 } , / * 佐中俊哉君 * / 41 : / * 高尾健同君 * / { "Takao", 173 , 80.0 , 0 } , 42 : 165 , 72.0 , 7000 の , / * 平木翫 ke 君 * / { ”翫 ke ” 43 : , 179 , 77.5 , 7000 の , / * 真崎宏孝君 * / { ” Masak i ” 44 : 45 : 48 : 49 : 50 : 51 : 52 : ポインタ ptr が指す構造体のメンバ mem は ( * ptr). mem ではなく , ー > 演算子 ( アロー演算子 ) を 用いて ptr— >mem と簡潔に記述できる sort(dat, MAX) ; 身長体重奨学金 " ) ; puts ( " 氏名 puts ( " ー for (i = 0 ; i く MAX; i + + ) printf( ” X-8s % 6d % 6.1fX71d*n", datCi]. name, dat[i]. height' datCi]. weight, dat[i]. schols) : puts ( " ー return( の ; 【問題 3 】 List 4 の setxyz 関数を void setxyz (struct xyz * xyz, int x, int y, int z) ・ の形式に書き換えよ。 第 明解 ANSI C 言語入門講座 1

9. 月刊 C MAGAZINE 1991年12月号

g032 a. out のように , g032 または debug32 のコマンドラ イン引数として指定することによって実行 されます。 ところが , この a. out 形式のファイルの頭 に , djgcc に付属している stub. exe をつけて copy /b stub. exe 十 a. out file. exe とすることによって , いちいち g032 を起動 しなくても , 単に file を実行するだけて、起動 て、きるようになります。 しかし , この標準の stub. exe には , rswa p と共存て、きない , という不便な点がありま rswap は , 本誌 ' 91 月号のフィルタ系言語 す。 から g032 が起動される際には , その exe ファ ところ力す , stub. exe が入った exe ファイル いうものて、す。 をもとの状態に戻して処理を再開する , と ほかのコマンドの実行が終わると , プロセス メモリ上のプロセスをディスクに追い出し , 中からほかのコマンドが起動されたときに 特集て、も解説がありましたが , プログラム イルのコマンドライン引数へのポインタが , g032 へのコマンドライン引数として渡され ます。具体的には , g032 !proxy 引数の個数引数のオフセッ ト引数のセグメント というコマンドラインて、 g032 が起動されま す ( オフセットおよびセグメントは 4 桁の 16 進数 ) 。 g032 の main( ) は , !proxy というコ マンドライン引数を見つけると , 残りの引 数をこの手順に従って解釈します。 このように , g032 はこのポインタが指し * 7 Stallman が忘れるく らいだからもっと複雑なこ とかもしれませんが , 考え られるのはこういうことて、 す。 リターン命令が 1 個しか 生成されないと , 複数の ret urn 文があったときに , ひと つのリターン命令のアドレ スにあちこちからジャンプ してくるようなコードにな るのて、 , 分岐命令のぶんだ け速度が落ちる , というデ メリットがあります。 リターン命令をそれぞれ の return 文に対して生成す ると , この分岐がないぶん だけ速くなりますが , リタ ーン命令のコードが長い場 合は , 全体のコードが長く なってしまいます。 * 8 80386 用 GCC て、は , のほかに -mregparm という 関数引数のレジスタ渡しを 行うためのオプションも用 ー意されていますが , これは レジスター度しコン , ヾンショ ンの性能評価用て、 , 実用に なる保証はありません。 80386 用 GCC て、指定て、き る -m オプションをまとめて おきます。 ー m80387 -msoft-float -m rtd -mnortd —mregparm —mnoregparm * 9 PCC などの従来の C コ ンパイラて、は , 関数の値が 構造体や共用体の場合 , 値 をメモリ上の静的領域に置 き , その場所へのポインタ をレジスタて、返していまし た。 GCC て、は , たとえ構造 体や共用体が関数の値とな っていても , その長さが 1 , 2 , 4 , 8 バイトて、あればレジ スタを使って値を返します。 74 C MAGAZINE 1991 12 定義に厳密て、ある必要があるプログラムが少数 ある。このようなプログラムに・一 ffloat ー store ' を 使用するとよい —fno—asm' asm, inline および typeof を予約語として認識 しない。これらの語を識別子として用いること がて、きるようになる。・一 fno ー asm ' を指定した場 inline および typeof_ を代 ロ , -fstrength-reduce' ておいて一度に pop する。 引数を関数呼び出し数回分スタック上にまとめ ックから pop する。通常コンノヾイラは ( 最適化時 ) 関数呼び出しの終了後すぐに関数引数をスタ -fno-defer-pop わりに使用することがて、きる。 る ( * (1)0 私はこのフラグによる違いの報告に興 合フェーズて、余分なレジスタロードは消去され がある。共通部分式て、なかった場合には命令複 候補となるため , よいコードを生成する可能性 スタにコヒ。ーする。メモリ参照が共通部分式の メモリオペランドを演算に先立って必ずレジ —fforce—mem' 違いの報告に興味がある。 い場合とがあるだろう。私はこのフラグによる に使用した場合 , よい結果になる場合とならな 合を許可する。・ -fcombine-regs' は・一 0 ' ととも 命令複合パスによるレジスタコヒ。ー命令の複 —fcombine—regs' プ変数消去の最適化を行う ( * 10 ) 。 ループストレングスリダクションおよびルー -fforce-addr' 味がある。 によいコードを出す可能性がある。私はこのフ -fforce-mem' と同様 ジスタにコヒ。ーする ( * 12 ) を メモリアドレス定数を演算に先立って必ずレ ラグによる違いの報告に興味がある。 -fomit-frame-pointer' 必要ない関数て、はフレームボインタを作成し -fomit-frame-pointer' によってフレーム ポインタの保存 , 初期化 , 復帰のための命令を 節約て、き , また多くの関数に対して余分のレジ スタを提供する。また , デバッグが不可能にな る ( * 13 ) Vax のようなマシンて、はこのフラグは効果が ない。標準関数呼び出しシーケンス中にフレー ムボインタ処理が含まれているため , フレーム ポインタが存在しないふりをしても得るものが ないのて、ある。マシン記述マクロ FRAME PO 爪 TER— REQU IRED によってターゲットマシンが tfomit-frame-pointer' をサポートするかどうか を記述する。 —finline—functions' 簡単な関数を呼び出し側関数にインライン展 開する。コンパイラは関数をインライン展開の 対象とするかどうかヒューリスティックを用い て決める。 ある関数への呼び出しがすべてインライン展 開され , かっその関数が ' static ' 宣言されている 場合には , 通常その関数のアセンプラコードは 出力されなくなる ( * 14 ) 。 —fcaller—saves' 関数呼び出して、保存されないレジスタに値を 割り付けることを許す。このために関数呼び出 しの前後に余分の保存 / 復帰命令を置く。このよ うな割り付けは通常よりよいコードになると田 われる場合にだけ行われる。 このオプションはあるマシンて、はデフォルト て、有効になっている。とくに関数呼び出して、常 に保存されるレジスタが設定されていないマシ ンはそうて、ある ( * 15 )

10. 月刊 C MAGAZINE 1991年12月号

A 新 IC 言ロロ 明解 柴田望洋 初級 9 級 8 級 7 級 6 級 5 級 4 級 3 級 2 級 1 級初段 2 段 く第 9 回〉構造体と共用体 前回までの内容は正しく理解できましたか。今回は , ポインタとともに C 言語の難 関のひとつであるといわれる構造体の解説をします。構造体は , その必要性や本質 を理解すれば , それほど難しいものではありません。しつかりと勉強しましよう。 List 整数のソート 並びかえ 1 : #include 2 : 3 : #define MAX 4 : 5 : void swap(int *x, int *y) i nt temp : 7 : 9 : temp = *x; 10 : 11 : *y = temp; 12 : ) 13 : 14 : void sort(int data ロ , int n) 15 : { int k = n-l; while (k 〉 = の { int i, j for ( i = 1 : i ← k : i + + ) if (dataCi-1] > dataCi]) { 20 : 21 : 1 J swap(&dataCi], &data[j]) ; 22 : 23 : 24 : 25 : 26 : } 28 : int main(void) 29 : { int i; 30 : 32 : sort(height, MAX) : 33 : for (i = 0 ; i く MAX; i + + ) 34 : printf( ” X4d", heightCi]) ; 35 : return( の ; 36 : く stdio. h> 5 第 5 回 ( 8 月号 ) のポインタの解説時に , 実在 する 5 人の学生に登場してもらいました。 の 5 人を身長順に並べましよう。データの集 まりを昇順・降順などのある一定の規則に 従って並びかえる操作をソーティングとい います ( 通常は単にソートと呼びます ) 。 ソートを行うアルゴリズムは数多く考案 されています。 List 1 に示したプログラム は , 一般にバブルソートという名て、知られ ている , 有名かっ低効率 ( 誉 ; ) のアルゴリ ズムて、す。隣り合うデータの大小関係を比 , 必要ならば交換するという作業を繰り 返すことて、 , データを並び替えます。この バブルソートについては , 過去に本誌のほ かの記事て、も取り上げられたこともあるの て、 ( 多くの市販テキストて、も解説されていま す ) , 誌面の都合上 , 詳細な解説は省略しま す。 こて、 5 ~ 12 行目の swap 関数がポインタ x, y の指す整数の値を交換する関数て、ある ことはわかるて、しよう。 データも用意して , いっしょに並び替えて さてプログラムを具体的に見ましよう。 表示する必要があります。そこて、作成した名前を格納する文字列の配列てある name を Fig. 1 の List 1 の実行結果を見てくださ プログラムが List 2 て、す。実行結果は Fig. 1 扱うように拡張したため , s 。 rt 関数の引数が い。このような表示て、は , 誰の身長が何 cm ひとつ増えていることに気づきます。 32 行 て、あるかがさつばりわかりません。名前の の List 2 の実行結果になります。 116 C MAGAZINE 1991 12 int height ロ = { 178 , 175 , 173 , 165 , 179 } ; 入門講座