最新 コンバイラレポート は 79 の C ソースファイルをひとつにまとめた sampler. 88 ファイルと , これをコンパイル , リンクして , 実行するためのプログラム runtest. c などからなるものてある。 runtest. c のソースはおもに UNIX 系の処理 系用に書かれているのて , 一部コンパイル コマンドなどを書き換える必要があるが , 書き換えさえきちんとすれば , すんなりと PowerC コンノヾイラをとおった。 runtest. exe は , sampler. 88 を入力ファイルとしてリダイ レクトして実行すると , これを 79 のソース ファイルに分割して , それぞれをコンパイ ル , リンク , 実行するようになっている。 そして , コンパイルてきたか , また正し く実行てきたかをみて ANSI への対応度を測 るわけてある。 テスト結果 各テストの結果は , TabIe 3 を見ていただ きたい。誌面のつごうもあるのて , ここて はいくつか目についた点について解説する にとどめる。 サンプル 1 は , 円記号 ( バックスラッシュ ) て終わる行の連結処理に関するテストてあ る。これは , ソースては次のように書かれ ている。 int/* SPACE */Ui ; わち , 下記のようになるということてある。 しく空白文字として取り扱っている。すな メントも , たんに取り除くのてはなく , 正 けはパスしている。 / * * / て区切られるコ は , 1 と同じ理由てある。それ以外のひっか サンプル 2 がコンパイルてきなかったの PowerC< はこれをサポートしていない のように解釈されるべきてある。あいにく れているのて , これは , 行文字を消して行を連結するように決めら \ て終わる行は , その円記号とその次の改 また , 隣接する文字列リテラルの連結も正 しくサポートされていた。 "st 「 ingl#""st 「 ing2' PCHAR(Pc) "string1#string2' PCHAR(Pc) サンプル 3 はトリグラフシーケンスのテス トて , これはほかの処理系に先駆けていち 早くサポートしたようだ。 サンプル 5 は float. h についてのテストて , MB LEN MAX 記号定数についてチェックし ているが , PowerC はサポートしていない。 ただしこれは ANSI の 1987 年 12 月の時点ての 要求てあり , 現時点てはサポートしていな くとも問題はないてあろう。 サンプル 7 , 8 , 9 はスコープ規則のテスト てある。変数名とラベルに同一の識別子を 使っているためにコンノヾイルエラーとなっ た。 ANSI< は , 識別子は所属する名前空間 ( オプジェクト , 関数 , ラベルなどなど ) が 異なれば , 同一の識別子を使えることにな っているのて , これはコンパイルてきなけ ればならない。サンプル 9 は , 異なる構造体 定義て , 同じメンバ名を使えるかどうかの テストてこちらは合格した。 サンプル 13 ては , 宣言時の型の指定につ いて調べている。 PowerC< は , signed, unsigned は , 必ず int や long よりも前に指定 するようになってしまい , これは問題あり。 サンプル 14 は , 定数の末尾に U , L などを つけて , unsigned, long 型の定数になるか のチェック。大 / 小文字どちらても OK だっ サンプル 15 は文字定数に関するテスト。 ANSI ては文字定数は , ' ( シングルクオート ) て囲まれたひとつ以上の文字としている。 これは , 多バイト文字 ( たとえば日本語 ) を 利用てきるよラにとの ANSI による拡張てあ る 0PowerC< は , あくまてもシングルクオ ートて複数文字を囲むことは許していない のて x 。しかしながらこれは日本語処理が てきないということてはない。同様にサン ↓ ↓ プル 18 は , 多バイト文字系を処理するため に ANSI に盛り込まれた wchar t 型のチェッ クてある。これに関しては PowerC< はまっ たくサポートしていない。ただし , 現時点 てこれをサポートしている DOS 上の C コンパ イラ処理系は見当たらないし , この規約を 正しくサポートすると , 現在利用されてい る日本語処理関数にも大きな影響がててく るのてはないだろうか ? サンプル 19 は , 古いコンパイラてサポー ーといった代入演算子 トされていた = 十 , が廃止されているかのテスト。もちろん使 えなかった サンプル 21 は , float 同士の演算精度につ いて。 ANSI ては float の演算は利 oat て行って よいことになっているが , PowerC< は , K& R の第 1 版に準じて必ず dou e て演算してし まう。 サンプル 22 は , 配列 , 関数のポインタに 関するテストてある。関数のポインタにつ いては , ANSI ては次の式は真となる。これ は PowerC ても問題なかった。 void func( ) ; func 配列のポインタについては次の式が真と なることが要求されるが , こちらはコンパ イルてきるが正しく実行てきなかった。 char a [ 10 ] ; sizeof( * (a) : sizeof(a) 配列名の sizeof は , 配列自体の大きさを意 味するのて , sizeof(a) は正しく , 10 とな る。しかし , sizeof( * (a) は 2 となってしま った ( スモールモデル ) 。これは PowerC が ,& a を配列のポインタへのポインタとみなし , * &a をポインタ自体とみているため , sizeof ( * (a) は 2 になってしまうのだろう。配列名 だけては変数とはならない ( もちろん代入て きない ) という , 初心者がよくまちがえると ころて、もある。 サンプル 29 は , 次の式のように右辺値を sizeof に指定した場合の値のテストてある。 &func ; PowerC ても次の式は真となる。 最新 C コンバイラレポート I 139
五ロ はじめて学ふプロクラー ニンク に値を直接代入すること自体が誤った使用 法なのて、すが・・・ ファイルの位置を獲得する関数 fgetpos と ファイルの位置を設定する関数 fsetpos を用 いたプログラムを作ってみましよう。ファ イルから int 型の数とそれに続く文字列を読 み取り , 数の大きい順に並び換えた新しい ファイルを作るプログラムて、す。 読み込むファイルは int 型の数の後に ( カンマ ) , そのまた後に文字列が続いてい るものとします。 1 行の長さは数と文字列 ( ' \ n ' を含む ) を合わせて 80 桁以内とします。 ファイル関係の関数の説明 List 1 printf(" ファイルの現在位置から 5 移動して % 2d 文字出力・ %s\n", MAX, !fseek( fin, i f ( -5L, SEEK_CUR ) ) { fread( s, sizeof( char ) . MAX, fin ) : printf(" ファイルの現在位置から一 5 移動して % 2d 文字出力 %sYn", MAX, if( !fseek( fin, 0 し , SEEK_CUR ) ) { fread( s, sizeof( char ) , MAX, fin ) : printf ( " ファイルの現在位置から 0 移動して % 2d 文字出力 : %sYn", MAX, if( !fseek( fin, 1 し , SEEK_CUR ) ) { fread( s, sizeof( char ) , MAX, fin ) : printf(" ファイルの現在位置から 1 移動して % 2d 文字出力 %sYn", MAX, if( !fseek( fin, 0 し SEEK_SET ) ) { fread ( s, sizeof( char ) , MAX, f i n ) : pr i nt f ( " ファイルの先頭位置から i f ( !fseek( fin, -5L, SEEK_END ) ) fread ( s, sizeof( char ) , MAX, fin ) : printf(" ファイルの末尾位置から一 5 移動して % 2d 文字出力 : %sYn", MAX, fc 1 ose ( f i n ) : / * ファイルクローズ * / っ -4 ・ - -0 C.D 々ー 8 0 ) 0 , 1 ワ 0 っ 0 -4 ・ LO CD ー 8 0 ) 0 1 ワレっ 0 -4 ・戸 0 ^. 0 % 2d 文字出力・ %sYn", MAX, テータファイル List 1. dat 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ List 2 関数 fgetpos [ ヘッダファイル ] ます。ここて配列 s は , モードてオープンしたのはなぜなのてしょ [ 書式 ] うか ? 関数 fseek をテキストモードてオー int fgetpos( FILE * stream, 01234 プンした場合は , 以下のふたとおりしか許 になっているはずてす。 29 行目ては , 現在 fpos_t * pos ) ; されていません。 こて、使用している fpos ー t 型とはファイル 位置 ( 数字の 5 がある位置 ) から 5 移動してい ① offset の値を 0 にして origin を SEEK の中の位置を指定するための型て、す。多く るのて、配列 s には , SET か SEEK END にする の処理系て、は long 型を typedef しているよう abcde ② origin を SEEK SET にして 0ffsetC){ 直 が入るはずてす ( Fig. 1 ) 。 て、す ( stdio. h を参照 ) 。 fpos t を用いることに をファイルの位置を返す関数 fte ⅱによ 以降はどのように動作しているかを 0 ⅱ gin より , 処理系による部分を吸収した移植性 と offset に着目して考えてみてください。簡 って求めた値にする のよいプログラムを作成て、きます。この関 offset に値を直接代入した場合 , 正常に動 数は *stream のファイル位置指示子の値を 単に理解てきると思います。 作するかは保証されていません。少なくと * pos に格納します。 * pos は関数 fse ゆ OS が も TurboC を使用した場合は期待したとお 位置付けするときに用いられます。関数の モード別の関数 fseek の動作 り動作しましたが , MS ー C ては正常に動作し 戻り値は成功したときに 0 を返し失敗した ません ( テキストモードて関数 fseek の offset ときに ! 0 を返します。 こて扱うテキストファイルをバイナリ Fig. 1 関数 fseek の動作 stdio. h ④ 2 回目の f 「 ead 後の位置 ( 要素数 5 ) ② 1 回目の f 「 ead 後の位置 ( 要素数 5 ) ↓ ↓ ・・・ Z EOF 0 1 2 3 4 5 6 7 8 9 a b c d e f ・ ↑ ① fseek(fin, OL, SEEK—SET) 9fseek(fin, 5L, SEEK—CUR) はじめて学ぶプログラミング 123
型構造体も必要に応じて ma Ⅱ oc することに しましよう。 この凵 ST 型構造体がメモリ上て実際に双 方向リストを形成した状態を示したのが Fig. 3 , この双方向リストをヒープにアロケート して追加する操作をする関数が List 2 てす。 ところ <Fig. 3 をよく見ると , もう少し効 率を上げることがてきる点があるのてすが , わかりますか ? そうてす , p 汁てす。凵 ST 型 構造体も文字列データもいずれもヒープ上 にアロケートされるのてすから , わざわざ 別の場所に ma Ⅱ oc してポインタてさしてや るのてはなく , ひとつのデータ構造にまと めてやれば pt 「はいらなくなります。そのう えデータのアクセスもポインタを通すのて、 はなく直接参照になりますから多少は高速 になるてしよう。 しかし , C 言語ては可変長の構造体を宣言 する方法はありません。何とか C をごまかす という呼び出してアロケートされた構造体 は Fig. 5 のようになります。 このように , 構造体宣言の最後に要素数 Fig. 3 双方向リスト ・ Ptr SUCC ・ pred "data string 1 " ことがてきればいいのてすが・・・ こて 要素が 0 個の配列のテクニックを使うのて す。 このテクニックを使って書き直した構造 体が List 3 てす。ここて要素が 0 個の配列は サイズが 0 てアドレスだけをもっことを思い 出してください。 sizeof( 凵 (T) は dat [ 0 ] を 除いたポインタふたっ ( p 「 ed と succ ) の大き さになります。そのうえ , dat は構造体のメ ンバてすから , 凵 ST 型構造体 x に対して x. dat とすることがてき , Fig. 4 に示すアドレスと なります。このアドレスに文字列を保存す ることがてきれば , うまくごまかすことが てきるというわけてす。 この構造体を使って List 2 の allocList() 関 数を書き直すと List 4 となります。 ma Ⅱ oc の パラメータになっている式に注意してくだ さい。 sizeof ( 凵 ST ) のほかに必要になるエリ アの大きさは , 文字列データの長さ strl en(dat) とターミネータのヌル文字 ( ' \ 0 ' ) の 1 バイトて、す。文字列の長さは , 呼び出しの たびに違っていても , 最適な大きを計算 しますからムダはありません。 allocList("heIIo world") ; 118 CMAGAZINE 1990 11 Fig. 4 Fig. 5 構造体 x X. dat- → x. pred X. SUCC 訓 ocList でアロケートされた構造体 ” hello wo 日 d ” 1 —>dat → X. SUCC x. pred 双方向リストのテータ構造 0 の配列を入れておき , ma Ⅱ oc て文字列を含 んだ大きさの連続したメモリ領域を確保す ることて可変長構造体をシミュレートする ・ pred SUCC ・ ptr data string 2 ” sizeof(LIST) これをひとつの 構造体だと考える 1 : 2 : 3 : 4 : typedef struct 1 ist struct I ist struct I ist *pred; *SUCC : Char い ST : *ptr; / * 前へのリンク / * 次へのリンク / * データへのポインタ * /
藏 C 言ー吾維学講座 List 2 ことがてきます。 AN SI 規格の場合 ANSI 規格てもテクニックは使えます。 0 要素の代わりに 1 要素だけの配列にすればい いのてす (List 5 ) 。 こうするとデータがまったくない場合に は配列の 1 要素分だけ余分なメモリを消費す ることになりますが , 文字列にかぎってい えばターミネータのヌル文字はもともと必 要なのてすから , あながちムダとはいえな くなります。 addList() 内の malloc のパラメ ータは , sizeof(LlST) 十 strlen(dat) となりますが , これを List 4 と比べると十 1 が消えています。これのほうがマジックナ ンバがなくなるぶんだけきれいだと主張す る人もいます。たしかに文字列にかぎると 美しいかもしれませんが , int とか別の構造 体の配列が可変個て、ある場合には結局ムダ が出てしまいます。 以上の理由から , 私は要素数 0 の配列を宣 言することがてきると , とても便利だと田 います。ときどき使うテクニックてすから ね。ても ANSI 規格てはないから , これを使 うと移植性に問題が出てくるわけて・・・ うーん , どうしましよ。 ちょっとしたマクロた さて , 話は変わって , 私が使っているプ リプロセッサのマクロをいくつか紹介しま 0 まずは List 6 。これは皆さんもよくご存じ の無限ループてす。 fo 「 ( ; ;) はカッコの中の セミコロンふたつがまるて泣き顔のように 見えますのて「ナミダの無限ループ」と呼ば れているようてす。 fo 「 ( ; ; ) の代わりに wh ⅱ e ( 1 ) を使う人も多い のてすが , do { リストの追加 1 ワ 3 っ - 履・ 0 ^ 0 ー 8 0 , 1 り 0 00 4 LO 7 ー 8 0 ・》 0 1 ー・ワっ -4 LO ^ 0 ー 8 0 -14 っ 0 っ 0 -4 ・ L-O 《 0 ー 8 0 》 0 1 よっ 0 1 よ 1 よ 1 よ、 1 1 よ、 1 、 1 、 1 1 よ 1 ・ 4 っ乙っ乙ワ 0 0 乙ワワっっ 0 0 乙っ 0 っ 0 っ 0 っ 0 っ 0 っ 0 っ 0 っ 0 っ・ 4 ・ -4 4 い ST をヒープに生成する a Ⅱ ocL i st : *alloc し ist(char *dat) Char い ST = malloc(strlen(dat) + 1 ) return (NULL) : strcpy(), dat) : / * 文字列をヒープに保存する * / ニ malloc(sizeof( い ST)) free(p) : return ()U しい : l->pred = NULL; l->succ = NULL; l->ptr ニ dat; return (l) : ニ NULL) ニ NUL し ) { / * い ST の初期化 リストに新たなデータを追加する add い st: *add い st( い ST *p, char *dat) い ST if ((new = alloc い st(dat)) return (NULL) : new—>pred p—>succ; new—〉 succ if (P->SUCC ! = NULL) p->succ->pred P->SUCC new : *new : = NULL) / * 双方向リストをつなぐ * / new; 0 List 3 双方向リストのテータ構造 ( その 2 ) 1 : typedef struct 1 ist 2 : struct list 3 : struct list 4 : Char い ST; ス レ ア クク頭 ンン先 ののタ 前次デ a. alIocList 関数 ( その 2 ) List 4 , 1 っ 3 っ 0 -4 ・戸 0 行ー 8 9 0 い ST をヒープに生成する ( その 2 ) a Ⅱ oc し i st : *alloc し ist(char *dat) い ST = malloc(sizeof( い (T) + strlen(dat) + 1 ) return ()U し L) : = NULL)
五ロ はじめて学ぶ C プログラー ニンク した n バイトだけ比較する関数てす。文字列 sl の最初の n バイトが , 文字列 s2 の最初の n バイトより小さければ負の値を , 等しけれ ば 0 を , 大きければ正の値を返します。返さ れる値の求め方は st 「 cmp と同じてす。 n より 小さい位置て , * s2 が 0 〃になった場 合 , そこから先は比較の対象にはなりませ ん (Fig. 7 参照 ) 。 この関数 strncmp てはバイナリファイルの コンペアはてきません。なぜなら , バイナ リファイルの中にはヌル文字 \ 0 クがいた るところにててくるからてす。したがって , この講座ては邸判〃てあろうがなかろう が , とにかく n 文字分比較する関数 ps 汁 ncmp を作成し , それを用いて比較を行っています。 簡易コピープログラムの説明 それては実際のプログラムについて説明 しましよう。 List 4 を見てください。 19 行目 ては , 引数の個数をチェックしています。 引数の個数が違うと関数 e 「「を呼びます。関 数 e 「「は 75 ~ 86 行目て定義してあります。 の関数は使用法を標準工ラー出力に出力し た後 , 関数 exit を用いてプログラムを終了さ せます。 関数 de ゆ ath は 97 ~ 106 行目て定義されて います。引数て与えられた文字列 ( フルバス のファイル名 ) からパスの部分を除いたファ イル名の部分の先頭ポインタを返す関数て す。また関数 e 「「てはプログラム名の末尾 . EXE を削除しています。 argv [ 0 ] を関数 de ゆ ath に渡してから 関数 e 「「に渡しているのて , 工ラーメッセー ジにはプログラム名の語幹だけが表示され ます。 23 行目ては第 1 引数て与えられたファ イルをバイナリファイル読み込みモード b 〃 てオープンしています。オープンてきなか ったときはエラーメッセージを出力させた 後に関数 e 「「を呼んてプログラムを終了しま す。 28 ~ 44 行目は引数が 2 個 ( a 「 gc が 3 ) ある場 合の処理てす。 29 行目ては関数 st 「 cpy を用い て文字配列 f ⅱ ename に第 2 引数の文字列をコ はじめて学ぶ C プログラミング 129 List 4 46 : 48 : 49 : 50 : 52 : 53 : 55 : 56 : 57 . 58 : 59 : 60 : 63 : 64 : 66 : 70 : 72 : } 73 : 74 : / * 工ラー処理をする関数 err * / 75 : VOid err( const char *file_name ) char delext[ 9 ] : 77 : 78 : i nt i : 79 : 80 : for ( i = 0 : *f i 1 e_na me ! = delext[ i ] = *fi le_name; 82 : delext[ i ] 83 : 84 : fprintf( stderr, ”使用法 : %s filel [path[file2]] く ret>Yn" 85 : ex i t ( 1 ) : 88 : / * 文字列の最後の文字を比較する関数 tchrc 叩 * / 89 : int lstchrcmp( const char *fi le_name, const char c ) 90 : { for ( ・ *fi le_name; file_name 十十 ) 92 : return( (int)( *--file_name ー c ) ) : / * 等しければ 0 を返す * / 93 : 94 : } 95 : 96 : / * フルバスの格納された配列の、ファイル名の先頭ポインタを返す関数 de lpath * / 97 : char *delpath( char 98 : { 99 : i nt i : 100 : for ( i 101 : 102 : for ( s-- 103 : 104 : 105 : return 十十 S : 106 : } 107 : 108 : / * 文字列 sl と文字列 s2 の n 文字比較 ( ヌル文字も比較 ) 109 : int pstrncmp( const char *sl, Char *S2, Size -t n ) const 1 1 0 : { i nt d i ff : 1 12 : 1 13 : while ( n- 1 14 : 1 16 : 117 : return 0 : 119 : 120 : / * コピー先に同名のファイルが存在するかどうかのチェック 121 : VOid file_exist( const char *filename ) strcpy( filename, delpath( argv[ 1 ] ) ) : / * ファイル名の抽出 * / file-exist( filename ) : / * 同一ファイル名のチェック if ( ( fout = fopen( filename, ニ NU しし ) { fpr i ntf ( stderr, " ファイル %s がオープンできません。 Yn" e rr ( de lpath ( argv [ 0 ] ) ) : filename while ( ( n ー fread( bufl, sizeof( char ) , fin ) ) ) MAX, / * コヒ。一部 * / fwrite( bufl. sizeof( char ) , n, fout ) : rew i nd ( f i n ) : ファイルのリワインド * / rewind( fout ) : while ( ( n = fread( bufl, sizeof( char ) , MAX, f i n ) ) ) { / * コンペア部 * / fread ( buf2, sizeof( char ) , MAX, fout ) : if ( pstrncmp( bufl, buf2, n ) ) { / * データの比較 * / fprintf( stderr, "%s から %s へのコピーは失敗しました Yn" argvC 1 ] , filename e x i t ( 1 ) : fcl ose ( f i n ) : fclose( fout ) : fpr intf ( stderr, / * ファイルのクローズ * / argvC 1 ] , filename "%s を %s にコピーしました。 Yn" f i 1 e-name + + ) / * ファイルの語幹の抽出 * / i 十十 , delext ) : i 十十 ) S 十十 , 汀 ( ( diff = * 十十 return diff,• * S2 十十
可変長構造体 学 本ロ 0 五ロ 乗松保智 C 言語には実行時に大きさが決定されるような可変長の構造 体を作る構文はありません。しかし , 配列を使ったトリック でこれをシミュレートすることかできます。今回はこの可変 長構造体について説明します。また , 便利な ( ? ) マクロ集を おまけにつけます。 Fig. 2 int array[O] ; 要素 0 個の配列 たとえば int 型のオプジェクト 10 個からな る配列を使いたいときには , int ar 「 ay [ 10 ] のように宣言します。にと当〃てはさま れた定数が確保される要素の数を指定して なぜこのような ANSI 規格外の例をもち出 は配列の先頭の要素を指しています。 います。このときにはメモリ上には Fig. 1 の してきたかというと , 実は 0 要素配列を使う ては要素の数を 0 にするとどうなるてしょ ように配列の領域が割り当てられます。そ ことがてきるとちょっとおもしろいことが の大きさは sizeof(int) * 10 ノヾイトて , array てきるのてす。実際 , 多くの処理系は要素 int array [ 0 ] Fig. 1 intarray[lo]; と書いた場合てす。 Fig. 1 の要素の部分を 0 数 0 の配列を許します。 個にしてみると , Fig. 2 のようになります。 array → 長構造体 要素 0 個の配列の大きさは sizeof(int) * 0 てす から 0 バイトてす。つまり a 「「 ay はメモリ上に はまったく場所をとらないオプジェクトと こて頭の体操てす。 【問題】可変長文字列をデータとしても なり , そのくせ array 自身は存在しないオプ ジェクトの先頭アドレスを指していること つ双方向リストを表現するためのデータ構 造を考えなさい。 になります。 可変長文字列を効率よく保存するために これはいったいどういう意味なのてしよ うか。結論からいうと , 意味はありません。 は , ma Ⅱ oc ( ) 関数を使ってヒープエリアにデ ータを溜めていくのがよいてしよう。 ANSI 規格は要素数 0 の配列は許していない また , 双方向リストてすから , 文字列デ のてす。すくなくとも , ひとつの要素が必 ータのほかにもリストを構成するためのポ 要だということになっています。 これ インタがふたっ必要てすね。これらを考え ては , ここて話が終わってしまいますから , とりあえず ANSI 規格は忘れましよう。 ると List 1 のような構造体になります。凵 ST C 言語雑学講座 117 a 「「 ay →ー -4 要素は存在しない一 a 「「 ay は存在しないオプジェクトを指している a 「 ray は先頭の要素を指している
height, h- bound List 8 10 : { 20 : } int *p; #define EXTERN #define EXTERN ール 2 : 4 : 6 : 7 : 9 : 14 : height, h-bound : 配列の要素数と上限 (sizeof(a) / sizeof((a) [ 0 ] ) ) 5 : #define height(a) V 0 i d int for for #define h_bound(a) func() int array [SIZE] : p く h-bound (array) : (i ー 0 : i く height(array); i 十十 ) ((a) + height(a)) array; p 十十 ) List 9 13 : 8 : 7 : 4 : 2 : GLOBAL, LOCAL GLOBA しし OCA い スコープを明示する 5 : #define GLOBAL 6 : #define LOCAL 9 : し OCA し 11 : G し OBAL Char VO i d static bufferC100] : func ( ) (a) MAIN, EXTERN ヘッダファイル (header. h) #undef #ifdef #else #endif EXTERN EXTERN (b) #define MAIN int Char MA I N EXTERN / * すでに EXT ERN が定義されている * / / * ときにはそれを無効にする / * メインモジュールのとき / * その他のモジュールのとき extern g ba 1 1 : g ba 1 2 : メインモジュール #include ” header. h ” (c) その他のモジュ #include "header. h" ~ ・ C 言語雑学講座 後てグローバル変数を追加や削除するとき ふたつのファイルに現れてしまいますのて , と定義が分離していて , それぞれが異なる ことになります。この方法て、は変数の宣言 をつけないて , グローバル変数を定義する す。そしてどれかひとつのモジュールて、 extern ダファイルをインクルードするのが普通て て宣言しておき , 各モジュールはそのヘッ バル変数はヘッダファイルては extern をつけ 分割コンパイルをするときには , グロー 示します。 部に関数名や変数名を見せるのかどうかを 定子の位置て使い , 分割コンパイル時に外 LOCAL は static< す。どちらも記憶クラス指 GLO BAL は実体のないただのこけおどし , 実際の定義はどうなっているかというと , てしよう。 はやめておいたほうがいいと思う人もいる ら平気なのてすが , キーワードと同じ綴り ろん C 十十も大文字 / 小文字を区別しますか は避けたほうがよいかもしれません。もち ワードになっていますのて、 PUBL ℃ , PR Ⅳ ATE す。しかし , C 十十 <public, private はキー の代わりに PR Ⅳ ATE を使う人もいるようて す 0GLOBAL の代わりに PUBL ℃を , LOCAL するために使うマクロ GLOBAL , LOCAL て List 9 は関数や変数のスコープを明らかに す。 要素を先頭から処理していくときに便利て C 言語雑学講座 121 とがてきます。 理てきるのてケアレスミスを未然に防ぐこ す。これならばグローバル変数を 1 か所て管 ジュールては extern つきの宣言になるのて ールては変数の定義になり , それ以外のモ ます。つまり MAIN が定義されているモジュ きには空になり , それ以外ては extern になり こて EXTERN は MA 爪が定義されていると ァイルの #ifdef から #endif を見てください ローバル変数を宣言します。 ( a ) のヘッダフ これを避けるために , List 10 のようにグ すくなるのてす。 に , ウッカリと一方を変更し忘れたりしや
List 3 域とデータ領域を , RAM に BSS 領域を割り 当てるだけのことて、ある。その際に , 0 以外 て、初期化されているデータについてリテラ ルにしてしまう必要があるのもミニマムモ デルの場合と同様て、ある。 また , 0 以外に初期化されているデータ を , スタートアップ時に ROM から RAM へ 読み込むという方法もある。この方法のよ いところは , 前述したとおり , プログラミ ングて、とくに気を使う必要がないことて、あ そして , この方法も ( ROM と RAM の容量 を気にしなければ ) 実現は , わりと簡単て、あ る。つまり , データ領域を RAM に割り当 て , スタートアップ時に ROM からロードす ればよいのて、ある。 それは , たとえば , mov # 0X6000 , 「 4 mov # datalo, 「 5 69 : rdr; *rnext 十十 if (&rbuf[sizeof rbuf] くニ rnext) 70 : rnext = rbuf; ー 0X40 : 72 : ssr & = 74 : 75 : @port void txi ( ) 76 : if (tdone = tnext) 77 : return: while (!(ssr & 0X80 ) ) 80 : tdr ニ *tdone 十十 : if (&tbuf [sizeof tbufJ くニ tdone) 82 : 83 : tdone tbuf : ー 0X80 : 84 : ssr & ニ 85 : / * TDRE * / 割り込みべクタテープル ( ebvec. c ) S , よワ 3 っ 0 ・ 4 ・戸 0 C.D 0 ー 8 0 ) . 0 0 0 0 0 0 0 0 Q. a. Q. a. 1 ーワ 0 00 ・ 4 ・戸 0 ( 0 ー 8 0 1 人 -4 ・ - -0 ^. 0 ー 0 1 よ C'O っ 0 、 4 ・ ( 0 0 ー 8 cmp # datahi, 「 5 bcc L4 mov @「 4 十 , 「 3 mov 「 3 , @「 5 十 bra L3 といったプログラムをスタートアップ中に 付け加え , この例て、はデータ領域を 0X6000 番地から ROM に焼き付けておくのて、ある。 そうすることにより , スタートアップ時に 本当のデータ領域にその初期値がロードさ れるというわけて、ある。 TabIe 1 オプジェクトフォーマットトランスレータ 味 意 インテル HEX , テクトロニクス拡張 HEX , モトローラ S レコード HP ー 64000 オプジェクトモジュールフォーマット ( HP ー OMF ) ESROFF フォーマット COFF フォーマット ソフィアシステムアプソリュートフォーマット ( SAOF ) 0xff7f 番地まて、なのて、 , このような配置にし そま、の てみた。 RO イの方法 図て、表すと Fig. 3 のようになる。 これを見てわかるように , データ領域は 最後にマキシマムモードて、の ROM 化につ 生成されなくなっている。 以上 , 駆け足て、あったが , H8 / 500 を題材 Fig. 2 て、 link の次にある hex というのは , いて簡単にふれておくことにする。 に , C プログラムの ROM 化を中心に述べ ニマムモードて、の例を挙げ 前節て、は , この場合 , モトローラ S レコードに変換する ためのユーティリティて、ある。このほかに たが , マキシマムモードて、もそう大差はな 説明不足の点も多かったかと思うが , 多 も Table 1 に示すように , いろいろなオプジ く , ー x4 て、コンパイル ( すなわち関数がテキ 少なりとも読者諸氏の参考になれば幸いて、 ェクトフォーマットに対応している (Table スト領域 , リテラルがデータ領域 , 0 初期化 ある。 1 参照 ) 。 データが BSS 領域 ) し , ROM にテキスト領 62 CMAGAZINE 1990 11 名称 h ex tOhp ts ro 幵 tcoff tosaof さしま
五ロ はじめて学ぶ 0 プログラミング 第 14 回高木聡 / 山崎信行 標準ライフラリ関数の使い方 3 前号では , ファイル操作に関する標準ライプラリ関数を取り上げました。今 回も引き続き , ファイル関係の関数について勉強します。前号で紹介できな かった関数について , 多少長めのサンプルプログラムを用いて解説します。 にプロック転送し , 配列 s を文字列として扱 進めることになります。 うためのものてす。 関数 fseek を用いたプログラム例 17 行目はファイルの先頭位置にシークし ています。この場合ファイルをオープンし それては ,List 1 を見てください。このプ それては , 復習を兼ねて簡単な例から始 た直後なのて , ファイルポインタは先頭位 ログラムは listl. dat(List 2 ) というファイル めましよう。まず , 関数 fseek を用いたプロ 置にあります。つまり , 実際には何もして をオープンし , ファイル内をシークしなが いないのと同じてすね。 18 , 19 行目はシー グラムを作ります。 らその内容を表示するものてす。 11 行目て クに成功したときの処理てす。この 2 行の実 関数 fsee k [ ヘッダファイル ] listl. dat というファイルをバイナリファイル 行には 17 行目の if 文の式を ! fseek ( ・・・ ) とする 読み込みモード感「 b 〃てオープンしていま [ 書式 ] 必要があります。なぜなら fseek はシーク成 す。 16 行目て読み込み用のバッファ (char 型 功時に 0 を返す関数だからてす ( if 文の式の真 int fseek( FILE * stream, の配列 s ) の最後の要素 s [ MAX ] にヌルを long offset, int origin ) ; と偽の処理を思い出してください ) 。 代入しています ( s は , 9 行目て MAX 十 1 要 この関数は , 0 「 igin の位置から offset< 指 18 行目ては , 関数 f 「 ead を用いて cha 「型の 定されたバイト数だけファイル位置を移動 素の配列として宣言してある ) 。この処理は 配列 s にプロック単位て MAX バイト読み込ん します。この動作のことを一般に感シーク〃 関数 f 「 ead を用いて MAX 個のデータを配列 s ています。 19 行目てその配列 s を出力してい と呼びます。関数の戻り値は , シークに成 関数 fseek を用いたプログラム例 功すれば 0 を返し , 失敗すれば ! 0 を返しま す。 origin には , Table 1 のようなマクロが 用意されています。 たとえば , fin を日 LE 型のポインタとし , 以下のようにします。 fseek( fin, 100L , SEEK_SET ) ; これは , ファイルの先頭位置から , 100 バ イト移動した位置にファイルポインタ fin を TabIe 1 origin に指定できるマクロ 意味 ongln ファイルの先頭位置 SEEK—SET ファイルの現在位置 SEEK—CUR ファイルの末尾位置 SEEK—END stdio. h List 1 1 : # i ncl ud e く std i 0. h> 2 : # i nc lude く std ⅱ b. h > 3 : 4 : #define MAX 5 : 6 : void main( void ) 8 : F I LE *f i n : cha r s [ MAX + 1 ] : 9 : if ( ( fin ま fopen( "listl. dat". fprintf( stderr, ”ファイル %s がオープンできません。 Yn ” ex i t ( 1 ) : 14 : 15 : 20 : 22 : / * 関数 fread の要素数 * / 5 = NU しし ) { / * ファイルオープン * / "listl. dat" ) : / * 文字列の終わりを表す。 if( !fseek( fin, 0 し SEEK-SET ) ) { fread( s, sizeof( char ) . MAX, fin ) : printf(" ファイルの先頭位置から if( !fseek( fin, 5 し SEEK_CUR ) ) { fread( s, sizeof( char ) , MAX, fin ) : % 2d 文字出力・ %sYn", MAX, s ) : 122 CMAGAZINE 19 11 ファイル操作 2
ROM 化 考察 また , 割り込み関数を , 通常の関数と同 じようにどこからて、も呼ぶことがて、きる。 割り込みによる関数の呼び出し後のスタッ クフレームは , 通常の関数とは異なるが , コンパイラはそれに合わせて ( あたかも割り 込みが起こったように呼ぶ ) コードを生成す 旧キー入力をエコーノヾックするプログラム ( ebmain. c ) 1 : @po 社 char smr @0xffd8; 2 : @port char brr @0xffd9; 3 : @port char scr @0xffda; 4 : @port char tdr @0xffdb; 5 : @port char ssr @0xffdc; 6 : @port char rdr @0xffdd; 7 : 8 : @port void eri() : 9 : int getch() : 10 : vo i d ma i n ( ) : 11 : void putch(int c); 12 : @port void rxi(); 13 : @port void txi(); 14 : 15 : char rbuf[32] 16 : char *rdone 17 : char *rnext 18 : char tbuf[32] 19 : char *tdone ま 0 : 20 : char *tnext = 0 : 22 : @port void eri() 23 : 24 : 25 : 26 : int getch() 27 : 28 : register int c; 29 : 30 : i f (rdone ー rnext) rxi(): 32 : C = *rdone 十十 : if (&rbuf[sizeof rbuf] く = rdone) 33 : 34 : rdone rbuf; 35 : 36 : return (c) : 37 : 38 : 39 : 40 : void main()s 41 : 42 : register int c; 43 : 44 : rdone ニ rbuf; 45 : rbuf; rnext 46 : tdone = tbuf; tbuf; tnext 48 : / * asynchronous, 8 bit, non parity, stop bit 1 * / smr 49 : = 0xf2; / * txi on, rxi on, te on, re on import clock * / SCr while (c = getch()) 50 : putch (c) : 52 : 53 : 54 : void putch(c) 55 : i n t C : 56 : putch( ・ Yr'): 58 : 59 : *tnext 十十 if (&tbuf[sizeof tbuf] く = tnext) 60 : tnext ニ tbuf; 62 : txi(); 63 : 64 : 65 : @port void rxi ( ) 66 : while (!(ssr & 0X4 の ) 68 : / * serial mode register / * bit rate register / * serial control register * / / * transmit data register * / / * resial status register * / / * receive data register * / リ ) 実際にプログラムを書いて , それをリン クしてみる。話を簡単にするために マムモードを用いる。つまり , メモリ上へ のマップは , テキスト領域を ROM, データ 領域はなくして , BSS 領域を RAM に割り付 ける。 List 3 は , H8 / 532 内蔵の SCI ( シリアルコ ミュニケーションインタフェイス ) を用い て , 端末装置からのキーポード入力をエコ ーバックするという簡単なプログラムて、あ 最初にポート変数を用いてレジスタを定 義し , 割り込み処理には割り込み関数を用 いている。 「 xi ( ) 関数は受信完「割り込み処理 , txi() 関数は送信完「割り込み処理 , そして e 「 i ( ) 関数は受信工ラー割り込み処理のダミー関 数て、ある。 これのための割り込みべクタテープルが List 4 て、ある。 そして , List 3 と List 4 のプログラムを一 x6 て、それぞれコンパイルした後 , リンクす るためのリンカへのコマンドラインが Fig. 2 て、ある。 これて、 , 0 番地からべクタテープル , その 直後 ( 0X00C0 番地 ) からテキスト領域 , そし て , 0x 80 番地から BSS 領域 , その直後に 0X100 バイトのスタック領域という指定て、あ H8 / 532 のシングルチップモードて、は , ROM が 0 から 0x7fff 番地 , RAM が 0x 価 80 番地から / * RDRF * / 特集 ROM 化プログラミング考察 61