オフジェクト工房 = ' を付加して埋め合わせます ( 逆変換の手 順は明らかですね ) 。 Base64 は 3 バイトの入力から 4 バイトの ASCII 文字を出力するため , 変換後の大き さは 33 % 増となります。 実装方法 能書きはこのくらいにして実装を試みま しよう (base64conv. cpp , 付録 CD-ROM に 収録 ) 。文字コード変換と同様に Base64 へ の変換 / 逆変換クラス ToBase64/FromBase 64 を Hose く char , char> から導出します。メ ールに載せるメッセージは 1 行の長さに制 限があるため , ToBase64 のコンストラク タで行の長さを指定できるようにしまし た。変換結果がここで指定した長さに達す るたびに復帰 / 改行 ( CR / LF ) を挿入します。 お試しルーチンではテキストファイル中 の JIS 文字列を Shift JIS , さらに Base64 ファ イルへ変換し , Base64 逆変換によって得 られた Shi れ JIS 文字列を標準出力にプリン トします。 おわりに また新たな応用例が見つかるんじゃないか スをスレッド間通信を使って接続すれば , すけど , ここで紹介したいろんな変換クラ 遊んでみました。まだアイデア段階なんで (' トコロテン方式 ' とでも言うのかな ? ) で た , Decorator パターンによる処理の連鎖 ・・・てなわけで , 庭の水撒きに端を発し ・・・なんてことを考えています。 List 2 より入力と出力の型が異なる場合でもいいように変更 template く class inT, class outT =inT> c lass Hose : pub lic N02 刎 e く inT> { public: Hose() : next-(O) { } VOid joint(Nozzle く outT>* n) { next— = n; virtual void flush( ) { f lushNext( protected : void injectNext(const outT* first, const outT* if ( れ t ー ) { next-->inject(first, last); VOid injectNext(const outT& val) { if ( next- ) { next——>inject(val) ー Nozzle«outT>* next private : next—->f lush ( ( next— ) { void f lushNext( ) { v 土 u 引 void reset( ) ( str— . erase( } virtual void f lush ( ) 0 public: std : : string st て一 class StringNozzIe : public Nozzle«char> { * 変換結果を std: :string に格納するノズル 変換結果を文字列に出力したい場合 ね s セ ) 水量が半ぶんになるホースと絶対値ホース class HaIf : public Hose<int,double> ( public: virtua ー VOid ( const int* first , const 土れ t * ー ast ) { while ( first last ) { injectNext(*first / 2.0 十十 first; . Nozzle く T>, Hose く inT,outT>,. Print<T> は省略 引 ass Abs : pub lic Hose<int> ( public: virtual void inject(const 土 n セ * first, const int* last) ( while ( first ! = ね s し ) { Print く double> n02 刎 int data[l = { 0 , 加し main ( ) { 十十 f 土て st ー injectNext(x 〉 0 ? x ・ -x); int x = *first; ー 3 , 4 Abs 日引 f hosel; hose2; virtual void inject ( const char* first' const char* last ) { Sにてー . append(first, ね s し std: :string s セて ( ) const { return s セて } hosel. j 0 土 n し ( &hose2 嶹 hose2. joint(&nozzle); 6 びてククのオブジェクト工房 return 0 ー hosel. flush( hosel. inject(data, data 十 5リ 89
ストで記録するときの先頭を示すために使 出したときにどう対応するかである。よく 記録したいので , 配列ではなく , 連結リス 考えれば , 本番前の試験フェーズなのだか ト (Fig. 1 ) で記録しておく。双方向ではな 用する。 ら , 必要以上にエラー回復をする意味がな く単方向連結リストにしているのは , 途中 メモリ確保関数 い ( ただしェラー検出は必須である。絶対 挿入や途中削除を避けるように実装するた め , 双方向にするまでもないからである (L に勘違いしてはいけない ) ので , 検出した 場合は , あっさりプログラムを停止させて ist 4 ) 。 malloc と calloc は似通った関数なので , わざわざ似通ったチェック関数を二重に書 List 5 の McFi Ⅱ Pat は領域外破壊チェック いる で必要である。わざわざ説明するまでもな 連結リストを単方向にするか双方向にす くめんどうさを避けるため , 共通のチェッ ク関数 mc ー a Ⅱ oc というのを用意する (List るかは悩みどころの 1 つになりうる。単方 いが , このパターンであらかじめ領域の外 部を埋めておき , 後に , この値が書き換わ 6 ) 。 向リストにするのはメモリの節約になる ( 微 っているなら領域外破壊が起きたと判断す 々たるものではあるが ) こととプログラム このへんは , さほど難解なことをしてい が簡単になるという理由が大きいが , すで ないので , わかりやすいであろう。いつも る。 gTagT 叩は確保した情報を単方向連結リ 悩むのは , チェックツール内でエラーを検 に双方向リストやその働きの代用になるコ 定数とグローバル変数の記述 型の記述 / * このソース内で利用する定数 * / / * このソース内で利用する型 * / メモリプロックに埋めるバターン * / / * 確認用構造体 * / static const char McFillPat = 0xED; / * struct MC—Tag { ヘッダ部分のチェック領域サイズ * / / * 次の構造体へのポインタ * / static const size—t McHeadSize = 0X20 * struct MC—Tag *mNexti StatiC const size—t McTailSize = 0X20 * テイラ部分のチェック領域サイズ * / / * ポインタ * / char *mPointer ー / * 確保サイズ * / size—t mAllocSize; static const MC—b00 ー MC—true = 1 ー / * 利用した関数名 * / static const MC—bool MC—faIse = char *mFuncName; / * 利用場所のファイル名 * / char *mFileName; / * 利用場所の行番号 * / / * このソース内で利用するグローバル変数 * / int mLineNo ー static MC-Tag *gTagTop ま L / * 確認用構造体の先頭 * / 0 typedef struct MC—Tag MC—Tag; typedef int MC—b001; List 6 用 一三 の 0 memset(theAllocMem 十 McHeadSize,0 ,inSize); memset(theAllocMem 十 McHeadSize 十 inSize,McFillPat,McTaiISize); )else{ memset(theAllocMem,McFillPat,McHeadSize 十 inSize 十 McTailSize); / * 確認用構造体に登録する * / theAllocMem 十 = McHeadSize; mc—tag—add(theAIIocMem,inSize,inFunc,inFiIe,inLine); return theAllocMem; void *ma 日 oc-checker—ma 日 oc ( const char * inFiI e は n セ inLine , s 土 ze ーセ insize ) return mc-alloc( "malloc" ,inFile,inLine,MC—false,inSize); / * ca Ⅱ oc の確認用 * / VOid *malloc—checker—calloc(const char * 土れ F e は n セ inLine, size—t inSize1,size—t inSize2) て e 加て n mc-alloc( "caIIoc",inFiIe,inLine,MC-true,inSize1 * 土 nS 土 2e2 static void *mc—alloc(const char *inFunc'const char *inFileÅnt inLine' MC-b001 inzero , si ze-t insize ) メッセージを出して緊急停止する static void mc—abort—v ( const char * inFormat, char theTempChar[256] 引 va—list theVA; va—start( theVA , inFormat vsprintf(theTempChar,inFormat,theVA); va—end(theVA); mc-abort(theTempChar); static void mc—abort ( const char *inMessage ) fprintf(stderr,"%s%n" ,inMessage); abort ( char *theAIIocMem; / * 0 バイト確保チェック * / mc-amrt—v()* 宅 s 工ラー : , 宅 d で 0 バイトのメモリ確保” ,inFunc,inFile,inLine); / * ヘッダとテイラを含めて確保する * / theAllocMem = malloc(inSize 十 McHeadSize 十 McTailSize); if(theAIIocMem = = NULL) { mc—abort—v()* 宅 s 工ラー : 宅 s d で d バイトのメモリ確保に失敗″ inFunc , inFil e , inLine , inSize / * 確保メモリにパターンを埋める * / if ( inzero ) ( memset ( theA 日 OCMem, McFi 日 pat, McHeadSize if(inSize = = 0 ) { 凵 0 C MAGAZINE 2 1 7
ucnv—open ("shift-jis , & e てて によって JIS と Shift JIS の UConverter を生成 し , これらを引数として ToUnicode , Fro u2s. jO 土 n セ (&nozzle); j2u. jOint (&u2s); print く char> nozz ー e ー は適当なノズルをつなぎます。 次にこの 2 本のホースをつなぎ , 末端に FromUnicode u2s (s); ToUni code j2u(j); mUnicode を生成します。 サンプルでは変換結果をストリームに j2u. inject ( s セて , str 十 strlen(str) 潺 const char * str = JIS 文字歹 が吹き出します。 ータを流し込めば , ノズルから変換結果 あとは , ホースの根元から変換したいデ 吐き出していますが , 文字列 std::string に 格納したければ , List 5 のような StringNoz zle を用意し , StringNozzle noz 刎 j2u. joint (&u2s); u2s. jOint (&nozzle); const char * s セて = JIS 文字歹 j2u. inject (str, s セて十 s して厄 n ( s セ r ) std: :string result = nozz ー e. s て ( とかやればいいでしよう。 nozzle. reset(); とができます。たとえば JIS で書かれたテ 与える関数オプジェクトとして用いるこ ば , 標準 C + + ライプラリのアルゴリズムに 追加しました。叩 erator ( ) を提供しておけ NozzIe<T> に bool 叩 erator ( ) (const (&) を 1 バイトずつの変換を簡単に行えるよう , Fig. 2 UN ℃ ODE 変換ルーチン「 ucnv_toUnicode/ucnv_f 「 omUnicode 」 〇 ucnv toUnicode : バイト列を UNICODE に変換 void ucnv—toUnicode ( UConverter * wchar—t * * const wchar—t * const char * * const char * 土 n セ 32 ーヒ * UBOO ー UErrorCode * converter ー targe し , targe し L 土 m 土 t , source , sourceLimit , offsets, f lush, err ) 〇 ucnv fromUnicode : UNICODE をバイト列に変換 VOid ucnv_fromUnicode ( UConverter * char * * const char * const wchar—t * * const wchar—t * int32—t * UBOO ー UErrorCode * 〇処理の流れ converter ー target , targetLimit , source , sourceLimit ー offsets, f lush, e てて ) キストファイル "jis. txt' を Shift JIS に変換す るならアルゴリズム for ー each を使って , ifstream file("jis. txt"); for—each ( istreambuf—iteartor く char> (file), istreambuf—iteartor く char> ( ) , j2u); j2u. flush(); のように実装できます。前述の newstrcon v. cpp では JIS で書かれたテキストファイル を ShiftJIS テキストファイルに。一気に ' 変 換するサンプルを納めています。 どうです , けっこうおもしろいでしよ ? せつかくだからもう少し遊びましよう。僕 が ICU に目をつけたのは , 異なる OS / プラ ットホーム間でのメッセージのやりとりを 行うためです。とくにメールを介したメッ セージの交換はいわゆる ASC Ⅱ文字に限ら れるため , ASCII 文字で構成されている JIS ( iso -2022- jp ) との相互変換を必要とするの です。文字列についてはここで紹介してい る ICU などを利用すればいい , しかし画像 や音声あるいは ZIP ファイルなどのバイナ リをメールに載せるのに ICU は使えませ ん。バイナリデータを ASCII 文字列に変換 するアルゴリズムとして現在もっとも広く 使われているのが Base64 です。 Base64 の概要 Base64 はバイナリデータ列を英字 / 数字 などの 64 種の ASCII 文字の列に変換します。 そのからくりはそんなに難しくはありませ ん。まず , 入力バイト列を 3 バイトずつに 切り分けます。そしてその 3 バイトすなわ ち 3X8 = 24 ビットを 6 ビットずつの 4 個に分 割し , それぞれの 6 ビット値を対応する AS C Ⅱ文字に変換します。入力列の長さが 3 の 倍数でないときは出力の末尾に 1 個か 2 個の Base64 88 1. [*source, sourceLimit) を変換し , [*target. targetLimit) に格納する C MAGAZINE 2001 7 4. * e 「「には処理の途中で発生したエラーが納められる ( U ー ZE 日 O ー ERRO 日なら正常 ) 3. 利 ush は引き続いて変換を行うときには se になる わない 2. 変換後 , * sou ℃ e/ * ta 「 get は , それぞれ変換後の入力 / 出力の末端を指す。 offset は通常 0 でかま
オプジェクト工房 使い方は簡単です。たとえば JIS から Shif くれ , flush=FALSE のとき内部バッフアに code , それぞれ Hose く char , wchar_t>/H ose く wchar t, char> から導出しています tJIS への変換を行うには , 蓄積されたぶんも含めて変換するので , んな場合でも正しく変換できます。 どちらも ICU のコード変換ルーチンと Hose UErrorCode = U—ZERO—ERROR; のインタフェイスの擦り合わせを行う小さ UConverter * j 実装方法 なラッノヾー (wrapper) です (newstrconv. cpp , ucnv—open( ”土S0ー2022ーjP , & e てて 付録 CD-ROM に収録 ) 。 実装したクラスは ToUnicode と FromUni UConverter * s ホースとなるクラス Fig. 1 DECORATO 日バターン ( 「オブジェクト指向における再利用のた めのデザインパターン ( 改訂版 ) 」より引用 ) 〇目的 オブジェクトに責任を動的に追加する。 Deco 「 ato 「バターンは , サ ブクラス化よりも柔軟な機能拡張方法を提供する ーく iostream> using namespace std ー temp ー ate く c ー ass T> class Nozzle { public: v 土てセ u 引 ~ N02 刎 e ( ) { } . * い as セ -1 ) を注入 (inject) する / / Nozzle に *first, last) virtual VOid inject(const T* first, const T* VOid inject(const T& val) { inject(&val, & v 引十 1 潺 virtual void f lush ( ) { } 〇別名 Wrappe 「 〇動機 あるクラス全体に対してではなく , 個々のオブジェクトに責任を追 加したくなることがある。たとえば , グラフィックユーザインタフェ イスツールキットでは , 境界線やスクロール機能などを任意のユーザ インタフェイスコンポーネント ( 以下 , component と呼ぶ) に追加で きるようになっているべきである。 責任を追加する方法のひとつに継承の利用がある。たとえば , 別の クラスから境界線を継承すれば , そのサブクラスのすべてのインスタ ンスのまわりに境界線を付けることができる。しかし , この方法では 境界線を付けるかどうかの選択が静的になされるため , 柔軟性に欠け る。すなわちこの方法では , いつ , どうやって component に境界線を 追加するかをクライアントは制御できない。 継承の利用よりも柔軟なアプローチに , 境界線を追加する機能を持 つ別のオブジェクトで component を囲んでしまう方法がある。囲って いるオブジェクトのことを deco 「 ato 「と呼ぶ。 deco 「 ato 「のインタフェ イスは , それが装飾している component のインタフェイスと一致して いる。そのため , component のクライアントにとっては deco 「 ato 「の 存在は透明になる。 deco 「 ato 「は component に対して要求を転送し , その転送の前か後にさらにアクション ( たとえば , 境界線を引く処理な ど ) を実行することがある。 deco 「 ato 「の存在を透明にしているため , deco 「 ato 「を再帰的にネストでき , したがって , 追加できる責任の数に 制限はなくなる。 例として , ウインドウにテキストを表示する Tex Ⅳ iew オブジェクト を考えよう。常にスクロールバーが必要とは限らないので , TextView クラスにはデフォルトではスクロールバーは付かない。スクロールバ ーが必要になったときには , Sc 「 0 Ⅱ Deco 「 ato 「オブジェクトを用いてス クロールバーを追加することができる。さらに Tex Ⅳ iew オブジェクト のまわりに太くて黒い境界線を追加したい場合にも , 同様に Bo 「 de 「 Deco 「 ato 「オプジェクトを利用して境界線を追加することができる。 このように , deco 「 ator を TextView オブジェクトと組み合わせるだけ でほしい結果を得ることができる。 temp ー ate<C ー ass T> class Hose : public Nozzle<T> public: Hose( ) : next-(0) ( } / / ホースの先にノズルを繋ぐ void j0int(NozzIe く T>* れ ) { next— = れ一 last ) virtual VOid inject(const T* first' const T* injectNext(first, ね s し virtual void flush( ) { f lushNext ( リ protected : last ) void inj ectNext ( const T* first , const T* if ( next- ) { next——>inject ( f irst , void f lushNext( ) { if ( next- ) { next——>f lush ( private : N02 刎 e く T > * next— template く class T> class print : public Nozzle く T> { public: last ) { virtual void inject(const T* first' const T* while ( first ! = ね s し ) { cout くく *f 土て s セくく 十十 f 土て st ー virtual void flush( ) { cout くく endl; int main( ) ( 4 土 n し data [ ] = { 0 , 1 , 2 , 3 , print く int> nozzle; Hose く int> hosel ー Hose<int> ね ose2 ー hosel. j0int(&hose2); hose2. joint(&nozzle); hosel. inject(data, data 十 5嶹 hosel. f lush( て eturn 0 ー 8 / どびてククのオブジェクト工房
からメロンソーダが噴き出すでしよう。ソ フトウェアの世界ならそんな、魔法のホー ス ' が簡単に作れそうです。おもしろいじ ゃん , やってみようよ。 この 2 つのホースをつなぐと対値を半分 ることがわかると思います。 にするホース ' として機能します。 魔法のホースは Deco 「 atO 「だ 魔法のホースでコード変換 さてと , それではこの魔法のホースの応 いかがでしよう ? 通常の関数呼び出し 用を考えることにしましよう。 Decorator とは逆方向に処理が進んでいく魔法のホー パターンのバリエーションである魔法のホ 魔法のホースは変テコなホース , 注入し ス , さまざまな機能を持ったホースをつな ースは , 要は " データ列を変換するオプジ たものとは別のものが飛び出します。そこ いでいくことでお望みの処理を、合成 ' する ェクト " ってことですよね。そしてこの変 で List2 の Hose くやをちょっと書き換えて , ことができます。魅力的なカラクリだと思 換オプジェクトを数珠つなぎにすることに 入力と出力の型が異なることを許してあげ いませんか ? 実はこのカラクリ , 僕が考 よって , 変換の連鎖というか合成ができる ます (List 3 ) 。新たなクラス Hose<inT, out え出したものじゃないんです。 SoftwareD というわけです。 は inject の引数すなわち入力の型がⅲ T で , evelopment Productivity Award を受賞した 文字コードの変換に利用する 後続する ( 先つばにつなぐ ) ノズルの入力の 名著『オプジェクト指向における再利用の 型が outT となります。 Hose く inT, out+ の ためのデザインパターン ( 改訂版 ) 』 ( EricG 前回の " 文字コードの変換 " の中で IBM I 派生クラスでは仮想関数 inject を再定義し , amma 著 , 吉田和樹 , 本位田真一監修 , ソ CU を紹介しました。 ICU は任意の文字コー 適当な変換 , たとえば粉末ジュースを溶か フトバンクパプリッシング ( 1999 ) , ISBN4 ドと UNICODE との相互変換を行います。 し込むような処理の後 , injectNext() を呼 -7973-1112-6 ) に "Decorator パターン " の名で ICU を使って JIS から Shift JIS に変換するに ぶことにします。たとえば , List 4 では新 書かれています。この書籍より Decorator は , JIS を UNICODE に変換し , 得られた U しい Hose<inT, outT> から派生した、水量 パターンについて , 引用させていただきま NICODE を ShiftJIS に変換するという 2 つの が半分になるホース ' , そして・絶対値ホー す (Fig. 1 ) 。魔法のホースがまさしくこの ステップを通すことになります・・・・・・。魔法 ス ( なんだそりゃ ? ) ' を作ってみました のホースにピッタリの例じゃないですか Decorator ノヾターンのバリエーションであ つまり JIS から UNICODE に変換するホース ノズルとなるクラス と UNICODE から Shift JIS に変換するホー スを用意し , その 2 本のホースを扣 int すれ ば JIS から Shi れ JIS に変換するホースが合成 できるってことです。やってみましよう。 今回用いる UNICODE 変換ルーチン ucnv toUnicode/ucnv fromUnicode のプロトタ イプを Fig. 2 に示します。前回紹介した uc nv toUChars/ucnv—fromUChars に比べて パラメータ数も多いし , なんだかややこし いインタフェイスですが , ドキュメントに よるとこいつがいちばん速そうだし , 入力 バイト列を任意の長さに切り分けて変換で きます。たとえば Shi れ JIS で表現された文 字列は 1 文字が 1 バイトもしくは 2 バイトで 構成されています。だから文字列をいいか げんに切り分けると末端が 2 バイト文字の 途中で切れてしまうことが起こりえます。 ucnv toUnicode/ucnv—fromUnicode は変換 途中の文字を内部バッフアに納めておいて ・魔法のホーズを作る #include く iostream> using namespace std ー template く class T> class Nozzle { public: / / Nozzle に *first, . *(last-l) を注入 (inject) する。 virtual VOid inject(const T* f 土て s し , last ) = 舮 const T* VOid inject(const T& val ) { inject(&val, & v 引十 1 v 土てこ u 引 void flush( ) { } temp ー ate く c ー ass T> class Print : public Nozzle<T> { public: virtual VOid inject(const T* f 土て s し , ね s 日 const T* while ( first ! = last ) ( cout くく * first くく 十十 f 土て s v 近セ u 引 void flush( ) { cout くく endl; 土 n セ main( ) { int datatl = { 0 , 1 , 2 を 3 , 4 Print く int> nozz ー e ー n02幻e. inject(data, da し a 十 5 nozzle. flush(); return 0 ー 86 C MAGAZINE 2001 7
真紀俊男の ローテク講座 解放忘れや間違った解放を検出するには , メモリ確保時に確保した情報を記録するこ とである。メモリがある限りはできるだけ る。「 #undef malloc 」としている理由は , このツールのソース内で記述する ma Ⅱ oc は 「本当の ma Ⅱ oc 」を呼びたいからである ( マ #include nmalloc—checker. h" / * 必ず最後に include する * / れを忘れると , 再帰呼び出しになってしま クロに置き換えられたものではなく ) 。 い , 無限ループに陥る。 テスト用プログラム void fl(size—t inA) static void TestMain ( void static int InputCase(void); #include く stdlib. h> #include <stdio. h> void f3(int inB) free (theMem ); theMem = malloc(2); if(inB){ char *theMem; void f2(int inB) char *theMem = malloc(inA); free(theMem); free(theMem); theMem = malloc(2); char *theMem ま NULL; char *theMem = malloc(5); int thel; void f5(void) free(theMem); *(theMem 十土 ) = の char *theMem = malloc(inA); void f4 ( size—t inA , int inB ) for(thel = thel く thel 十十 ){ malloc—checker. h ( 第 1 段階 ) / * ma Ⅱ oc の確認用 * / #ifndef NDEBUG void *malloc—checker—realloc(const char *,int,void *,size—t); / * て ea Ⅱ 00 の確認用 * / #define calloc(X,Y) malloc-checker-calloc(—FILE—,—LINE—,X,Y) VOid *ma Ⅱ oc—checker—ca 日 OC ( const char * , int , size—t, size—t / * ca Ⅱ oc の確認用 * / #define malloc(x) malloc-checker-maIIoc(—FILE—,—LINE—,X) VOid *malloc—checker—malloc(const char *,int,size—t); #endif / * #ifndef NDEBUG * / #define free(x) malloc-checker-free(—FILE—,—LINE— void malloc—checker—free(const char *,int,void * / * f て ee の確認用 * / #define realIoc(X,Y) malloc—checker—realloc(—FILE—,—LINE—,X,Y) List 1 *theMem 十十 = 0 ー free(theMem); f4 ( 4 , 5 / * 確保した領域を越えて書き換える ( テイラ方向 ) * / case 6 : break; f4 ( 4 广 1 / * 確保した領域を越えて書き換える ( ヘッダ方向 ) * ノ case 5 : break; f3 ( 1 / * f て ee を 2 回呼ぶ * ノ case 4 : break; f2 ( 0 / * 不正なポインタで f て ee をする * / case 3 : break; fl ( 0 / * 0byte で malloc する * / case 2 : break; fl ( 1 / * メモリの解放忘れ * / case 1 : switch( lnputcase( ) ) { static void TestMain(void) return atoi(theDat); fgets(theDat,sizeof(theDat)-1,stdin); ff lush ( stdout fprintf ( stdout, ”選択 = ″ char theDat[16]; static 土 n セ I nputCase ( VOid ) break; f5 ( 潺 / * 不正なポインタで f て ee をする * / case 7 : break; #include "malloc—checker. h" #include く stdarg. h> #include <string. h> #include <stdlib. h> #include <stdio. h> malloc_checker. c 真紀俊男のローテク講座 #undef free #undef rea Ⅱ oc #undef calloc #undef malloc / * malloc-checker. h 内のマクロを解除 * / #ifndef NDEBUG 1 39
真紀俊男の ローテク講座 ンテナを扱うよいライプラリがあるなら , ないので , わかりやすいであろう ( List8 ) 。 Fig. 2 テストプログラムの実行結果 こだわらずにそちらを利用したほうがいい 実際に , 先ほどのテストプログラムを走ら ー 2 、を 63.0 を だろう。 せると (Mac OS 9 上で , Code Warrior 6 使 ・ onh 齲 rm ・ 0 響を“をレ蜊 v , ) を こでは汎用的なチェックツール 用。ただしコンソールしか使わないプログ として , どこでも動作させようと欲ばった ラムなので , ほとんどの OS 上でコンパイ ↑ 1 ( メモリの解放忘れ ) , 5 と 6 ( 領域外破壊 ) はま ことを考え , また後の回で説明するかもし ルして確認ができる ) , 2 ( 0 バイトの確保 ) , だ検出できない れないが , 解放した領域の記録を破棄しな 3 と 7 ( 不正ポインタでの解放 ) , 4 ( 二重解放 ) SIOUX ・ 1 ⅳ 0 haS 3b0 パ・ d い場合を考えて , あえて List7 のような実 は検出できるが , 1 ( メモリの解放忘れ ) , を・を ( d ・レ v ・朝れ ) を ・ 0 、工ラ - : T ”ね .0 刈で 0 バイトのメモリを保 装をとった。記録の削除をする場合 , 単方 5 と 6 ( 領域外破壊 ) はまだ検出できない 向リストはめんどうくさいので , (Fig. 2 ) 。 こでは ↑ 2 ( 0 バイトの確保 ) を検出したところ mPointer メンバに NULL を書くという手抜 S いⅨ 5 ね : 新 0 n has 地を ed. きをやっている。 ・を”をを ( レ蜊” rs の n ) 第 響作“工ラ - : T ・ M 新 .0 ′ 53 で不正なメモリ 0X0 物・ 0400 ) モリ解放関数 ( 第 1 段階 ) 顰 次回はやり残した部分 ( メモリリーク報 ↑ 3 と 7 ( 不正ポインタでの解放 ) , 4 ( ニ重解放 ) は 検出できる 領域外破壊チェックなど ) の実装を行 このへんも , さほど難解なことをしてい う。お楽しみに こ・ 0 ー 0 次回は 0 malloc_checker_free mc—tag—add 確認用構造体の作成と登録 static void mc—tag-add(char *inptr,size-t insize,const char *inFunc, const char *inFil e ,int inLine ) MC—Tag *theTagPtr 咢 gTagTop; MC—bool theFound = MC—false; / * 空き構造体を探す * / while(theTagPtr ! = NULL & & !theFound) { if ( theTagPtr->mPointer = = NULL ) { theFound = MC—true; }else{ theTagPtr theTagPtr->mNext; / * f て ee の確認用 * / void ma Ⅱ oc—checker—free ( const char *inFi 厄 ,int inLine , void * inptr ) char *theMem; ー MC—Tag *theTagPtr; / * 該当メモリを探す * / theTagPtr = mc-tag-find(inptr); if(theTagPtr = = NULL) { mc-abort-v()* free 工ラー : 宅 s , 記で不正なメモリ解放 ( ) ” inFil e , inLine , inptr なメモリを解放する * / theMem = theTagPtr—>mPointer ー McHeadSize; free(theMem); / * 確認用構造体を無効化する * / theTagPtr->mPointer = NULL; / * 空きがないなら新規に作成し構造体チェーンのトップにする * / ( !theFound) { theTagPtr = ca 00 ( 1 ,sizeof(MC—Tag) if(theTagPtr = = NULL) ( mc-abort()* 内部工ラー : mc ユ ag ー add でメモリ確保に失敗” theTagPtr->mNext = gTagTop; gTagTop = theTagPtr; / * 構造体のメンバを書き換える * / / * ポインタ * / theTagPtr—>mPointer = inPtr ー / * 確保サイズ * / theTagPtr—>mA Ⅱ OCSi ze = inSi ze ー / * 利用した関数名 * / free(theTagPtr—>mFuncName); theTagPtr—>mFuncName = mc—strdup ( inFunc ) 引 / * 利用場所のファイル名 * / free(theTagPtr->mFileName); theTagPtr—ymFileName = mc—strdup(inFi le); / * 利用場所の行番号 * / theTagPtr—>mLineNo = inLine ー 土 nptr に一致する確認用構造体の検索 見つからないなら阯を返す static MC—Tag *mc—tag—find(void *inptr) MC—Tag *theTagPtr; for(theTagPtr = gTagTop; theTagPtr ! = NULL; theTagPtr = theTagPtr->mNext){ if(theTagPtr->mPointer = = (char * ) 土 nP セて ) { return theTagPtr ー return NULL ・ 文字列データの動的確保 static char *mc—strdup ( const char * instring ) char *theAns = maIIoc(strIen(inString) 十 1 if(theAns = = ) { mc-abort()* 内部工ラー : mc ー strd 叩でメモリ確保に失敗” strcpy ( theAns ,inString return theAns ー 141 真紀俊男のローテク講座
記移植テ 2 ニックを考をる ものの , それぞれの有効範囲はほば同じと 思ってよいだろう。 ・ローカル変数 ローカル変数は , 変数を宣言した手続き や関数内で有効となり , VB でのローカル 変数と同じ扱いとなる。宣言は , 予約語 「 var 」を利用し , 手続きや関数内で行う。 ・プライベート変数 プライベート変数は , ユニット内であれ ばどのイベントハンドラからも参照が可能 だ。しかし , ほかのユニットからの参照は できない。同一フォーム内であれば参照が 可能となる、のプライベート変数と同じだ。 VB では , フォームの宣言部 ( General 部 ) で 言するが , Delphi ではユニットの imple ・バブリック変数 mentation 部で宣言する クラスを利用しないバブリック変数の宣 はならない。 節で参照先ユニットを宣言しておかなくて ただし , 参照するユニットの側では , uses かのユニットからも参照可能となる ( Iist8 ) 。 ユニットの lnte 可 ace 部で宣言すると , ほ ・クラスの概念を利用しない方法 なる。 部で宣言するグローバル変数と同じ扱いと はあるが , VB の Public キーワードや Global あり , VB に比べて若干わかりづらい部分 パプリック変数には 2 種類の宣言方法が こからでも参照が可能だ。 パプリック変数は , プロジェクト内のど ・クラスの概念を利用する方法 もう 1 つの方法は , 予約語「 public 」を使 いフォームクラスのメンバとして宣言する (List 9 ) 。この方法を利用すると , initializa ti 。 n を利用した初期化ができなくなる。フ オームクラスの一部だから , フォームその ものが生成されるときに , その変数も生成 されるためである。 適用範囲を間違えると誤動作したり , コ ンパイルエラーに悩まされることになる。 VB のオリジナルのコードをよく読み , 変 数の適用範囲を把握してコードを書き直す ようにしたい。 記号定数 記号定数は予約語 const を利用して宣言 する。 VB の Const ステートメントと同じ扱 いだ const tax = name ' 山田 式と演算子 演算子の扱いは , 代入記号を除いて DeI phi も VB もほとんど変わらない。 代入演算子と比寅算子 ( 「 = 」の使い方 VB では , 「 = 」を代入演算子にも比較演算 子にも同じように使用できるが , DeIphi で は代入演算子にはコロンを付けて「 : = 」とし , 比較演算子では「 = 」のみで使用する。 VB でも , 本来は代入はレ t ステートメン トで行うが , 多くの場合 = 演算子を利用す る。比較と代入が同じ記号というのは間違 いを誘発しやすいので , こは VB のソー スをよく読み , 正しい置き換えが必要だ。 基本的に VB の = 記号は , 制御構造の条件 文では比較演算子 , そうでない箇所では代 入命令となる。 ・代入の場合 ・比較の場合 文の終わり DeIphi では , 文の終わりはセミコロン「 ; 」 で区切る。、では文末に何も付けないので , 文の終わりを意識することは少ないだろう。 ついうつかり忘れるとエラーとなる。 行末の「 ; 」忘れは , VB プログラマがよく ; がない場合 , ( 仮 犯すケアレスミスだ。 に改行が入っていても ) 行末は次行に続く ものとみなされるので要注意だ。 算術演算子 , 論理演算子を比較演算 これらは VB とまったく同じで , ほとん どそのまま移植できる。ただし , 演算後の データ型に注意だ。たとえば ShortInt 型同 士の乗算で , 答えが LongInt 型になってし まうような場合や , 先述したエディットボ ックスの text プロバテイへの代入処理だ 今回は , VB から Delphi への移植におい てもっとも基本的な , プログラム構造の違 いと変数の扱いなどを説明した。次回は , 制御構造とデータベースの扱いを紹介する。 = 1 の A If A = 10 then ~ uni し interface uses type private public end; var { PubIic 亘 implementation end. クラスを利用したバブリック変数の宣言 unit interface uses type TForml = c ー as s ( TForm ) ◆ー private pub け c { PubIic 旦 end; var imp lementation end . ここ「 ( : 宣・三・ 156 C MAGAZINE 2001 7
るのかを管理するのはプログラマの責任で す。もっといえば , ポインタ値が指し示し ているのは配列ではなく , 単純な変数かも しれません。 C の言語仕様上は , ポイント 先が配列かどうかは区別されません。常に 配列としてアクセスできる可能性があるの が特徴であり , それが正しいかどうかにつ いては , 言語としては何も検査しません。 「 int * p ; 」と宣言されたポインタ変数が指 し示している先にどんなものがありえるの か , 可能性をすべてあげれば , Fig. 13 のよ うに 7 種類になります。この中で , ケース 7 ( 工ラー ) というのは , たとえば p が初期化 されていないとか , ポイント先にあった配 列オプジェクトが何らかの理由で失われた ( 配列オプジェクトが破棄された ) などで , 正しいオプジェクトを指していない状態で す。それ以外は , 正しいプログラムの進行 上ありえる状態です。たとえば「 p [ 1 ] 」とい うアクセスが正しいのは , ケース 3 , 4 に限 られ , 逆に「 p [ -1 ] 」というアクセスが正しい のはケース 4 ~ 6 に限られます。けれども , 実際にそうしたアクセスが , 正しいケース において行われているのかどうかについて , 言語側ではチェックしません。そうしたこ とはすべてプログラマが気を配る必要があ るのが C という言語の特徴です。 C のポインタと配列 C の初心者にありがちな間違いに , ポイ ンタと配列を混同してしまうことがありま す。実際には両者はまったく違うものです が , C の言語仕様において両者は非常に密 接なかかわりを持っていて , ある意味で確 信犯的に両者を似せた形式で表現できるよ うに設計されているのです。本稿はポイン 0 : タの解説を目的にしていますが , 実際の C プログラミングにおいてはポインタと配列 の区別をきちんと把握することが重要で , 3 : 24 , これができていないとまともなプログラム は書けません。そこでまず , 少していねい に両者の相違を説明します。最初に , 両者 の違いをまとめておきましよう。 28 C MAGAZINE 2001 7 ・配列は複数の変数が連続した領域に割 り付けられたものです。また , 配列名 はポインタ定数であり , 配列名には代 入できません。配列名に割り当てられ た値を更新することもできません ・ポインタ変数は単独の変数です。した がって ( const 修飾されていない限り ) 代 入が可能ですし , 値の更新も可能です ・配列は宣言時に定数式を用いて要素数 を指定しなければなりません。そして その配列オブジェクトが生成された時 点で指定した数のオブジェクトが連続 して確保されます。配列オブジェクト が生成されるタイミングは , その配列 の記憶クラスによって変わります。記 憶クラスが au の配列であれば , している関数に制御が移った時点で確 保されます。それ以外の記憶クラスで あれば , コンパイル時 ( あるいはリンク 時 ) に確保されます ・ポインタ変数は「指し示す先のオブジ ェクトの型情報」を持つにすぎません。 ポインタ変数を宣言しただけでは , そ れが指し示す先のオブジェクトは確保 されません。ポイント先のオブジェク トの確保はプログラマの責任になりま す。また , ポイント先に存在するのが 配列全体へのポインタ 配列「 a 」があった場合 , 式の中で「 a 」と書 くとそれは「 & a [ 0 ] 」に等しくなることは繰 り返し述べました。では , 「 & a 」と書いた ら , これは「 & a [ 0 ] 」と等しいのでしようか。 答えは , 「アドレス値としては同じだが , 型 が違う」です。 ANSI/ISO では , 配列オプジ ェクトそれ自身に単項の「 & 」演算子を適用 した結果は「その配列全体へのポインタに なる」と定めているからです。たとえば , 「 i nta [ 20 ] ; 」と宣言されている場合 , 「 & a 」と いうポインタ値は「ⅲ t の 20 要素の配列全体 へのポインタ」という型を持ちます。これ に対して「 a 」すなわち「 & a [ 0 ] 」は , 「ⅲ t への ポインタ」にすぎません。 コラム 4 単一のオブジェクトなのか , あるいは オブジェクトが複数個並んだ配列なの かの区別や , 仮に配列だとしてその要 素数がいくつであるかについては , ポ インタ変数自身には何の情報も備わっ ていないため , これもプログラマの責 任で管理しなければなりません ポインタ生成ルール C におけるポインタと配列の混同の第一 歩は , ポインタ生成ルールです。一般に C の式の中に配列オプジェクトが出現する と , それは自動的に先頭要素へのポインタ 値 ( 定数 ) に変換されます。たとえば a が ( 何 らかの型の ) 配列であるとすると , 単に式 の中で「 a 」と書くのと「 &a [ 0 ] 」と書くのは 同じ意味になります ( ただしこのルールに は 2 点例外があります ) 。実際には , ポイン タ生成ルールはより拡張されていて , 式の 中で「 a + ( n ) 」 ( n は整数値 ) と書くのと , 「 & a [n] 」と書くのとは同じという形になって います。「 a 」が「 &a [ 0 ] 」に等しいというの は , たまたま「 n 」がゼロだったケースとい う解釈です。またこのルールを成立させる ためには , ポインタとオフセットの演算「 a + ( n ) 」に対しては , 先に述べたスケーリン グが必要になるわけです。 このポインタ生成ルールは , 文字列定数 についても同様に成り立ちます。 C の言語 仕様では文字列定数は「 char の配列の定数」 と解釈されているため , 式の中に文字列定 数が登場すると , それは「先頭の文字への ポインタ値」へと変換されるのです。この Fig. 14 List 9 のサンプルラン $ . / は 8t9 2 : 12 , 12 4 : 48 , 48 5 : 96 , 96 6 : 192 , 7 : 384 , 8 : 768 , 3 6 24 192 384 768