宣言 - みる会図書館


検索対象: 月刊 C MAGAZINE 1990年5月号
33件見つかりました。

1. 月刊 C MAGAZINE 1990年5月号

const と volatile 6 ロ - 本ロ 0 五ロ 一三ロ 乗松保智 今回は const と v at ⅱ e について解説します。 const と v at ⅱ e は , ANSI 標準規格で導入されたキーワードで , volatile はと もかく , const は今後よく使われるようになると思います。 この機会に理解してしまいましよう。 頭にひとつだけ現れますが , 宣言子はカン C 言語の文法定義のうち , 宣言に関連する const と volatile マて、区切っていくつもつづけることがて、き 部分を抜き出したのが List1 て、す。このリス ます。 トは yacc の記述法に基づいていますが , 省 ところて、 , この宣言て、は P はポインタ型 , 略可能なことを表す記号として [ ] を使っ const と volatile は ANSI 標準規格によっ c は char 型のオプジェクトになることはご存 て導入されたキーワードて、す。宣言の中て、 ています。また , 簡単にするために多少省 こて、 * の位置に注目し じのとおりて、す。 略した部分や不正確な部分があります。 使い , オプジェクトの性質を決めます。 てほしいのて、すが , なぜ c は char 型なのて、し const は単語 constant の省略形て、す。 con 宣言は大きく宣言指定子と宣言子のふた ようか。 stant には「不変の」「定数」という意味があり つに分けることがて、きます。 char * p, c; ます。この意味からもわかるように , const char * p, c; と書くと , p も c も char へのポインタのよう の char が宣言指定子て、 , *P と c が宣言子て、 を使って宣言されたオプジェクトは , プロ にも見えますね。この書き方は許されてい グラムの実行中に書き換えることはて、きな す。宣言指定子はひとつの宣言について先 くなります。 言指定子と宣言子 volatile という単語には「揮発性の」「変わ りやすい」という意味があります。つまり , Char * 実行中に値がころころ変わるオプジェクト を宣言するときに使います。 これらのキーワードがもつ意味について は後ほど詳しく解説することにして , まず は const, volatile を使った宣言の文法につ いて説明しましよう。 宣言子 これが宣言指定子なら p も c も ポインタになる 言の構文 Char ↑ const, volatile は int, char のような型 指定子て、はなく , また static, extern のよ うな記憶指定子て、もありません。 const と volatile だけて、ひとつの構文要素になってい ます。 子 宣 は の一部 実際には宣言指定子は cha 「だけ 116 CMAGAZINE 1990 5

2. 月刊 C MAGAZINE 1990年5月号

いときには , わざわざ「リスト」になっているのは , const これも構文を満足しています。が , const Char は宣言子て、はなく , char とくつついて宣言 と volatile を両方とも書くことを許している char const d; からてす。 指定子として解釈されます。 言子は c だけ というふうに宣言を分割しなければなりま て、す。 int * const volatile 店 せん。 のように書けるのて、す。ただし , const const Char const c, d; char C, * const p; のように同じ型修飾子を重ねたときには工 というのはどうて、しよう。これも const は char なら OK て、す。 ラーになるて、しよう oconst と volatile を併 とくつついて宣言指定子になりますから OK て、は実際のコンパイラの動作を確かめて て、す。 c も d も const 属性をもつオプジェクト 用する意味については後述します。 みましよう。 List2 のプログラムを MS-C と 宣言子の中て、 const, volatile を書いても になります。 TurboC て、コンパイルしてみると , Fig. 2 の よいのは * の後ろだけだという点に注意し c, const d; char ようになります。 これはどうてしようか。 c はよいのてす てください この結果を見て驚いたのは , なんと MS- が , d が問題て、す。カンマの後ろは宣言子の char * const p; C て、は 2 行目の 1 の宣言が通ってしまう ( / ) こ はずて、すが , 文法ては宣言子の中の const は これは「 const 属性をもつポインタ P 」の正 とて、す。さっき文法工ラーだと書いたばか * の後ろにしか現れなくてはいけません。 しい宣言てす。 c れ ar が宣言指定子 , * const ・ ,TurboC て、はしつかりとエラ りなのに・ この宣言は文法工ラーて、す。 P が宣言子て、す。 ーになってしまいます。 このように , d だけに const 属性を与えた char MS-C< は const 1 が宣言子として解釈さ れているわけて、すが , ては 1 行目の宣言ては 宣言指定子は int だけて、 , const i が宣言子と なっているのて、しようか。 MS-C のコンパイル結果からみると , 1 行 目の宣言て、 i , j はどちらも const 属性をもっ ています。 6 , 7 行目の i , j への代入文がエラ ーになっています。したがって , int const は宣言指定子として , i が宣言子として解釈 されているといえます。 おそらく MS-C< は , 「宣言子のリストの うち , カンマの後ろには const が出てきても よい」という文法の拡張を行っているのてし よう。皆さんはこういう処理系独自の拡張 を使わないようにしたほうがいいてしよう。 これは ANSI 標準規格からはずれた文法てす から , せつかく作ったプログラムの移植性 を損なうことになります。 const の意味 const 属性をもつオプジェクトの宣言のし かたについてひととおり説明しました。次 const 属性についてお話しましよう。 まず , const 属性が与えられると , そのオ プジェクトは値を変更することがて、きなく なります。 const c; コンバイラのテストプログラム / * 正しい宣言 / * Ⅱよ工ラーになるはず * / List 2 1 ー 0 乙っ 0 -4 LO 6 0 ー 8 9 0 1 11 11 k, const 1 : 工ラ 工ラ 、エラ / * OKO ただし , 1 の宣言がエラー * / Fig. 2 コンバイル結果 (a)MS-C A 〉 cl a. c Microsoft ( 日 ) C Optimizing Compile 「 Version 5.10 Microsoft CO 「 p 1984 , 1985 , 1986 , 1987 , 1988. Copyright (c) AII rights reserved. ⅳ specifies const' Object C2166 : a. c(6) e 「「 0 「 C2166 : lvals specifies const' object a. c(7) e 「「 0 「 ⅳ specifies 'const' Object C2166 : a. c(9) e 「「 0 「 (b) TurboC A 〉 tcc a . C C Version 2.0 Copyright (c)1987 , 1988 Borland lnte 「 national Tu 「 bo a. c; a . C 2 : Declaration syntax e 「「 0 「 E 「「 0 「 a. c 6 : Connot modify a const object in function f E 「「 0 「 a. c 7 : Connot modify a const object in function f E 「「 0 「 a . C 9 : Undefined symbol 'I' in function f E 「「 0 「 4 e 「「 0 「 S in Compile * * * AvaiIable memory 239974 118 CMAGAZINE 19 5

3. 月刊 C MAGAZINE 1990年5月号

ロロ ます。空白 ( もちろん改行やタブコードも含 れているのは宣言子 ( declarator ) の中だけ const と volatile の構文 みます ) はトークンを区切るだけの意味しか て、す。 ありませんから , コンパイラにとっては先 これはたんなるソースプログラムの書き の宣言も後の宣言もまったく同じて、す。 方の問題なのて、すが , 意外と初心者はひっ const と volatile は型修飾子 (type-quali のように書いても c はやつばり char 型なのて、 fier) と呼ばれます。 const, volatile そのも かかるところかもしれません。いかにソー す。 のがオプジェクトの型を指定することはあ スの書き方を変えてみても , 文法を変更す つまり , * は宣言子の一部て、あり , りません。型の性質 ( 属性 ) を与えるだけて、 ることはて、きません。宣言子に含まれるの 指定子て、はないということて、す。 * p がひと は * だけて、はなくて , 配列の [ ] , 関数の ( ) す。 List1 の 5 ~ 28 行て、定義しているのは宣言 つの宣言子て、あり , c は * p とは別の宣言子 [ ] や ( ) は識別子 ( 名 指定子て、す。宣言指定子は記憶クラス指定 も含まれます。ただ , て、す (Fig. 1)。 List1 て、は pointer という構文 子 , 型指定子 , 型修飾子がいくっか並んだ 前 ) の後ろにつくのて、まちがえることは少な ものて、す。これらの並びの中から , オプジ 要素が * を表しています。 pointe ェが使わ いて、しよう。 ェクトの型は型指定子が決定し , 型修飾子 言の構文 ほ型の属性を決定します。また , その並び 方の順番は自由て、す。 こからの説明て、は const を例に使用しま すが , const のかわりに volatile を使っても 構文は同じて、す ( 同一の構文要素なのて、すか ら , あたりまえといえばあたりまえて、す ) 。 int const int const k; const int 以上はいずれも int 型のオプジェクトの宣 言て、す。 i だけが属性をもたない int 型て、 , ほ かは const 属性をもっ int 型て、す。 先ほど型修飾子は属性を指定するだけの ところて、 意味しかもたないと書きました。 j は const としか宣言されていません。それ なのに int 型になるのはなぜて、しようか。そ れは , C 言語て、は「型が省略されたときには int と解釈する」ことになっているからて、す。 k もしくは 1 の宣言から int が省略されたもの が j の宣言だと考えられますね。 const Char c; const d; この例て、は c は char 型て、すが , d は int 型て す。 int て、ない型は省略て、きません。 const, volatile は宣言子の中にも書ける ようになっています。 List1 の pointer の定 義の中にある型修飾子リスト (type quali fier list) がそうてす。この型修飾子リスト には [ ] がついていますが , あってもなく てもよいことになっています。 C 言語雑学講座 117 / * 宣言 * / 1 : declaration declaration-specifiers declarator 2 : 3 : 4 : / * 宣言指定子 * / 5 : declaration_. specifiers strage_class_specifier [declaration_specifiers] ー type_specifier Cdeclaration_specifiers] 7 : ー type_qualifier [declaration_specifiers] 9 : 1 1 : strage_class_specifier ' aUtO' ー 'register' ー ' stat i c ・ 14 : ー ' extern ' ー 'typedef' 19 : type_specifier 20 : ー 'char' 22 : ' i nt ' 23 : 24 : 25 : type-qualifier 26 : const' ー ' vo I at i I e ' 28 : / * 宣言子 * / 30 : declarator [pointer] direct-declarator 32 : 33 : 34 : direct declarator 35 : identifier ' ( ' declarator ' ) ' 36 : ー direct-declarator ー direct_declarator 38 : ー direct_declarator 39 : 41 : pointer 42 : 43 : 44 : / * 型修飾子リスト * / 46 : type-qualifier_list type-qualifier ー type-qualifier_list type-qualifier 49 : List 1 ・ 1 よ・ / * 記憶クラス指定子 * / / * 型指定子 * / / * 型修飾子 * / / * 直接宣言子 * / [constant_expression] [parameter_type-l ist] [identifier-list] / * ポインタ * / ' * ' [type_qualifier-list] list] pointer ー・ * ' [type_qualifier

4. 月刊 C MAGAZINE 1990年5月号

五ロ はじめて学ぶプロクラー ニンク 匚。司 った段階て、メモリの状態は Fig. 7 ー 1 のよ int * p ; うになっており , 領域が確保されてい 「 * 」は前節て、て、てきた , データ値を求める ます 間接演算子て、す。「ポインタ、、 p 〃の指す、、値〃 ② 12 行目 ( a = 10 ; ) て、 c れ ar 型変数 a に 10 は、、 int 型〃」を意味します。同様に「 char 型 これらのポインタの演算を行うためには , が代入されます (Fig. 7 ー 2 ) データを指すポインタ p 」の宣言は , やはりそのポインタ用の領域をメモリ上に ③ 13 行目 ( p = & a ; ) て、は変数 a のアドレス char * p ; 確保し , その値を記憶しなければなりませ 値を取ってきて ( アドレス取得演算子を となります。一般的にポインタ宣言の書式 ん。というのもポインタも変数のひとつな 使っています ) , ポインタ p に代入して は , このあたり慣れないと混 います。実行が終わった段階て、ポイン のて、すから・・ テータ型 * ポインタ名 乱するかもしれませんね。ポインタは , 記 タ p は a のアドレスを指すようになりま となります。ポインタの配列を取りたいと 憶しているデータが別の変数のアドレス ( 物 きには「ポインタ名 [ 要素数 ] 」とします。 す (Fig. 7 ー 3 ) 理的なアドレスという場合もある ) て、あると ④ 14 行目 ( x = * p ; ) て、はポインタ p の指 いうだけて、 , 変数の一種て、あることに変わ すアドレスから char 型のデータを取っ てきて ( 間接演算子を使っています。取 りないのて、す。 8 ー 2 て、は p はポインタて、あるとして説明し ってきた値は 10 て、す ) それを char 型変数 こまて、説明をして , ようやくまとまっ ましたが , 実際にはポインタを使う前に「 P x に代入しています。実行が終わった段 たプログラムを記述て、きるようになります。 する必要があ はポインタて、ある」と旦 階て、は Fig. 7 ー 4 となっています ポインタを使ったごく簡単な例を List1 に示 ります。しかし , ポインタ型などというデ 結局これらの動作により a の値が x にコピ します。動作が理解て、きるて、しようか ? ータ型も , それを表す型宣言子も存在しま ーされました。しかし直接代入するのて、は 蛇足かも知れませんが , Fig. 7 を使って説 せん。仮にあったとしてもこれまて、に述べ なく , ポインタ p を介してコビーされたとい てきた , 変数とポインタの互換性がとれな 明してみましよう。 うことが新鮮て、すね。そして , 変数 a が実際 ① 10 行目 (char * (;) まて、実行が終わ 何番地に用意されているのか , くなってしまいます。というのは変数とい という具体 っても char, int, float, double などと種 類がたくさんあり , 「おのおのデータのサイ ズが異なるからて、す」 (TbI.2)o したがって , たとえばポインタ P が同じと ころを指していても , データ型によって取 り出すデータのバイト数が異なります。 Fig. 6 この例て、は , にこの説明をしました。 → 1000 番地のデータ char 型データ を取り出す → 1000 , 1001 番地の int 型データ データを取り出す という使い分けが必要になります。ポイン タとひと口にいっても , 「 char 型データを取 り出すポインタ」や「 int 型データを取り出す ポインタ」といった使い分けが必要て、す。 「 c れ ar へのポインタ」とか「 int へのポインタ」 といういい方をする人もいます ( もちろん 「 float へのポインタ」や「 double へのポイン タ」などもちゃんと存在します ) 。 これらの事柄をふまえたうえて , 「 int 型デ ータを取り出すポインタ 0 」の宣言は次のよ うになります。 P 川 00 用囲番地 1001 番地 ha 「型データ int 型テータ ( 注 ) この例では cha 「型は 1 バイト . int 型は 2 バイトのサイズをもつ場合を示す TbI. 2 変数の基本テータ 型宣言子 バイト長 意味 1 バイト整数型 整数型 ( 注 1 ) 単精度浮動小数点型 ( 注 2 ) 倍精度浮動小数点型 float 型の 2 倍 注 1 CPU による。一般的にいってビットのコンビュータでは給ビット , つまり 2 バイト。 MS-DOS 上ではおもに 2 バイト 注 2 CPU による。パソコンクラスでは 4 バイトが一般的 Char i nt float double 1 はじめて学ぶ C プログラミング 125

5. 月刊 C MAGAZINE 1990年5月号

いうように使い分けることが可能て、す。さ て List2 の結果はどうなりましたか ? この プログラムの動作がわかりますか ? わか りにくかったら Fig. 7 のようなメモリの状態 図を書いて考えるとよいて、しよう。 * p を変数として使う場合の注意をここて、 申し上げておきましよう。 List3 を実行して みてください。何が起こりましたか ? ①コンパイル時に警告がて、た ②実行したら暴走した ③正しく実行て、きた ④やるたびに結果が異なる など , わけのわからないことが生じます。 何回やっても正しく実行て、きる人もあるか もしれませんが , この List3 のプログラムは 「まちがったプログラム」なのてす。どこ がまずいかおわかりになりますか ? 「 * p は変数として使えるんだからかまわ んじゃないか。 List4 の a を * P に置き換えた だけだぞ」という考えの方もいらっしやる かもしれません。確かに List4 は正しく動作 する正しいプログラムてす。ポインタとし て宣言された P を変数として使うのだから , このプログラム中の a をすべて 0 に置き換 というのも無理からぬ考えて、 えればよい す。 こて、 8 ー 1 ー 2 の記述を思い出してくださ 「変数を使った表し方て、は , データを置く アドレスをユーザがいちいち管理する必要 はありません。このアドレスはコンパイラ ( ないしはインタブリタ ) が決めて管理して くれます。ユーザは変数のデータ値を自由 に変更することが可能て、す。対してポイン タを使った表し方を用いれば , データを置 くアドレスの決定権をもユーサはもっこと ができる」わけて、す。 「データを置くアドレスの決定権をもユー ザはもつ」 , 逆にいうとユーザがきちんとア ということなのて、 ドレス管理をしなさい 的な話はさほど重要て、はなく , ポインタ p が るときに , * p を int 型変数として使うこと す。 List3 のメモリイメージを Fig. 8 に示した 変数 a のアドレスをヾ指している〃という考 がて、きるのて、す。ポインタとして宣言され ように , 宣言された P は値が不定て、す え方に意味があります。 ている p を変数として使いたいときには * P ( ? ? ? ? になっている ) 。不定とはいえ何 さらに List2 のような使い方もて、きます。 を , そして実際アドレスをポイントする必 かしら値が入っているわけて、すから , P はそ p が int 型へのポインタとして宣言されてい 要がて、てきたときに p としてポインタを , と 126 CMAGAZINE 19 5 ポインタの例 ( 1 ) List 1 2 : 4 : 5 : #include く stdio. h> 6 : 7 : void main(void) 9 : Char a, X : char *p; = 10 ; 14 : printf(" い s t 1 ポインタの例 ( 1 ) / * これはただの char 型変数です / * char へのポインタ p / * char 型の a に 1 0 を代入 / * ポインタ p に a のアドレスを代入 * / / * p の指す値を x に代入 = XdYn ” X ポインタの例 2 Li st 2 2 : 4 : 5 : #include く stdio. h> 6 : 7 : void main(void) 9 : / * これはただの i nt 型変数です int a; / * int へのポインタ p int *p; 12 : / * a のアドレスを p に代入 / * p の指すアドレスの内容を 1 0 にする = 10 : printf("*p ニ %d, a ニ %d\n" List2 ポインタの例 ( 2 ) まちがったポインタの使用例 い st3 間違ったポインタの使用例 List. 3 2 : 4 : 5 : #include く stdio. h> 6 : 7 : void main(void) 9 : int *p; printf("*p = %dYn" 13 :

6. 月刊 C MAGAZINE 1990年5月号

とがて、るようになっています。 Fig. 3 ループ不変式の最適化 = b 十 C wh i 1 e (. a b + c; tmp wh i 1 e a tmp; 19 行目は cs は const て、すからエラーになり ます。 21 行目は cs が const なのて、そのメンノヾ mem も const 扱いされています。これに対し て pi は const て、ない int へのポインタて、すか ら , 工ラー , あるいは warning になります。 23 行目は配列と const が同時に指定されたと きには , const が配列そのものて、はなくて要 素の型を修飾することのデモンストレーシ ョンて、す。したがって a [ O ] は const int へ のポインタという解釈になります。 構造体のメンバにも型修飾子をつけるこ ループの中で b や c への代入がなければ , 一時変数 tmp を 120 CMAGAZINE 19 5 real time clock; extern const volatile long 味になるのて、しようか。 volatile と const を併用するとどういう意 への指示を与えるわけて、す。 適化をしてはいけないよ」というコンパイラ を使って「値がどんどん変化しているから最 このような事態を避けるために , volatile 正しくないコードになってしまいます。 式の値が変わる可能性があるのて、 , これは すが , b や c がポートだったらループごとに て、の計算量が減るのて、 , 高速化されるのて、 する最適化て、す ( Fig. 3 ) 。通常はループの中 もつ式があれば , それをループの外に移動 も代入されないオプジェクトだけを右辺に たとえばループの中に , 値は使うけれど とはて、きなくなってしまいます。 すが , その後はポートの状態を確認するこ て、しようか。最初はポートから読み込みま そのオプジェクトが I/O ポートだったらどう ードを避ける最適化があります。しかし , もってきた値を再利用し , メモリからのロ たとえば前述のように , 一度レジスタに えてみましよう。 属性をもつべきオプジェクトとの相性を考 な最適化を施します。そのいくつかと volatile 行速度を高速にするために , 実にさまざま 最適化コンパイラて、は , プログラムの実 のて、す。 あるオプジェクトに volatile はうってつけな 使ってループ内の計算量を減らすことができる 規格からはすれていないといえそうて、す。 あなたの処理系は const に関しては ANSI の 。 19 , 21 , 23 行て、エラーを報告すれば , おもちの処理系て、コンパイルしてみてくだ さまざまな代入を実行しています。これを うふうに効力を発揮するかを調べるために 16 行目からの関数 test て、は const がどうい にはなりません。 ちませんから , 8 行目の宣言て、は ncs は const ところが struct s そのものは const 属性はも って cs は const 属性をもっことになります。 構造体 cs の宣言があります。この宣言によ る例を示します。 5 ~ 7 行目には const っきの List4 に ANSI 標準規格書に掲載されてい ることもて、きます。 に使用して const 属性をもっ構造体を宣言す 構造体も型指定子て、すから , const と同時 構造体の const, vo ti は : = てはいけないのて、 , const がついています。 た , 通常 , システムの時計を勝手に変更し せん。そのために volatile が必要て、す。ま あるいは専用の LSI のレジスタかもしれま によって定期的に更新されるのて、しよう。 ェクトだとします。おそらく割り込み処理 time clock はシステムの時刻を表すオプジ はいけない」オプジェクトになります。 real ども , 値がころころ変わるから最適化して real time clock は「代入はて、きないけれ struct const char struct int S 1 ; st; という宣言て、は struct s のメンノヾ i だけが const 属性をもちます。したがって , st. i に 対する代入はて、きなくなりますが , st. c には 代入て、きます。 ところて℃て、は構造体の代入が許されてい ますね。て、は , この構造体 st に対しての代 入は許されるて、しようか。私にはわかりま せん。私の探し方がへたなのかもしれませ んが , ANSI 標準規格て、は const のメンバを もっ構造体の代入に関する記述は見つかり ませんて、した。 私見て、すが , 構造体中にひとって、も const のメンバが存在するときには , その構造体 への代入も禁止されると考えるのがよさそ うて、す。構造体の代入を許すと , const のは すのメンバまて、代入されてしまいます。 List5 は const のメンノヾをもっている構造 体の代入に対する動作のテストプログラム て、す。構造体の定義をよく見ると , struct stI, struct st2 ともに const のメンノヾが 含まれています。したがって私の考えて、は , 代入はどちらもエラーになるはずなのて、す が・・・・・・さて , あなたの処理系はいかがて、す

7. 月刊 C MAGAZINE 1990年5月号

五ロ const オプジェクトの初期化はイニシャラ イザによって , 宣言のときに いっきに実 行します。このときに初期化しないとプロ グラム実行中には値を代入て、きません。 const int num = 100 ; 能になります。 値を変更してほしくないオプジェクトが s は const 属性がついたポインタてす。し スタンドアロンて、使われるさまざまな機 たがって , s を変更することはて、きません。 あれば , それを const として宣言しましよ 器を制御するプログラムなどを開発してい しかし , * s は変更することがて、きます。 う。うつかりと代入するようなミスは , コ る場合 , そのプログラムは最終的には ROM て、は , ンパイラが未然に防いて、くれます。 に書き込まれます。そのときに const のつい これは関数パラメータに対しても同じて、 const char * const t; たオプジェクトの値は変更されることはな す。たとえば , strcpy という標準ライプラ はどうて、しようか ot も * t も const 属性をも いのて、すから , ROM に書き込むことがて、き リ関数は List3 のように const を使ってヘッ っていますから , どちらも変更て、きません。 ます。 RAM に置かれるデータの場合は , プ ダファイル string. h の中て、宣言されていま const にはコンパイラが最適化をしやすく ログラム実行中に書き換えられる可能性が す。 strcpy は src が指している文字列を , するという意味もあります。 あります。そのため , 最初は ROM に書き込 たとえば , 次のプログラム片を考えてく dst がさしている文字配列にコヒ。ーします。 まれている値をプログラムの実行開始時に 書き換えられるのは dst が指す文字配列て、 , ださい RAM にコヒ。ーするような操作が必要になり src が指しているコヒ。ーされる文字列は変更 int ます。 しません。したがって , dst には const をつ けてはいけませんが , src は const として宣 以上 , const のもつ意味をいろいろとあげ 言してあるわけて、す。 てみましたが , 実際には ANSI 標準規格て、規 また , const オプジェクトは「アドレスが i はアドレスをとられていますから , 関数 定されているのは , 「 const オプジェクトに func の呼び出しによって i の値が変更される とられないかぎり , インライン展開しても 対する明示的な代入をチェックする」ことだ 可能性があります。コンパイラは①の部分 よい」ことになっています。インライン展開 けて、す。そのほかの点については規定され と②の部分て、 i の値が変わっていると考えま というのは , ていません。代入のチェックさえ実行すれ す。①て、 i の値を使っていたとしても , ②て、 num; ば , const を無視することさえ許されていま i の値を使うときには i が割り付けられたメモ という式を , 変数 num から値をロードするの す。 リからロードしなおす必要があります。 て、はなく , あたかも , て、すから処理系に対して , 「最適化をしな 一方 , i が const 属性をもっているなら IOO; 1 いじゃないか」とか「 ROM に配置しないの ? 」 ば , func の呼び出しによって i の値は変更さ と書いたのと同様に処理することて、す。 # といった文句をいっても , あまり意味はあ れることはないと考えられます ( 何て、もて、き define と似ています。 りません ( 笑 ) 。 てしまうのカ℃言語て、すから , もちろん func , こて、ちょっとひと休みして , の中て、「むりやり」 i に代入することもて、きま const char * 0 ; volatile の意味 すが それをされるとコンパイラは困っ * const s; char てしまいます ) 。①と②て、 i の値が変わらない 。本連載 の違いについて考えてみましよう volatile は値がころころ変化するオプジェ 第 2 回 ( ' 89 年 12 月号 ) に書いたようにして宣言 のなら , ①て、 i の値を使ったときにメモリか クトを指定するために使います。 らレジスタに読み込んて、おいて , ②て、使う を解釈してみると , たとえばメモリマッブド I / O 方式の周辺デ ときにはレジスタにある i の値を利用するこ p → * → const → char バイスのポート入出力てす。ポートはデバ とがて、きます。 i の値をメモリから読み込む s—»const ・→ * ー→ char イスの状態が変化することによっていつの よりは , レジスタを使うほうが , 当然なが となります。 間にか変わって・いきます。あるいは割り込 ら実行は高速て、すし , コードサイズも小さ p 自体は const て、はありませんから変更て、 み処理によって値を変更される変数などに きますが , p が指しているのは const 属性が くなるて、しよう。 も volatile 属性を与えます。つまり , プログ const 属性をもつオプジェクトは , ROM ついた文字てす。したがって , * p を変更す ラムの実行以外の要因て変化する可能性が ることはて、きません。 (Read Only Memory) に配置することも可 C 言語雑学講座 119 st 「 cpy のプロトタイプ宣 *strcpy (char *dst, const char *src) : List 3 Char func(&i); 1

8. 月刊 C MAGAZINE 1990年5月号

名前 name や住所 address は非公開て、あるのに 年齢 age は保護になっています。これは , name や address は基底クラスのメンバ関数からのみ操作 され , age は派生クラスのメンバ関数からも操作さ れる可能性があることを意味します。つまり , age を保護メンバにし , 基底クラス patient から派生し た全クラスからて、も ( 注 1 ) 直接操作て、きるようにし ているわけて、す。 この例のように , 操作がクラス中に限定される データメンバは「非公開」に , 派生したクラスにお よぶものは「保護」にするのが適切な方法て、あり , これがラベル protected: により示される第 3 のアク 1 : c lass pat ient { / / 医療データベースのための基底クラス 12 : } : 18 : } : 22 : } : 26 : } : C + + プログラミング入門 87 セスレベル protected ( 保護 ) の使い道て、す ( 注 2 ) また , List3 の各派生クラスは , 公開 (public) 基 底クラスをもっています。つまり , 派生の際に ListI- ①のように , 基底クラスを公開に宣言してお り , 基底クラスの公開メンバは , そのまま , 各派 生クラスの公開メンバのように扱われます。これ は , 基底クラスのメンバのみを操作する , 各派生 クラスに共通の基本的なメンバ関数を基底クラス に用意し , それを派生クラスのユーザに提供する ためて、す。 List1- ②のように , 基底クラスを非公開に宣言し ( 注 3 ) , 派生クラスが非公開基底クラスをもつ場合 2 : 3 : 4 : 5 : 7 : 8 : 9 : List 2 2 : 3 : 5 : 6 : 8 : 13 : 20 : 23 : 24 : List 3 全患者クラスの基底クラス / / 非公開部 private: / / 名前データ Str name; Str address : / / 住所データ / * 他の非公開メンバ * / 6 : protected: / / 保護部 / / 年齢データ Byte age : / * 他の保護メンバ * / / / 公開部 10 : public: / * 他の公開メンバ * / 患者派生クラス例 patient(Str, Str, Byte); / / 隠されたコンストラクタ 1 : class Medicine : public patient { / / 内科の患者のクラス / * 内科独自のデータメンバ等 * / / * 内科独自の操作等 * / / / 公開部 4 : public: Medicine(Str, Str, Byte); / / 公開コンストラクタ void lhis-output(void) : / / 独自データの出力 ・ public patient { / / 歯科の患者のクラス 9 : class Dentistry ・ / * 歯科クラスのためのメンバ * / : public patient { / / 外科のクラス一般形 C lass surgery / * 外科独自のデータメンバ等 * / / * 外科独自の操作等 * / 16 : protected: / / 保護部 sugery (Str, Str, Byte) : / / 隠されたコンストラクタ c lass PrimarySurgery : public surgery { / * 第一外科クラスのためのメンバ * / c lass SecondarySurgery publ ic surgery / * 第ニ外科クラスのためのメンバ * / / / 第一外科クラス { / / 第ニ外科クラス ( 注 1 ) 基底クラス öatient から派生したクラ スを基底クラスし , さらに 何段階にも派生したクラス てもかまわない。 ( 注 2 ) 第 1 および第 2 保 護レベルは , もちろん pri vate 俳公開 ) と public 伀 開 ) てある。 ( 注 3 ) 古い C + + 処理系 は , 非公開 (private) 基底 クラスの宣言に予約語 pri vate を使えない。それらの 処理系て、は , たんに , class gomi : patient { と書けばよいはずてある。

9. 月刊 C MAGAZINE 1990年5月号

とになります。 C 十十から取り込んだ C の表記の例とし て , たとえば引数がないことを強調したい ということて、あれば , 次のように void を書 きます。 / * no arguments * / f(void) また , 特定の引数が必要なことを強調し たいということて、あれば , 次のような形て、 表記されることになります。 / * one argument * / もちろん , 必ずしも名前 x を記述する必要 はありませんが , この x の表記をもっことに よって , 引数のチェックに大きな意義をも たせることて、きます。 て、すから , ある関数呼び出しが , 上記の ような宣言と一致しない引数による関数呼 び出しならばチェックが働き , 工ラーにな ります。また , 引数にポインタまたは構造 体を指定した場合てあっても , やはりチェ ックを得ることがてきます。この例て、は , ⅲ t に代入て、きる型の引数だけがエラーなし になります。 しかし , 実際には可変個の引数があり , それを表現て、きるような方法を提供するこ とも , 私たちにとって重要て、あると考えて いました。ひとつ以上の引数を宣言するこ とによって可変個の引数の関数に対処する ことがて、きるからてす。 f(char * p, … ) / * varying * / char * P は必ずそこに存在していなけれ ばならないことになります。しかし , 任意の引数をもってくることがて、きること を示しています。 printf や scanf 関数の引数を定義する際 には , この方法を使います。 ANSI C て、この printf 関数のファンクションプロトタイプ を書くことがて、きることは , たいへん重要 だと考えています。これに関して私たちは , f(int x) 60 CMAGAZINE 19 5 成功したと思います。 C 言語の追加機能として , この関数のプロ トタイプ宣言がもっとも重要なもののひと つだと思います。そして , 追加した際に さらにそこから次の段階への展開が見えて 型修飾子 ものて、あると確信します。 ての C プログラマにとって , 利益をもたらす そのため , 関数プロトタイプとは , とも , 重要な点て、す。 いるということがすて、に証明されているこ すべ ちろん私はここて、型修飾の話をしているわ 重要な変更事項てあると考えられます。も んどの場合 , 組み込みプログラムにとって とって有利て、あるとは考えませんが , ほと ります。必ずしも 100 % この変更が皆さんに プログラムにとってとくに重要なものがあ もうひとつ重要な追加として , 組み込み volatile const けて、すが , const char cc; / * can be ROMed * / て、す。 ば , そちらのほうがずっと簡単にすむわけ ースコードの中て、明確にすることがて、きれ て、すから , ROM の中に入れるデータをソ という問題が発生します。 イルにしてコンパイルしなければならない しかし , その場合 , ROM 用データを別ファ うことによって , その問題を解決しました。 ンパイラて、は , コンパイル時のフラグを使 たとえば旧バージョンの Whitesmiths の C コ ばならない状況が発生します。このとき , ときとして , データを ROM に入れなけれ の修飾子て、解決することがて、きます。 際に直面しうる問題の多くは , このふたっ 組み込みアプリケーションて、これらを書く というふたつの重要な型修飾子があります。 これて、 ROM の中に入れることがて、きま す。これはまた cc の内容をプログラムが変 更て、きない ( させない ) ことを意味します。 つまり , cc の内容を変更するような記述は 工ラーになるということて、す。もちろん C コ つまり , これは型と記憶クラスのちょう ばれるゆえんなのて、す。 りません。このことが , const が型修飾と呼 ンタがどこを指すのかを注意しなければな すると , ポインタがどこにあるのか , ポイ たとえば const をポインタと結びつけると なりません。 には , 必ずポインタのことも考えなければ す。そこて、 , const のような修飾を考える際 算やポインタを使ったメモリ参照がて、きま 機能がついています。ポインタを使った演 しかし C の場合には , ポインタと呼ばれる すことがて、きます。 に格納するのか , その名前によってのみ探 FORTRAN などの場合は , いったいどこ いへん大きな違いがあります。 に定義づけるのかというふたつの間に , た すればよいのかを知ることと , それをどこ へん微妙て、す。 C 言語の場合にはどこに格納 しかし , この分野に関して C 言語は , たい か定義しなければなりません。 ラスを通じて , const 変数をどこに格納する ていかなければならないからて、す。記憶ク は const のための新しい記憶クラスを整備し ど簡単な作業て、はありませんて、した。本来 は const を追加しましたが , これは , それほ がった動作をすることになります。私たち そうすると , 皆さんのプログラムはまち いのて、す ) 。 のて、す ( つまり完璧な不変定数が実現しにく 変更するようなコードも記述て、きてしまう どのちょっとした手段て、 const 変数の内容を は困難て、す。て、すからポインタを用いるな 変数へのアクセスをすべてチェックするの はありません。しかし , C コンパイラが const ROM の中になければならないというものて、 ンパイラにとっては , 必ずしもこの変数が

10. 月刊 C MAGAZINE 1990年5月号

工ル・エス・アイジャパン 0 「 m 面 00 from (ompiler ma 「 5 LSI C -86 LSI C -80 今回はわかりにくいコンパイル 工ラーやコーディングミスのなか て、 , とくに多い質問を集めました。 0 Warning . function assumed printf' undefined to be int なというコン′くイ ) レエラ ーがでるのですが。 A 未宣言の関数を呼び出すとこ のエラーがてます。 printf のよう なライプラリの場合は各ライプラ リのプロトタイプ宣言がヘッダフ ァイルに入っていますのて , 必要 なヘッダファイルをインクルード (#include) してください また , ライプラリてない場合は プロトタイプ宣言が必要になりま す。同一ソース内に関数が定義さ れていても , 参照よりも下に関数 が定義されている場合はこのエラ ーになります。どうしてもこの工 ラーがうるさいという場合は , -w オプションて、エラーメッセージが てなくなります。 Q&A List 1 0 以下のソースで•Warning illegal pointer combination ( = ) ″というコンパイルエラーが でるのですが。 char * p; p = 0X8000 ; ポインタと整数間の代入また は比較を行うとこのエラーになり ます。この場合は以下のようにキ ャストします。 (char * )Ox8000; 0 ー v a ー u e 「 e q u i 「 e d (XXX)& というコン′ヾイルエラー がでるのですが。 代入て、きないものに代入演算 子または & 演算子を摘要した場合 にこのエラーになります。とくに 以下のように , 誤って配列名に & 演 算子をつけるとこのエラーになり 将来的にはこの警告ェラーのみ を出力しないように指定する -wi オ プションをサポートする予定てす。 A P A ます。 Char X gets(&x); [ 128 ] ; ② " ・ #def i ne ABC ABC) printf("hello, worldYn") : : testl. Obj test. exe lcc -0 test. exe testl. Obj getchar()) { switch (c printf(" 改行 Yn"); break: defalt: putchar(c) ; break; 0X10 : getx(n + + ) ー (getx(n) くく 8 ) : #code= 0 #data=8000 test. hex tck test -froml ib #CODE= 0 #DATA=OAOOO test. hex tck test - f 「 0 引 ib #CODE= 0 #DATA=AOOO test. hex tck test -froml ib 0 List ト①のソースの if 文の行で syntax e 「「 0 「 near tax error near ' ) ' ″とし、うコン / ヾ イルエラーがでるのですが。 A もちろんこれは # define の最 後の、、 ; 〃が不要なのて、す。 # define 文がエラー箇所の近くにあればす ぐ気づくのて、すが , ヘッダファイ ルのなかにある場合や #define は 、、 ; 〃が必要だと思い込んて、いる場 合には , なかなかまちがいに気づ かないものて、す。 0 List1—②の makefile で make をかけたら•missing after に c ″というエラーになるのです が。 lcc の行の先頭がタブてない と , このエラーになります。タブ がないと make が正しくコマンド行 を認識て、きません。ェデイタてフ ァイルをセープするとき , タブを スペースにかえるように設定して ある場合は注意が必要て、す。 0 List1- ③の switch 文の default が実行されないのですが。 A defalt のつづりが誤っていま す (default が正しい ) 。この defalt は switch 文の default< はなく , 一度も参照されないラベルになり ます。したがって本当の default の A とき何も実行されません。 0 ListI ー④のソースが正しく実 行されないのですが。 A getx(n 十十 ) と (getx(n) くく 8 ) のどちらが先に評価されるかはコ ンパイラによります ( K & R 「プログ ラミング言語 C 』参照 ) 。したがっ て getx ( n 十十 ) が先に評価されると はかぎりません。このような場合 は , 以下のように 2 行にわけて評価 の順序を明らかにします。 getx(n 十十 ) ; X x ー = getx(n) くく 8 ; 0 LSI C ー 80 で List ト⑤のコマン ドファイルを与えて knil でリンクす ると•syntax e 「「 0 になるので すが。 A ROM や RAM の先頭配置は 16 進数て、指定するのて、すが , 先頭の 文字が数字て、ないとこのエラーに なります。 List ト⑥のように修正し ます。 0 LSI C ー 80 で凵 5t1 ー⑦ ) のコマン ドファイルを与えて knil でリンクす るとエラーにはならないのですが , 正しく配置されません。 A knil て、は ROM や RAM の配置 を指定する code, data は大文字て、 ないと正しく認識しません。つづ りをまちがえても同じ結果になる のて , マップファイルを出力して 配置を確認してください 152 CMAGAZINE 19 5