N more 文字列は文字の配列という実体をもち , す てに再三述べてきたように C 言語ては配列と ポインタは非常に入り組んだ関係にあって 混同しやすい。実際それらは数多くの誤解 と曲解を受け , 幾多の悲喜劇を生み出して きたに違いない LiSt く stdio. h> 1 : # i nc I ude 2 : 3 : VO i d 4 : foo(void) 6 : puts("hello, world") : 文字列定数 関数 puts が出力するものは同じてある。なぜ 載第 5 回 ( ' 91 年 2 月号 ) て、も述べたように , ならば List 1 ても , List 2 ても , puts に渡さ 式の中に配列オプジェクトが登場した れるのは文字列定数 " he110 , world ”の先頭 場合 , 先頭要素へのポインタが生成さ 本連載の第 4 回 ( ' 90 年 12 月号 ) ても述べた の文字 ' h ' へのポインタて、あるからだ。 List 1 が , C 言語の文字列定数は「 ( 無名の ) 文字の れる ては直接文字列定数を評価したことによっ というルールに従い , 関数呼び出しに際し 1 次元配列」という取り扱いになっている。 て生成され , List 2 てはいったん char * てあ て引数の文字列が評価され , 先頭要素 ( この もっともよくお目にかかる文字 おそらく , る変数 p へと代入された後に puts へと渡され 場合は先頭の文字 ) へのポインタが得られ , 列定数の形は , 出力関数への実引数として るという違いはあるが , 値としては同じも 渡されているケースだろう。たとえば List 1 その得られたポインタが関数 puts に渡される のてある。 puts へはつねに文字へのポインタ からてある。 のような場合て、ある。 が渡されている。このことも重要てある。 こて、 , 関数 puts へは文字列定数の値その コンパイラは List 1 をどのように解釈し , コンパイラによる List 2 の翻訳結果は Fig. ものが渡されているのて、はなく , 先頭文字 翻訳するのてあろうか。まず , 関数 puts の実 2 のようになる。まず , char * p ; の宣言を へのポインタが渡されている。なぜなら文 引数に文字列” hello , world ”を見た時点 見た時点て、 , 文字へのポインタ p は auto なの て、 , 定数を格納する領域に文字列 " hello , 字列定数は配列オプジェクトてあり , 本連 て , その領域をスタック領域に確保するよ world ”の 1 文字 1 文字を連続的に char の後に うにする。次に , p に対する文字列 " he110 , Fig. 1 List 1 の翻訳結果 ' \ 0 ' を付加して格納する。そしてこの領域の world ”へのポインタの代入を見た時点て , 先頭アドレスを関数 puts へと渡すのてある 定数領域に文字列 " he110 , world ”を ( ' \ 0 ' を (Fig. 1 ) 。ただし , コンパイラによっては静 付加して ) 格納する。そしてこの領域の先頭 的データ領域と定数領域を区別していない アドレスを p へと代入するコードを生成す 場合もある。 る。最後に p の値を関数 puts へと渡すのてあ 文字へのポインタ変数 る。ここて , ポインタ変数 p に設定されたポ インタ値の先には文字列定数が存在するこ とに注意 ! ただし , コンパイラによる最適 さて , List 1 は List 2 のように置き換えて 化が行われて変数 p が省略されてしまうこと も結果は変わらないだろう。 こて , 文字 ( char ) へのポインタて、ある p はありえる。その場合 , 実質的に生成され に , あたかも文字列定数 " he110 , world" を代 るコードは List 1 と同じになるだろう。 List 3 は , 変数 p の宣言と同時に文字列定 入しているように見えるところが第 1 のワナ てある。とくに BASIC て育った人たちは , 数の値 , すなわち先頭文字へのポインタて 初期化している。この場合 , 初期化は List 2 この p への代入を文字列定数全体の代入だと 誤解する傾向がある。繰り返しになるが , の p への代入とまったく同じ意味をもってい 文字列定数を評価すると先頭文字へのポイ る。実際 , ほとんどのコンパイラては List 2 と同等のコードが生成されるだろう。当然 ンタが得られる。 p に代入されるのは文字へ ながら実行結果も同じてある。 のポインタてあって決して文字列定数その 余談だが , C 言語の記法てかなりまぎらわ ものてはない。いずれのプログラムても , 定数領域 'h' ー puts 先頭アドレスカ値接関 uts へと渡される 'VO' ANSI C : more 101
文字へのポインタと文字列 more 第 6 回 今回は , 配列とポインタの関係の中でも , おそらくもっとも混乱 を極めているであろう , 文字列定数と文字配列と文字へのポイン タの関係について , 一部復習を含めて , 詳しく考察してみる。 一般に , ある程度以上の力量をもったプロ る。実は C 言語における文字列の取り扱いは はじめに グラマに C 言語の受けがよい理由の一つがこ このケースの典型例だといえる。 多くの言語ては文字列は基本データ型の にあると思われる ーってある。それらの言語てはいくつかの データベースや表寸算ソフト , あるいは 組み込み機能という点て、考えると , 一部 オペレータ ( 演算子 , たとえば BASIC< は連 ワープロや DTP などのアプリケーションプ の BASIC ( たとえば QuickBASIC) や PL/I な ログラム , いい換えれば工ンドユーザ向け 結のための十とか , 比較のための = とか ) が どの言語は非常に豊富な組み込み機能をも のソフトウェアてあれば , 組み込み機能が 文字列のために用意されている。また文字 っているのに対し , C 言語はかなり機能が少 列を丸ごと代入することが基本操作として 多いにこしたことはない。たとえば表計算 ない言語の一つて、ある。しかし , それによ 備えられている。しかし , C 言語て、は文字列 ソフトて , ある列のデータの総和を求める って C 言語て、て、きることが制限されてしまう は基本データ型てはない。したがって , 文 のに , いちいちプログラミングをしなけれ のかというと , 決してそうて、はない。いや , 字列操作は組み込み機能としては存在しな ばならないようては使い勝手が悪すぎて絶 むしろ逆て、あるとすら思えることが多い い。なぜ基本データ型に文字列が含まれて 望的てある。しかし , 組み込み機能をいか C 言語て、は関数を定義することて機能を増 に大量に用意しても , すべての要求に答え いないかは , もとをただせば C 言語の設計者 加て、き , また , 必要な仕事を実現するため の趣味によるということになるが , 本来の るのは不可能てあることも事実だ。たとえ の基本的かっ原始的な機能 ( プリミテイプ ) C 言語の目的て、あるシステムプログラミング ば表計算ソフトのほとんどすべては総和を を備えている。さらに , 豊富なライプラリ 求める組み込み機能を有しているだろうが , においては , 文字列を組み込みのデータ型 関数が用意されているため , 多くの場合 , 2 乗和に ( x * x ) ) を求める機能を有しているソ とすることて , ある局面て、生じるかもしれ ライプラリ関数を呼び出せばことが足りる。 ない実行効率の低下を許容てきないと判断 フトはどれくらいあるだろうか ? もっと ライプラリにない機能も , プリミテイプと 一般に n 乗和を備えるソフトはあるだろうか ? したためてあろう。とにかく , C 言語ては文 既存の関数を組み合わせて新しい関数を定 字列の実体そのものに関する操作はライプ そして , もし備わっていない場合には , そ 義することにより , 必要な機能を容易に追 ラリ関数を呼び出す形て行わなければなら の機能を容易に定義てきるだろうか ? 加てきるのて、ある。この場合 , 言語にプリ ある機能が最初からは備わってはいない ミテイプが必要十分に備わっているかどう 一方 , 「文字へのポインタ」は , ポインタ が , その機能を容易に実現て、き , 利用する かが重要て、ある。 て、あるから C 言語における基本データ型の一 のはもっと容易てあるならば , 「機能が備わ 組み込み機能の豊富な言語て、はプリミテ っていない」ということがどういう欠点にな ってある。なお , 文字へのポインタとか , イプをおろそかにしてしまう傾向が往々に るて、あろうか ? 欠点があるとすれば「ほか 文字配列 ( Char の配列 ) へのポインタは存在 してある。そのため , 組み込み機能の豊富 の言語と違う表記をするための誤解」てあろ するが , 文字列が基本的データ型て、ない以 な言語てはコンビュータにとって本来は非 う。とくに「一部似たような記述がてきる 上「文字列へのポインタ」というものは存在 常に簡単なはずの作業てあるにもかかわら が , 実はその意味はかなり違うし , ほかの しない。したがって , 文字列へのポインタ ず , 実際のコーディングて非常にまわりく 場合は全然違う記述をしなければならない」 はほかのすべてのポインタと同様に , 代入 どい記述をしいられることがある。 C 言語て、 というのは最悪の誤解を生む原因となりう したり比較したりすることがてきる。また , はそのようなことはまれにしか起こらない きだあきら 0 ニロ 100 C MAGAZINE 1991 4
ボ - ランドジャパン lnformation from Compi1er Makers Q Turbo C 十十で拡張された seg 修飾子とはどのようなものでしよ う力、 ? A seg は , セグメントボインタ 型を宣言するための修飾子て、す。 ポインタは 16 ビットて、管理され , 80X86 のセグメントを示します。 単純なセグメントボインタは , オフセットが 0 て、ある far ポインタと 考えることがてきます。整数を加 List 1 1 : #include く dos. h> 2 : 3 : void cls(void) 減するときには , 通常のポインタ と同じようにポインタの指す型の 大きさと値を乗じたオフセットを 指します。また , セグメントボイ ンタと near ポインタを加算する と , それぞれをセグメント : オフ セ、ツトとする far'* インタを生成て、 きます。このとき , それぞれのポ インタは同じ型へのポインタて、あ るか , 一方が void へのポインタて、 なければなりません。 List 1 , 2 は , それぞれ far ポイン 5 : 9 : 8 : 7 : 6 : unsigned far *text (unsigned far *)MK_FP(0xA000. の : i nt i : for ( i = 0 : i text[i] く 80 * 24 : i + + ) 10 : } List 2 1 : #include く dos. h> 2 : 3 : void cls(void) 5 : 6 : 7 : 8 : 9 : *text 0 : i く 80 * 24 ; text[i] for ( i i nt i : unsigned -seg i 十十 ) (unsigned -seg * ) 0XA000 : 3 : 5 : 8 : 9 : 10 : 12 : 13 : 15 : List 3 char near *b_off; char _seg *b_seg . 6 : void foo(void) 4 : char far *buffer: 2 : #include く dos. h> 1 : # i nc lude く a Ⅱ oc. h > buffer ニ farmalloc(10000) : b_off + + : * (b-seg + b-off) for ( i ニ 0 : i く 10000 : i + + ) { b_off ニ (char near *)FP_0FF(buffer) : b-seg - (char -seg * ) FP_SEG(buffer) : タ , セグメントボインタを使って テキスト画面をクリアするプログ ラム例てす。 セグメントボインタを使うとオ フセットの計算が単純化されたり 16 ビット ( 1 ワード ) て、管理されるた め , 可能てあれば , 自動的にレジ スタ変数を割り当てることがて、き るなど , far ポインタよりも効率の よいメモリアクセスが行われます。 List 3 は , セグメントボインタと near;* インタを使って , メモリを アクセスするプログラム例て、す。 Q B 引でグラフィックス画面を初 期化した後 , pc98fkeyon を使ってフ ァンクションキー表示を行ってい ますが , setgraphmode を使うとファ ンクションキー表示が消去されて しまいます。 A BM-PC て、は , 通常ファンク ションキーを表示する機能はあり ません。 BGI(BorIand Graphics lnterface) は , IBM-PC 版の Turb0 シリーズとの互換性に配慮して初 期化時にファンクションキー表示 を含めテキスト画面を消去するよ うになっています。 closegraph を 行うと , ファンクションキーの状 態は initgraph を行ったときの状態 を復帰 ファンクションキーの表示状態 ・ closegraph, restorecrtmode て、 退避した後 , 消去 アンクションキーの表示状態を ・ initgraph, setgraphmode< フ は , 次のように退避・復帰します。 ファンクションキーの表示状態 de の実行時にも行われます。 が , setgraphmode と restorecrtmo に戻されます。これと同様のこと Turbo C 十十 Ver. 1.0 ファンクションキーをつねに表 文字コード 0 を書き込む場合は , 「しているとみなしてしまいます。 関数はその文字列がその位置て、終 に、、 \ x0 〃を入れると , 文字列を扱う て使われています。文字列の途中 ばれ文字列の終了を表す文字とし 文字コード 0 は , ヌル文字と呼 A き込まれません。 みたいのですが , 第 \ x0 ″としても書 fprintf で文字コード 0 を書き込 Q ます。 アドレスなどが変更されてしまい 数領域やスタック上の関数の戻り るため , 場合によってはほかの変 て、は 2 バイトの領域が書き換えられ トの領域しかありませんが , 、、 % d 〃 を想定しています。 char 型は 1 バイ が int 型 ( 2 バイト ) を指していること 、、 % d 〃は , 与えられたポインタ A かしくなるようです ( List 4 ) 。 式を使うとプログラムの動作がお ませたいのですが , scanf で第%d″書 Q 。 ha 。型の変数に数値を読み込 fkeyon を呼び出してください や setgraphmode を実行後に pc98 示しておきたい場合は , initgraph 点以下が切り捨てられます。また , 値を整数値に代入する場合は小数 Turbo C 十十て、は , 浮動小数 A てしまうのはなせですか ? のように一行にまとめると 0 になっ は整数変数 n が一一になるのに , ( 2 ) List 5 のプログラムで , ( l) で Q ください fputc や fwrite などの関数を使って 150 C MAGAZINE 1991 4
ライフボ lnformation from C0mpiler Makers 13 : } 13 : } Q プログラム内にファイル名を 指定してファイルをオープンして も , うまくいかないことがありま す。 List ーのプログラムでファイル myfile はできますが , ファイル test はできません。 A 文字列の中て、は , \ 記号は特別 な意味があります。 \ n は改行 , Yt はタブなどて、す。て、すから , 文字 列の中て、 , \ を表すためには , \ \ と 記述する必要があります。したが って , 、、 f:Ytest" は , 、、 f:YYtest" と書 かなければなりません。 "f:Ymyfile" の場合は , \ の後に n や t などの指定 うになります。 List 1 のプログラムは , List 2 のよ ているため , 偶然うまくいきます。 て , 変換しないということになっ 文字がないときのエラー処理とし 外の文字を見つけるまて、読み込み , 字て、 , 、、 0 ~ 9 〃 , が , ANSI 規格て、は , \ x に続く文 く 2 桁を 16 進数として扱いました がされています。従来は , Yx に続 の扱いが ANSI 規格に準じた変史 Ver. 4.0 以降 , 文字列内の \ x A Q fprintf(stdprt, "%xlbA<") : 力できていたのですが・・・ まいます。 Ver. 3.22 ではうまく出 ムでは , 0xba ミく″が出力されてし したいのですが , 以下のプログラ く″を出力して , プリンタを制御 タにエスケープシーケンス A ″ Lattice C Ver. 4 」で , プリン 最後の 2 桁を 16 進数として扱うよう に変更されました。したがって , l•. の場合には , 、、 \ x 〃に続い 1bA 〃 を読み込み , 最後の 2 桁を 16 進数と して扱うのて、 , 0xba となります。 また ANSI 規格て、 , ふたつの並んだ 文字列は , ひとつの文字列に結合 されるということになりましたの て、 , E 記の例は , 以ドのように書 いていただく必要があります。 fp 「 intf(stdprt,"%xlb" "A<"); 文字列の間には , 0 個以 E の空 自 , タブ , 改行文字が誓けます。 Q 警告「 Warning137 Huge ポイ ンタが far ポインタに変換された」が 表示されることがありますが , huge ポインタと far ポインタは , どこが 違うのですか ? A huge ポインタも far ポインタも 32 ビットボインタて、すが , huge;tk インタはポインタ計算の結果 , オ フセットから繰り E がりがあった とき , セグメントに繰り E がった 分が加算されます (Fig. 1 ( 例 a ) ) 。 方 , far ポインタのほうは , ポイン タ計算の結果 , オフセットからの 繰り上がりがあっても , セグメン トに加算しません (Fig. 1 ( 例 b ) ) 。そ のぶん , ポインタ計算が速くなり ます。以上の違いがあるために この警告を出しています。 Q りたいのですが , 64K バイトを越え 546 バイトの構造体を丐 0 個と るところでアクセスできなくなり ます。何かよい方法はありません か ? A ンバにアクセスて、きなくなります トにまたがって定義されると , メ ' する場合 , 構造体がセグメン タを使用します。構造体の配列を アクセスするために , SI ・ DI レジス コンパイラは構造体メンバに 4 : F ILB *fp : 2 : ma i n ( ) #include く stdiO. h> List 1 1 : 5 : 6 : 7 : 8 : 9 : 12 : Lattice C (Fig. 2 ( a ) ) 。またがらないようにす るためには , ひとつの構造体の大 きさが , 65536 をきれいに割り切れ る数字て、ある必要があります。 546 以 l•. て、 , いちばん小さい 65536 を割 切れる数は , 1024 て、す。 478 バイト のダミーメンバを定義する必要が あります (Fig. 2(b))0 また , キーワ ード pad を指定すると , これが自動 的に計算されます (Fig. 2(c))0 List 2 #include く stdio. h> fclose(fp) : fwrite("test i f()p - fopen ("a:yymyfile" fclose(fp) : fwrite( ” test file ” . 9. if()p ニ f 叩 en("a:yytest", FILE *fp; 2 : ma i n ( ) if(fp fopen ("a:ytest" fclose(fp) : if()p = fopen( ” a:ymyfile" fwrite("test file", 9. I. fp); b 十 2 の結果は , a000 : 0001 a 十 2 の結果は , b000 : 0001 a = b = a000 : 幵 ff のとき , char far *b; char huge *a; fclose(fp) : fwrite("test file ” . 9. l, (o) : 4 : 5 : 6 : 7 : 8 : 9 : 10 : ( 例 b) ( 例 a) f Ⅱ e ". 9. (a ) Fig. 2 9976 : 0000 (c) 8976 : FFFO - S ト団でアクセス不可能 アクセス可能 (&struct-ary[120D &struct-ary[O] 構造体配列の 1 要素のべースアドレス struct step { / * 546 バイト * / char dummy[478] ・ -pad struct ste { * 546 バイト * / lnformation f 「 om Compiler Makers 153 / * 478 バイトのダミーが自動的に挿入される * /
たとえば , プログラムが ROM になってい に固定して使用するような用途も増えてい るようなケースを除けば , MS-DOS 上のコ るため , 文字列定数が変更不能な環境が存 ンパイラて、すべて文字列を変更することは 在すること , また 2 か所以上てまったく同一 ( 規格違反だが , やろうと思えば ) 可能てあ の文字列定数が使用されている場合に , 定 る。なぜなら , 「定数」とはいえ , それらは 数領域に確保する実体を一つにまとめてメ RAM の上にロードされるしかなく , したが モリを節約てきるようにしたいということ って , メモリの内容を書き換えることは物 がある。なお , この決定にもかかわらず , 理的に可能て、あるからだ。 文字列定数の型は array of char て、あり , しかし , 今後作成するプログラムは文字 array of const char て、はないことは若干注 列定数の値を変更しないように注意してプ 意が必要て、ある。 ログラムする必要がある。 ANSI がこのよう 従来は , 文字列定数の値はいつ変更され に定めた理由には , そもそも定数を変更す るか予測てきなかったため , 同一の文字列 るというような行為はやはり不自然て、「バグ て、も登場するたびに静的データ領域に格納 の素」になりやすことと , C プログラムを ROM しなければならなかった。現在て、は重複し Fig. 3 List 5 ないし List 6 の翻訳結果 data 領域 ー puts puts の呼び出しに対して , 配列 b 窈直、すなわち先 良腰素へのポインタ ( 先頭 要素のアドレス ) を渡す LiSt Ⅳ 0 ' 1 : #include 2 : 3 : vo i d 4 : foo(void) 6 : Char 7 : puts(b) : 8 : く stdio. h> "hello, world ”・ 違いは ,List 1 て、は文字列定数の先頭文字へ のポインタ ( 先頭文字のアドレス ) が関数 puts へと渡されるのに対して , List 5 て、は ( 初期 化がなされた ) 文字配列 b の先頭要素へのポ インタが puts へと渡されていることて、ある。 すなわち前者は定数へのポインタて、あり , 後者は変数へのポインタて、あるということ が異なっている。 AN の規格では ANSI の規格て、は , 文字列定数の値は変更してはならない ことになった。定数だから変更してはなら ないのは当然のように思われるかもしれな いが , 実は ANSI 以前の仕様て、は , 変更する ことはプログラミングテクニックの一つと して容認されていたのてある。 ANSI 準拠の コンパイラても実は文字列定数の変更は可 能な場合が多い List く stdio. h> 1 : # i nc lude 2 : 3 : vo i d 4 : foo(void) 6 : Char 7 : 9 : 0 W 0 puts(b) : LiSt く stdio. h> く string. h> 1 : # i nc I ude 2 : # i nc 1 ude 3 : 4 : 5 : foo(void) b [ 13 ] : Char 7 : 8 : strcpy(), "hello, world") : puts(b) : 10 : 104 C MAGAZINE 1991 4
•char * d0 d げて , char へのポイン タ変数 d0 を宣言し , 同時に引数として渡さ れた d の値て初期化している。 こが非常に 不自然に見えるという声はよく聞く。なぜ ならば , 通常 * d0 に代入する場合はポイン タ d0 がポイントする先 , すなわち「 char のオ プジェクトへの代入」が行われるのに , この 部分てはあたかも * d0 に代入しているかの ように見えるにもかかわらず , 実際には d0 への代入 , すなわち「 char * の変数への代入」 が行われているからてある。 こういうものだと納得するしか これは , ないだろう。この混乱は , C 言語の設計者が 「変数の宣言時には , その変数が使用される とき同じ形式を用いて型を指定する」という 方針を取ったことに起因している。変数宣 言のとき , ヾ * 〃は変数の型を指定するため の「飾り」てあり , 実際の「ポインタ剥し ( der eference) 」を行うことはないのだということ てある。 List 1 : #include 3 : VO i d 4 : foo(void) 6 : Char 7 : 8 : 9 : 10 : } く stdio. h> ” hello, world ” PUtS(P) : List 1 : # i nc I ude 2 : 3 : VO i d 宿 4 : f00 (void) Char PUtS(P) : く stdio. h> ” hello, world ” Fig. 2 List 2 の翻訳結果 定頁域 スタック領域 static の文字配列 本題にもどろう。文字列定数を用いてポ インタ変数の初期化を行った場合には , そ の文字列の先頭文字へのポインタ値を用い て初期化されるということを再び確認して おこう。決して文字列定数そのものが初期 値として設定されるわけて、はない が , 配列の初期化においては , 文字列定数 による初期化は異なる意味になる。 List 5 ても , List 1 ~ 3 と同じ出力が得ら れる。しかしこの場合 , 文字列定数はポイ ンタを生み出しているわけてはない。それ は文字列そのものとして配列の初期化に使 われている。つまり List 5 は , List 6 のよう に書き換えても , 実際に生成されるオプジ ェクトには ( おそらく ) 違いはない コンパイラによる List 5 ないし List 6 の翻 のが , 変数の宣言時の初期化ては「 * 」が付 しい部分の一つが , このようなポインタ変 訳結果は Fig. 3 のようになる。 static char 数の宣言時の初期値設定て、はないだろうか。 加された char *p ” string ” ; となってし b [ ] の宣言を見た時点て , 文字配列 b の領域 まうのて、ある。ほかにも , List 4 ては 6 行目 代入として書けば p ” string ” ; となるも ih' putS 関数 f00 内部で変数 p の値として先頭のアド レスを言聢する puts の呼び出しに対して 変数 p の値を渡す はポインタによるポイントを表す →は値の移動 ( 引数鍍し , 代入 ) を表す 'VO' 102 C MAGAZINE 1 1 4
員 mo 警告を見逃してしまうプログラマは予想外 に多い。コンパイルエラーが出ないため , 実行してみるまて誤った操作てあることが わからない。いや , 実行してみても検出て きないかもしれない。ポインタ b は宣言した だけて初期化が行われていない。すなわち どこをポイントしているかわからない。そ のどこかわからない先へと strcpy は文字列を コヒ。ーしてしまうのてある。つまりメモリ の一部を不当に壊していることになる。そ れがたまたま OS の重要なコード部分だった りすると , 致命的なシステムクラッシュを 引き起こすことになる。そうてなくても , 物スタックの一部を上書きしてリターンアド レスが書き換わってしまうだけてプログラ ムは暴走してしまう。 b の値が , 偶然無害な 領域をポイントしていれば , 一見正しく動 作してしまうことがあるかもしれない。し かしそれは単なる「好運」の結果て、 , まさに 爆弾を抱えているようなものてある。この 例に限らず一般に ポインタ変数を宣言しただけではポイ ント先のオプジェクトは確保されない ということは肝に命じておこう。そして , 何がポインタを初期化し , 何が配列を初期 化しているのか , ソースの微妙な違いを確 実に読み取れるようにしておかねばならな ーっの誤ー 一文字列の一 C 言語ては , 通常文字列を連結する操作は 関数 strcat を呼び出して行われる。 strcat と いう関数を C 言語て記述すると List 12 のよ うになるだろう。 こて重要なことは , 第 1 引数がポイント している文字列の終端の ' \ 0 ' を探し , その 上から上書きして第 2 引数がポイントしてい る文字列を ' \ 0 ' が登場するまてコヒ。ーして いることてある。したがって , 文字列連結 の操作が期待したとおりに行われるために List 13 1 : #include く stdio. h> 2 : #include く string. h> 3 : 4 : #define BUF し EN 128 / * 十分なサイズ * / 5 : * 正しい文字列の連結例 , その 1 7 : 8 : 9 : vo i d 10 : fool(void) Char Char 14 : strcat(), q) : PUtS(P) : * 正しい文字列の連結例 , その 2 20 : 22 : void 宿 23 : f002(void) 24 : { Char strcat(p, 27 : puts (p) : 28 : 29 : } 30 : * 正しい文字列の連結例 , その 3 32 : 33 : * / 34 : void 35 : f003 ()o id) 37 : char strcpy (p, strcat(p, 40 : puts (p) : 42 : } 43 : 44 : / * * 正しい文字列の連結例 , その 4 45 : 47 : vo i d 48 : f004(void) 49 : { Char Char 54 : 55 : 59 : #include く stdlib. h> 朝 * 正しい文字列の連結例 , その 5 63 : void p[BUFLEN] = "hello, ” world ” p[BUF し EN] = "hello, "world") : p[BUFLEN] ; ” he 日 0 , " ) ; ” world"); buffer[BUPLEN] : buffer; strcpy(p, strcat(p, puts (p) : "hello, " ) : "world"); ANSI C ー more 107
List 10 て、は明らかてあるが , List 8 , List 9 においても文字列定数の値 ( 各文字と最後 の ' \ 0 ' ) が関数 fo 。の実行時に毎回配列 b に対 してコヒ。ーされるのて、ある。このことは実 行効率の低下が起きるかもしれないという ことを意味している。もし auto< ある必要 がなければ , 初期値を伴う文字配列は static にしておくほうが実行速度的には有利てあ ると思われる。 ー数と文ー ーれた配ー List 1 ~ 3 の場合 , puts に渡されたポイン タのポイント先は ( 物理的に変更可能かどう かは別として ) 変更してはならない。一方 , List 5 ~ 8 の場合には , 文字列の長さを延長 しないかぎり , ポインタのポイント先を変 更することは合法的な操作て、ある。一方は 定数をポイントしているのに , 他方は変数 をポイントしているからて、ある。しかしソ ースプログラムの表面的な記述上て、は両者 の差が判然としない。これが第 3 のワナてあ る。なお , List 5 ~ 8 の場合も , 文字列の長 さを延長することが許されないのは , 配列 変数 b のサイズが初期化に使われた文字列定 数の長さにちょうどびったりになるように 確保されているからてある。つまり拡張し た文字列を格納する余地がないのだ。 まちかった文字列の値設定の例 List 11 に文字列定数による変数の値設定 の典型的なプログラムエラーの例を三つあ げておこう。 f001 , f002 はコンノヾイラが工 ラーを発する。しかし , なぜこれがエラー になるのか , ちゃんと理解てきるだろうか ? この中ていちばんこわいのはいうまても なく f003 の場合て、 , これはコンパイラ時に はエラーが発生しない。ただし「値を与えら れていない変数邸 b 〃が使われている」という 警告を発するコンパイラは存在するのだが , List 1 : #include 2 : # i nc 1 ude 3 : 5 : * f001 はコンパイルエラーになる 6 : 7 : VO i d 8 : f001 (void) 14 : } 鷽 * f002 はコンパイルエラーになる 19 : VOid ' 20 : f002 ()o id) 22 : Char 24 : 26 : 28 : } 29 : * f003 はコンパイルエラーにはならない 32 : ー 33 : 34 : VO i d 35 : f003(void) Char 38 : 39 : ・ 40 : く stdio. h> く string. h> b [ 13 ] : Char ” hello, wo ⅵ d ”・ puts(b) : / * 配列には代入できない * / *b = { / * ポインタの場合 , こんな初期化はできない * / 0 puts(b) : strcpy(), "hello, world") : puts(b) : / * コピー先が確保されていない * / List 12 1 : char 2 : *strcat 、 (char *d, const char *s) 4 : Char *d0 5 : 6 : / * ステップ 1 : 文字列 1 の終端を探す * / 7 : while ()d ー 8 : 9 : / * ステップ 2 : 終端の ' \ 0 ' の位置から文字列 2 をコピーする * / / * ステップ 3 : 文字列 1 の先頭へのポインタを返す * / return d0 : 106 C MAGAZINE 1991 4
C の導具箱 ーキーの表示内容をユーザのプロ グラムにあわせて moji て、指定する内容に変 更する。また , リバースて、表示される色を color にする。 応 C 鬣語 List 1 chgvatr(SKYlREVERSEE, 0 , 0 , 65 , 0 , の : chgvatr(WHITEIRBVERSEE, 66 , 0 , 79 , 0 , の ; printv("* *",WHITEIREVERSEE,67,0, の ; printv("C:YY", SKYIREVERSEE, 0 , 0 , の : pr i ntv ( " > メニューを選択して下さい [ スペース : F I LE/TRE E] " , WH I TE, 0 , 23 , の : printv("FILE(Ver 1.03 ) " , PURP し E , 66 , 23 , の : wattrset(&winl, WHITE) ; wattrset(&win2, WHITE) : kattrset(&winl, WHITE) : kattrset(&win2, WHITB) : mvwprint(&win1,0,0,"/"); mvwprint(&winl, 1 , 0 , "/TMP") : mvwprint(&win1,2,0,"/H"); mvwprint(&winl, 3 , 0 , "/CTREE") : mvwprint(&winl, 4 , 0 , LLAPP") : mvwprint(&win2, 0 , 0 , " mvwprint(&win2, 1 , 0 , "MSDOS mvwprint(&win2, 2 , 0 , "FONT24 wattrset(&win3, WHITE) ; wattrset(&win4, WHITE) : kattrset(&win3, WHITE) : kattrset(&win4, WHITE) ; mvwprint(&win3,0,0,"/"); mvwprint(&win3, 1 , 0 , "/TMP") : mvwprint(&win3,2,0,"/H"); mvwprint(&win3, 3 , 0 , "/CTREE") : mvwprint(&win3, 4 , 0 , LLAPP") : mvwprint(&win4,0,0,"10 mvwprint(&win4, 1 , 0 , "MSDOS mvwprint(&win4, 2 , 0 , "FONT24 tlwfresh(&winl, "TREE") : tlwfresh(&win2, "FILE") : chgfnc70(fnc1,WHITE,0); do { ー 8 0 、ー 4 り朝っ -4 戸 0 ー 8 0 1 ムワなっ 0 4 L.D ー 8 0 ) 0 11 り 0 っ 0 -4 ・ - -0 ^ 0 ー 8 0 11 ワ 0 っ 0 4 ・′ 0 々ー 8 0 、 1 っ 0 、ーよ、 1 1 1 よ 11 、ー 4 1 ー・ 1- よ 1 ー 1 、ー・ 1 よ 1 tlsubwin( ) 関数 ( CM910 2 ) ウインドウの定義および / ヾッフアの確保 【引数】 * pw ←ウインドウ関連のパラメータを 格納する構造体のポインタ ←ウインドウの左上端の x 座標 xl ←ウインドウの左上端の y 座標 ←ウインドウの右下端の x 座標 x2 ←ウインドウの右下端の y 座標 【戻り値】 正常終了の場合はウインドウのポインタを , 指定にまちがいがあった場合 , およびメモ リの確保に失敗した場合には NULL を返 す。 イ一 4 ・ 0 0 0 0 0 0 0 0 0 0 0 0 ワ〕 っ 0 00 CO 、ー人 1 ー 4 1 ー・ ー行ーっ 8 8 行ー 8 8 8 ^ 0 8 0 -. 0 っ 0 ー 尸 0 LO SYS SYS FNT 【機能】 (xl, (l) を始点とし , (x2, y2 ) を終点とす る大きさのウインドウとして pw を定義す scandi r (p luskey) switch(ii){ case SPACEE ・ if(flag== の { flag=l; werase(&winl) : tlwfresh( ) 関数 ( CM9104 ) ウインドウバッフアの内容を 画面に表示 ( タイトルも表示 ) ←ウインドウ関連のパラメータ を格納する構造体のポインタ タイトル 面 画 期 初 の 4 【引数】 * PW *title ← 【戻り値】 正常終了の場合は 0 を返す。 【機能】 pw て定義されたウインドウバッフアの内容 を画面に表示するとともに , ウインドウの 左上にタイトルを表示する。 応用 c 言語 95
拡張 C として の C + + 派生させるとき , 基本クラスに対し public の 同じ名前にしたほうがプログラムが見やす 仮想関数 指定を行わない , 基本クラスのすべてのメ くなる。このように , 派生クラスの中から , 同じ名前の基本クラスのメンバ関数を呼び ンバを派生クラスの非公開部に置く場合は , 出す場合に , スコープ演算子「 : : 」を用いる 基本クラスのプロテクテッドメンバは派生 仮想関数は , 派生クラスを用いる場合に (List 13 の 20 行目 ) 。 クラスの非公開部に置かれてしまう (List 使用され , プログラムの実行時に決定され こまて、解説してきた派生クラスは , る型 ( クラス ) のメンバ関数を呼び出す機能 11 , List 12 ) 。 つのクラスをもとに新しいクラスを派生さ ( メカニズム ) て、ある。派生クラスては触れ 派生クラスを使用するプログラムて、は , せるものて、ある。 しばしば , 基本クラスのメンバ関数と同じ なかったが , 派生クラスのポインタは基本 いくっかのクラスを組み合 名前のメンバ関数を派生クラスにもっこと クラスへのポインタ変数に代入することが そのほかに , せるような形式て、新しいクラスを派生させ て、きる (List 14 ) 。 がある。 List 9 のように , 基本クラスのメン る機能がそなわっているが , 誌面の関係て、 このとき , p->print() として関数の呼び バ変数を表示するメンバ関数と派生クラス 割愛する。 出しを行うと , 基本クラスのメンバ関数 print のメンバ変数の値を表示するメンバ関数は , Fig. 8 基本クラスと派生クラス 基本クラス class date int yy, mm, dd; public : dset( ) VOid 派生クラス 2 public date 派生クラス 1 class employee name[20J; Cha 「 i nt age; public: VOid class employee name[20]; Cha 「 i nt age; public: VOid d ate eset( ) eset( ) 非公開部 公開部 拡張 C としての C 十十 79