のは当然です。 設計工程ではソフトウェアを構成する各 トコードを実行するとその中に定義された では求められている多くの機能を一気 部品の外部仕様が定められます。外部仕様 テストケースが順に実行され , 合格 / 不合 格の判定を下します。 に実現するのではなくて , より基本的ある とはインタフェイスすなわち関数とそれに いは重要なものから少しずっ , 実際に動く 与える引数および戻り値 , そしてその関数 XP ではなぜ , 部品の実装に・先立って ' テ ものを作っては要求を満足しているかを検 がどのように振る舞うか , です。実装工程 ストコードを書くのでしようか。それは部 証し , 変化を受け入れながら軌道修正しつ でプログラマは外部仕様を元にコードを書 品の実装時に積極的にテストを利用するた つしだいに完成に近づけていきます。 き , 与えられた仕様を満たしていることを クラス Counter の外部仕様 ( クラス宣言 ) 検証するためのテスト , いわゆる。単体テス C0de alittlej Test alittle ト : UnitTest' を行います。 通常はある程度まとまった量のコーディ では , 従来手法とは比較にならないほ ングが完了した後 , 検証のためのテストを どの頻度でテストが繰り返されます。確実 行うのですが , XP は違います。 XP では外 に動くことが検証されない限り , その上に 部仕様が定まった時点でテストを書いてし 積み上げることを許しません。不安定な土 まいます。外部仕様が明らかになったのな 台の上に家を建てるのは , まさしく砂上の ら , どの関数にどんな引数を与えたらどん 楼閣です。 な結果が得られるかが決まっているのです 頻繁なテストはコーディングの工程にも から , それを検証するテストコードが書け 例外なく ( むしろより過激に ) 適用されます るはずです。 今作ている部品が確実に動作することカ : テストコードは部品が満たすべき数多く 検証されて初めて全体を組み上げるために の外部仕様を 1 つ 1 つ検証するテストケース 結合されます。 の集合 ( テストスイート ) となります。テス テストコード テストコードの実装 * Counter.h #ifndef COUNTER—H #define COUNTER—H class Counter ( private: int count-; / / カウント値 public: / / カウント値の初期値を in 土引に設定する Counter(int initial = 0 〃カウント値をインクリメント void incr( 〃現在のカウント値 int count( ) const; ろうかく #endif * CounterTest . h #ifndef COUNTERTEST—H #define COUNTERTEST—H #include <cppunit/TestCase. h> #include く cppunit/TestSuite. h> #include ” Coun し e て . h ” class CounterTest : public CppUnit: :TestCase private : Counter* c—; / / テストに必要なメンバ void test-ctor(); 〃コンストラクタをテスト void test—incr ( ); / / incr( ) をテスト public: virtual void setUp( / / テストケースごとの前準備 v 辷セ u 引 void に ea て ( / / テストケースごとの後始末 / / テストケースを束ねる static CppUnit: :TestSuite* suite( * CounterTest. cpp #inc lude "CounterTest. h" #include "Counter. h" #include <cppunit/TestCaIIer. h> #include <cppunit/extensions/TestSuiteBuiIder. h> VOid CounterTest: :test—ctor( ) { assert ( c—->count ( ) del ete c— c—ま new Counter(1); assert( c—->count( ) = 1 void CounterTest : : test—incr ( ) { c-->incr ( assert ( c—ocount ( ) assert ( c—ocount ( ) = = void CounterTest: :setUp( ) ( #endif void CounterTest: :tearDown( ) ( del ete c—; CppUnit: :TestSuite* CounterTest: :suite( ) { CppUnit : : TestSuiteBui ー der<CounterTest> bui ー der ( "CounterTest ″ buil der. addTestCa Ⅱ er に ctor ″ , test—ctor); test—incr builder. addTestCaller( "incr return buil der. takeSuite ( 72 C MAGAZINE 2001 9
オプジェクト工房 したくなります。簡単です。 main ( ) をほん の少しいじればいいのです。 / / テストスイート A ,B , c をテスト CppUnit: :TestSuite suite; CppUnit : : TextTestResu ーし resu 比一 suite. addTest (A. suite. addTest (B suite. addTest (C : :suite()); initia 嵭 count— 複数のテストスイートをテストする int Counter : : count ( ) const re turn count このようにコーディングとテストを繰り 返しながら個々の部品を組み上げていくわ けですが , 複数の部品をつなぎ合わせて作 られるプログラムをテストするとき , たく さんのテストスイートをまとめて一気に通 HTML 出力への変更 (HTMLTestResult.cpp) #include <iostream> #include "cppunit/HTMLTestResult. h" #include "cppunit/Exception. h" #include ncppunit/Test.h" #include "estring. h" namespace CppUnit { std: :ostream& CppUnit: :operator<<(std: :ostream& stream, HTMLTestResult& result) { resu ーし print ( stream return stream ー VOid もう一度コンパイルして実行すると , ! ! !FAILURES! ! ! Test Results: Run: 2 Failures : 1 : 0 Errors There was 1 failure : 1 ) line : 16 CounterTest. cpp c—->count ( ) 思惑どおりうまく動いてくれました。残る 1 つは test—incr() で起こったものです。 coun t ( ) が正しい値を返すのは検証済みですか ら , あやしいのは incr ( ) です。修正しまし よう。 void Counter : : incr ( ) 十十 count HTMLTestResult: :addError(Test* test, Exception (e) { TestResult: :addError (test, e); std: :cerr くく” E ”くく std: :endl; void HTMLTestResuIt: :addFaiIure(Test* test, Exception (e) ( TestResult: :addFailure (test, e); std: :cerr くく″ F ”くく std: :endl; 再度コンパイル / 実行すると , OK ( 2 tests) おめでとう ! Counter の完成です。お ほうび 菓子でも買ってきて , 自分に小さなご褒美 をあげましよう。 追加拡張時も同じ手順です。インクリメ ントがあるんだからデクリメントもあった っていいじゃない。メソッドを追加しまし よう (List6)0 実行結果はこんな感じ。 ! ! !FAILURES! ! ! Test Results: Run: 3 Failures: 1 E てて 0 て S . There was 1 fail ure : 1 ) line : 23 CounterTest. cpp c—->count ( ) 失敗して当然です。 decr ( ) の中身がから っぽなんですからね。正しく実装してあげ てください。 OK ( 3 tests) と出力されたらできあがりです。 VOid HTMLTestResult: :startTest(Test* test) { std : : cerr くく "Running くく testogetName ( ) くく TestResult: :startTest (test); Std : : cerr くく くく std : : end 嵭 namespace { std: :string escape(std: :string& str) { std: :string result; std : : string : : const—iterator it str. begin ( 曲 e ( it ! = str. end() ) { switch ( * 土し ) { case ' & ' : result 十 = fi& ー break; result 十 = "< ・ break; case く : result 十 = ” & g セー ー break; case > result 十 = "" ー break; case result 十 = *it; default: 十十土 return result; inline std: :string escape(const char* str) { return escape(std: :string(str) VOid HTMLTestResult: :printErrors(std: :ostream& stream) { if (testErrors ( ) ! = の { stream くくく p > く / く tab 厄 border= は” if (testErrors ( 「 = = 1 ) stream くく”く caption>There was く b > ″くく testErrors( ) くく” error く /b> く /caption>" くく std: :endl; stream くくく caption>There was く b > ”くく testErrors( ) くく” e てて 0 て s く / b > く / cap セ ion > ″くく std: :endl; stream くく "<tr><th>name く /th><th>line く /th> く th>file</th><th>expression</th> く /tて>朝くく std: :endl; int 土 = 1 ・ fo て (std: :vector く TestFailure*>: :iterator it = e てて 0 て s ( ) . begin( it ! = errors( ) . end( ); 十十土セ ) { 75 びてククのオブジェクト工房
めです。コーディングはテストとともに行 われます。つまりコードを少し書いてはテ ストして , 正しく動作することが確認でき たらまた少しコードを書く。これを繰り返 すわけです。こうやってちょっとずつ書い てはテスト , 書いてはテストを繰り返し , すべてのテストケースに合格したら完成で す。 完成したら , 部品とともにテストコード も大切に保管 / 管理しておきます。部品の 機能拡張 / 変更の際にはまずテストして , 確 実に動作することを確認しておきます。変 更の内容に応じてテストコードにも適宜修 正もしくはテストケースの追加を行います。 そして新たなテストコードに基づいて・ち よっとずつ書いてはテスト (Codealittle,T est a little) ' を繰り返します。いつだって 動くコードを元にしていることによる安心 と自信が得られます。変更に失敗してもち ゅうちよなくすてられます。ちょっとずつ ・ CppLlnit Test Results ー Microsoft lnternet Explorer つアイルの : 偏集、表示お気に入り しか書き換えないのだから , 手戻りもちょ Unit Test Framework っとで済みますからね。 である "CppUnit" です。 今回紹介する Unit Test Framework の C + + 版 1 発で瞬時に判定を可能にしてくれるのが なテストコードが不可欠です。で , ボタン トケースごとの合否を教えてくれる , そん で全テストケースを一気に実行し , 各テス じゃないがやってられません。ボタン 1 発 間と集中力と忍耐を強いられます。とても はならないとしたら , 完成までに膨大な時 画面に出力されたテスト結果を読まなくて 1 つにデータをキーポードから入力したり きなくてはなりません。テストケース 1 つ 全テストケースを一気に / 自動的に実行で しながら効率的に完成に近づけるためには , このように実装とテストを頻繁に繰り返 Fig. 1 HTML 出力の結果 入ルプ ( ! ' ' ロ ■回図 Run FaiIures Errors Test Results !!!FAILURES!!! オフジェクト工房 テストコードのメインルーチン * main #include く土 os セて eam > void decr( / / 追加 class counter ( ・ Counter. h メソッドの追加 void counter : : incr ( ) { return 0 ー int Counter: :count( ) cons セ { counter: :counter(int initial ) { #include "Counter. h" * Counter. cpp 中身がからっぽの Counte 「 . cpp return 0 ー result. p て int(std: :cout); suite. run(&result); suite. addTest(CounterTest: :suite( ) ) CppUnit: :TextTestResult result; CppUnit: :TestSuite suite; 土 n し main( ) { #inc lude "CounterTest. h" #include <cppunit/TestSuite. h> #include <cppunit/TextTestResult. h> 3 1 na me 0 There was 1 failure fi 厄 CounterTest. decr 23 CounterTest. cpp c- expression ->count() = ーわ ・ Counter. cpp void counter: :decr( ) { / / 追加 ・ Counte 「 Test. h class CounterTest void test-decr ( / / 追加 ・ CounterTest. cpp void CounterTest: :test—decr( ) { c—->decr ( assert ( c—•count ( ) = = -1 c-->decr ( assert ( c—-»count ( ) CppUnit: :TestSuite* CounterTest : : suite ( ) { builder. addTestCaller("decrn test—decr5; return builder. takesuite( ど m びてククのオブジェクト工房 73
共有権付きポインタの例 参照カウンタ付きポインタを実現するための構造体 #endif #define DBG(X) X # se #define DBG(X) #ifdef NDEBUG #inc lude ” shareptr. h" #inc lude く assert. h> #include く stdlib. h> #include く stdio. h> shareptr. c memset(sp—get(theMem1) ,Ox55,ALIÆATE-. SIZE); / * theMem1 が所有しているメモリに 55h を埋める * / sp—assign(theMem2,theMem1); / * th eml の所有ポインタを theMem2 にも共有させる * / shareptr theMem2 = sp—new(NULL); / * theMem2 が所有するメモリはない * / shareptr theMem1 = sp—new(maIIoc(ALLOCATE—SIZE) / * theMem1 に ALLOCATE-. SIZE バイトのメモリを所有させる * / 土 n に main ( void ) #define ALIÆATE-. SIZE 256 #include "shareptr. h" #include <stdlib. h> #include <stdio. h> typedef struct { void* mObjPtr; unsigned int mcount; } sharecel 嵭 struct shareptr—struct { sharece Ⅱ *mCe Ⅱ Ptr; / * 共有されるメモリへのポインタ * / / * 参照カウンタ * / 土 nObjp いを指す参照カウンタ付きポインタを新規確保して返す shareptr sp-new(void *inObjPtr) 十十 ioDest—>mCe Ⅱ Ptr->mCount ー if(ioDest->mCellPtr ! = NULL) { ioDest->mCellPtr = ioSrc->mCellPtr; static void sp—retain(shareptr ioDest,shareptr 土 OS て c ) (ioDest ! = 土 osrc の前提 ) 土 osrc の参照カウンタ付きポインタを i 。 s セも共有開始する ( 内部使用 ) return theAns ー DBG(printf("h01d objec 日ゆ ) ” ,inObjPtr) theAns->mCe Ⅱ Ptr->mCount = theAns->mCellPtr->mObjPtr = inObjPtr; assert(theAns->mCellPtr ! = NULL); theAns → mCellPtr = malloc(sizeof(sharecell ) )else( theAns->mCellPtr = NULL; if(inObjPtr = = ) { assert(theAns ! = NULL); DBG(printf(nsp—new%n") shareptr theAns = malloc(sizeof(struct shareptr—struct) 真紀俊男の ローテク講座 DBG(printf("objectßp) ref count = List 7 return の sp—deIete(theMem2); sp—deIete(theMem1); / * theMem1,theMem2 を解放する * / abo ( printf( ” * NG * n ” if(*thePtr 十十 ! = 0X55 ) { for(thel = thel く ALI.mATE-. SIZE; thel 十十 ) { int thel; char *thePtr = (char *)sp—get(theMem2); に 55h が埋まっているのを確認する * / / * theMem2 が所有しているメモリ ( theMem1 が所有しているものと同一 List 6 iosrc—>mce Ⅱ Ptr->mObjPtr , iosrc—>mce Ⅱ ptr—>mcount ) DBG(printf( "object(%p) ref count = 宅 u 蕚 n ” )else{ f て ( ioSrc->mCe Ⅱ ptr free(ioSrc->mCeIIPtr->mObjPtr); DBG(printf( "release objectßp)*n" は0SてC → mCellPtr->mObjPtr) if(--ioSrc->mCeIIPtr->mCount = 0 ) { if(ioSrc->mCeIIPtr ! = NULL) { static void sp—release(shareptr 土 OS て c ) 土 os て c の参照カウンタ付きポインタを解放する ( 内部使用 ) ioDest->mCellPtr->mObjPtr,ioDest->mCellPtr->mCount) 土 ns て 0 の指しているオブジェクトへのポインタを返す sp—retain(ioDest,i0Src); sp—release(i0Dest); if(ioDest ! = 土 oS て 0 ) { DBG(printf("sp—assågn%n") void sp—assign(shareptr i0Dest,shareptr i0Src) 土 os て c の内容を io s セに移す ioSrc->mCellPtr = NULL; free ( iosrc sp ーて引田 se ( 土 OS て c DBG(printf( "sp-delete%n") void sp—delete(shareptr i0Src) 土 os て c を解放する return (inSrc->mCellPtr = = NULL) ? NULL : inSrc->mCellPtr->mObjPtr; DBG(printf("sp-get%n") void * sp—get ( const shareptr insrc ) 真紀俊男のローテク講座 1 17
standard portlibrary) 19. 診断ライブラリ (Diagnostics library) 20. 汎用ライブラリ (GeneraIutilities libr ary) 21. 文字列ライブラリ (Strings library) 22. 地域化ライブラリ (LocaIization libra 23. コンテナライブラリ (Containers libr ary) 24. 反復子ライブラリ ( は e 面 0 「 s library) 25. アルゴリズムライブラリ (Algorithm s library) 26. 数値ライブラリ (Numerics library) 27. 入出力ライブラリ (lnput/output libra また , 補遺 4 は「互換機能」を定義してい る。これもまた標準扱いである。 言語部分と同様 , ライプラリの章は , 診 断ライプラリのようにきわめて単純なもの から , 地域化ライプラリや入出力ライプラ リのようにきわめて拡張的なものまで幅広 い。 C + + 標準に準拠しているかのテストを 書く観点から , これらライプラリの章を順 に見てゆく。これらのテストの集合は , 通 常 , 検証スーツ (validation suites) と呼ばれ る。前回に説明したように , 検証スーツは もっとも実用主義的なツールであり , C + + 標準などの与えられた仕様に対し , ある実 装が準拠している程度を決定するためのも このようなテストを 1 のである。 ListI に つ示す。これは , 17 章の「ヘッダは 1 回以上 インクルードできねばならない」という要 求に対するテストである。もちろん , この テストは網羅的とはとうていいえない。で きることのすべては , 代表的なヘッダを 2 つインクルードし , ヘッダく casse れ > がそれ に値する特別な扱いを受けることを確認す ることだ。テストそれ自体は 2 つのコード 部分からなる。 1 つはファイルレベルで存 在し , もう 1 つは関数 main の中にある。そ れぞれのコード部分は同じ種類の ifdef ロジ ックで囲まれる。これによりテストをバッ チで流す際にさまざまな条件テストオプシ ョンが可能となる。ヘッダ "defs. h" は , be gin—chk や end chk などの , テストのログ 取りや結果のレポートに使ういくつかの関 数とマクロを定義している。たとえば , 関 数 ieq は , 整数引数 2 つが等しくない場合に 失敗をレポートする。このコード自体は , Dinkum C + + Proofer の中の代表的なテスト であるが , 実質的にどのような検証スーツ でも , その中には似たようなものが見つか るはずだ。 17. ライプラリ前書き Libr ary intro duction 17 章は , ライプラリの概要説明を行って いる。いくつかの術語を定義し , ライプラ リ全体にわたる要求をいくつか規定してい る。ということで , 実行可能なコードによ ってテストできる客観的な文をいくつか含 んでいる。たとえば , 「本ライプラリでは , 型の安全性を保証するために適当な修正を 施したうえで , 標準 C ライプラリの機能も また同様に利用可能でなければならない」 といった宣言がなされている。おそらくテ スターは , これを標準 C ライプラリへッダ の存在のテストをする口実にするだろう。 しかし , そのようなチャンスは後からいく らでも出てくる。少なくとも , 「適当な修 正」の詳細の説明がない現状では , 標準 C ラ イプラリが「適当な修正を施されている」か どうかをテストする方法がないことは確か List a Clause 17 test #include "defs.h" #if !defined(SKIP—17421T22)&&( !defined(ONLY) lldefined(CASE—17421T22) ) / * ー 17421T22 は 2 度以上インクルードされるかもしれない ( く assert. h> を除く ) * / #include く cstddef> #include く cstddef> #include <iostream> #include <iostream> #undef NDEBUG #inc ー ude く cassert> static int funl ー 17421T22 ( ) #def ine NDEBUG # ー ude く cassert> static int fun2 ー 17421T22 ( ) #undef NDEBUG # ー ude <cassert> static int fun3 ー 17421T22 ( ) #endif / * CASE ー 17421T22 * / 土 n セ main ( 土 n char * [ ] ) enter—chk( ” t17421t22. cpp" {assert(l); {assert(0 ) , {assert(l); return ・ return return ( 1 ) ( 1 ) / * ー 17421T22 は 2 度以上インクルードされるかもしれない (<assert. h> を除く ) * / #if !defined(SKIP—17421T22)&&( !defined(ONLY) lldefined(CASE—17421T22) ) begin—chk( ”ー 17421T22 ” ); ieq ( funl ー 17421T22 ( ) , 1 ieq ( fun2 ー 17421T22 ( ) , 1 ieq ( fun3 ー 17421T22 ( ) , 1 end—chk( ″ー 17421T22 ” #else skip—chk( ″ー 17421T22 ″ #endif / * CASE ー 17421T22 * / return leave—chk( ”セ 17421 に 22 ” 66 C MAGAZINE 2001 9
します ) 。 List 8 は , 呼び出し側のコードで スタティックに実行ファイルにリンクされ Linux では DLL のことを「共有オプジェク ト」 , もしくは「共有ライプラリ」と呼びま す。 ていました。それに対して , ハードディス クを前提としている Windows では , DLL(D す。その使い方は , Windows のそれとほぼ List 9 は , それらをビルド , インストール ynamic Linkage Vbrary) の機構を用いて , するためのメイクファイルです。共有ライ 同等です。 現在の Linux では , 共有ライプラリを作 プラリをビルドする際に , , fPIC " を付与し ライプラリの関数やリソースを , 実行ファ イルに動的にリンクする方法を提供してい るのは非常に簡単です。 List7 は , ライプラ て Position lndependent Code を生成し -s リのコードです (testl. c というファイル名と hared オプションを付ければ共有ライプラ ました。 リを生成してくれます。 List ファイルの属性を表示する 3 また , インストール後には , ldco ⅶ g を実 行し , ライプラリキャッシュに反映させま #include <stdio. h> す。詳しくは , ldcon ⅱ g のオンラインマニュ #include く日 ys / t ) 8. h > #include <sys/stat. h> アルを参照してください。ちなみ , ldcon #include <unistd. h> を実行する前に実行権をチェックしてい #include 土 2. h > *define ShowBit(bit) るのは , パッケージ作成の際に , 一般ユー if & bit) = = bit) ( p て北 f に 8 新 # bit, pszDeIim); ザで「仮想インストール」をする可能性があ るからなのです。詳しくは , M のマニュ アルを参照してください。 なおここで挙げた例では , わかりやすさ のために必要最低限のオプションだけを用 いています。実際に配布する共有ライプラ リを作成する際には , バイナリ互換性の考 慮や規約に従ったバジョン名のつけ方な どが必要になります。より詳細な情報に関 しては , GCC-HOWO(http://www.linux.or. jp/JF/JFdocs/GCC-HOWTO. html) などの 文書を参考にしてください。 void G Ⅲアプリケーション int main ( int argc' 00 れ 8 セ char * pszPile; 8 セて uc に 8 セ a に statl ー p を e = gv [ 1 if い at ( p 日 i , & 日 ta ヒ 1 ) ト 0 ) ( fprintf(stderr, 日 ta し failed%n"); return ( 1 printf( "Device: 宅 04X n % (int) (dev-t) statl. st-dev); printf( "lnode れ um て : (int) (ino—t) gtatl. 日 t ー土 no printf( "Mode: "); 8h0 e ( (mode-t) 日セ a セ 1. 北ー mode printf("%n" 潺 土 n ( ” N 題ェ、 0f は献 8 : 記齟 % ( 遍し ) (nlink-t) statl. st-nlink); printf( "UID: %d*n", ( 土 n セ ) (uid-t) 8taセ1. Bt—uid); p て土れし f ( "GID: %d%n"/ ( 土 n 日 (gid-t) sta セ 1. ー g 土 d if (S-ISCHR(stat1. st-mode) Ⅱ S-ISBLK(Btat1. st-mode) ) { printf( ” v 土 ce major/minor れ um て : 宿 04X n ” ( 加日 ( d ユ ) 日セ a セ 1. ーて dev printf( "Size: ( 土 n 日 (off-t) sta セ 1. st ー size printf( ”引 ock 日土 ze : %ld%n", (unsigned long) gtatl. gt ー b 慊日土 ze printf( ” Nu て 0f ocks : (unsigned long) statl. st ー ocks p て土れセ f ( "La. st acce 日 8 : 宿日 % 0 し土 me ( (time—t * ) & gtatl. st-atime) printf( ” I 歳 st modification 日 % ctime( (time-t * ) & statl. st-mtime) printf( "Last change: 宿 8 % ctiæ( (time—t * ) & gtatl. gt-ctime) て e に n ( 0 ーツールキット ツールキットとは , GUI を構築する際の 部品 ( ウイジェット , Windows ふうに言う とコントロール ) の集合体であると考えて ください。 Linux 環境では , 現在 , GTK + と Qt という 2 種類のツールキットがメジャー です。それ以外にも , , Motif, Athena などがありますが , これから学習を始める のであれば , GTK + か Qt のどちらかを学ぶ べきだと思います。 ちなみに , GTK + の開発には基本的に C 言 語を , Qt では C + + 言語を用いることになり ます。また ,. どちらのツールキットにも , PerI, Python, Ruby などの高級スクリプト 1 6 C MAGAZINE 2001 9
前回の Homework の答え Ex. 4.2 ポインタ型変数を使用した引数 前々回の Homework Ex. 3.2 のプログラム をポインタ型の引数を使用して , 作り変え るよう出題しました。 List 2 に解答を示し ます。 前回は float 型の引数で半径を関数に引き 渡していましたが , 今回はポインタ型の引 数として関数に引き渡しました。そのため , 関数 fncgetcirclearea 内での変数 r の扱いが 少し変わりましたが , ポインタとアドレス の関係を理解していれば , 簡単なプログラ ムだったと思います。 Ex.4.3 関数間での配列データのやりとり 次のような条件を満たすプログラムの作 成・実行を出題しました。この解答は前回 の List 4 をヒントにしたので , 2 通りの解答 を List3, 4 に示します。 前回の List 4 をヒントに gets 関数と tolowe r 関数を使用してプログラムにしました。 前回の List 4 とは異なり , 入力した文字が 変換されて画面上に表示されるので , 自分 の作成したプログラムをより体感すること ができます。それぞれ自分が入力した文字 がどのように変換されるかをイメージしな 同じアドレスであることを確認してくださ がらプログラムを起動させて確認してくだ さい。 今回の Homework では , 今回説明した内容から Homewo ⅸ を出題します。次回 , 解答とその解説を行 ist 1 : 2 : 4 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : 14 : } 17 : { 19 : 20 : 21 : 22 : 23 : 24 : } ISt 1 : 2 : 4 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : } 14 : 16 : { 17 : 18 : 20 : 6 ロロロロ Ex. 4.3 の解答 ( cha 「 * 型の戻り値で文字列を返す ) #include <stdio. h> #include«ctype. h> 3 : char * chgchapa(char *str); 5 : void main( ) char st て wa [ 128 char *strwA; gets ( strwa ); strwA = chgchapa(strwa); printf("strwA = %s *nn, strwA); 16 : char *chgchapa(char strt ] ) fo て ( 土 = st て [ 幻 ! = ' 基 0 i 十十 ) { s [ 幻 = t 引 owe て ( s [ 幻 return S セて一 Ex. 4.3 の解答 ( ポインタ型引数で文字列を返す ) #include く s 記土 0. h> #include<ctype. h> 3 : void chgchapa(char * s い 5 : void main( ) char strwa[1281; gets(strwa); chgchapa(strwa); printf("strwa = strwa); 15 : void chgchapa(char *str) while(*str){ *str = し 0 ー 0 e て ( * st て 十十 st て一 う予定です。 します。さらに変数 cl , 己を % p で表示して , 文を 10 行目の下に移動すると , 正しく動作 理を入れてみてください。 17 行目の printf 実際に 12 行目に for ループなどの適当な処 ログラミングして実行してください。また 今回例題として示した List1 を実際にプ ・ Ex. 5.1 プログラムの実行 Ex. 5.2 ・関数内で初期値を 0 とする static 変数を くヒント > 定し , 結果を戻り値とする関数を作成 して受け取り , それまでの最大値を判 今回画面から入力された数値を引数と するプログラム た数値の最大値を画面に出力し , 終了 力された時点で , それまでに入力され 数値を任意の回数入力し , 0 以下が入 ・画面から 0 より大きい i nt で表現できる くプログラムの仕様 > 使用して作成・実行してください。 次の条件を満たすプログラムを , 関数を 記憶クラス (static) を使用したプログラム 使用し , 値の保持を行う あとがき 今回は主に変数のスコープと記憶クラス について , 説明しました。いかがでしたか ? ポインタと変数の記憶クラスについてさえ しつかり理解しておけば , まずは 1 段階ク リアしたといえます。データのやりとりと , 記憶クラスの関係を把握しておくことは , C プログラミングにとても大事なことと言 えます。 次回は , 標準関数についてまとめます。 これらを知っておくと , C 言語をより有効 に使用することができます。また , C 言語 の次なる段階の構造体の説明に少しずっ入 っていきます。 Get to C World ! ! 53
圓『衄 m 加 Fig. 1 データ送信のサンプル 1 プログラム データの共有 Ti データ送出側 共有したいデータ内容を記述し下のボタンをクリックして下さい Fig. 2 データ受信のサンプル 2 プログラム データの共有 TIPS データ確認側 共有したデータ内容を確認するには下のボタンをクリ↓クして下さい da ね共有テスト 共有データ 广可 0 0 MAGAZINE 0 MAGAZINE を 0 e 002 List List 1 データ送信のサンプル 1 プログラム WPARAM wparam, LPARAM lParam) データ共有 Tips メモリマッブドファイル作成側 #def ine STRICT ゾ #include «windows. h> #include <windowsx. h> #include "sample. h" #include "resource. わ” static 日の hWndmain; static HINSTANCE hlnst; CMAGAZINE"• static char szAppName[ ] char szMsg [ 256 int e て ぎ朝 a e ef 1 で新を static 日の L 窟 hMapFile = 肌ル s ねセ土 c LPTSTR lpMapAddress = NULL; static HANDLE hMute ま U し・ Wi nd ow を画面中央表示する void winposition(HWND hwnddlg) int x,y,xsize,ysize; RECI' rect; GetWindowRect(hwnddIg,&rect); xsize ま rect. て ight ーて ec セコ ef セ引 ysize = て ec セ . し om ーて ec し tO ー x ( GetSystemMetrics(SM—CXSCREEN) - xsize ) / y = ( GetSystemMetrics(SM-CYSCREEN) ー ysize ) / MoveWindow( hwnddIg,x,y,xsize,ysize,TRUE); HICON hlcon; LRESULT lResult = ERROR—SUCCESS; gwitch (message) { case WM—INITDIAIÆ: hWndmain = h 田 hlcon ま LoadImage(hInst,MAKEImESOURCE(IDI—ICON1), Ⅲ AGE ー ICO 32 , 32 , 0 SendMessage(hDlg,WM—SETICON,FALSE, (long) (HICON)hIcon); hlcon = LoadImage(hInst,MAKEINTRESOURCE( IDI—ICONI), I AGE ー ICON 卩 2 , 32 , 0 リ SendMessage(hDIg,WM-SETICON,TRUE, (long) (HICON)hIcon); apF 幻谷・を t Ⅱ app 土れヨも 0 を F を , : 瓸 , 。 PAG 呂ー R D E , のユ 024 , 期 2 ー巫 return ( TRUE case WM-. SYSCO>MAND: if (wparam = = SC-CLOSE) { ー idD 土引伍 D , TRUE); return TRUE; ) else return FALSE; break; case W 懸ー CO D ・ if ( 能 = IDC-ON ) { if ( hMapFile ) { ゆ齟 PAd e pv 土 e モ F 土地ぐ市鯊äPF Ⅱ e , FILE—MAP—ALL—ACCESS,O, 0 , 0 WaitForSingIeObjeet(hMutej FI E リ 岫 00Y い 1 pMapAddress 声 sg ) 引薹第、 ReleaseMutex(hMute); 垣 shV fF 土垣 ( = es 恒い一印 ( 。は p p 号ナ 引 se ( MessageBox(hWndmain," マッピングが失敗しました” szAPPName,MB—OK); て e にれ ( 駅 * function : mainD 地 P て oc ( ) LRESULT CALLBACK mainDlgProc(HWND hDlg, UINT message, があったことを知りたいわけです。こうな は , DLL 内の変数保持ではなく「フック」が 法を利用する るとそのハンドルがプロセスごとに別の値 メインだったので簡単な方法で実現してい 今までは次のように #pragma を利用して ましたが , 今回はこの DLL 内での変数保持 になっていては困ってしまいます。またフ いました。 ック関数内で処理しない場合は次のフィル を別の方法で実現することになります。 #pragma data-seg ( ″ . sharedata") タ関数にその内容を渡す必要がありますが , もう一度簡単におさらいをすると , 別の HWND hWndtgt=NULL ー この場合もプロセスごとにコピーされてい アプリケーションによってロードされたほ HHOOK hhook ー たのでは , まったく違う内容を渡すことに かの Wrn32 DLL などとデータを共有するに #pragma data-seg ( ) なってしまいます。実際にはほかのプロセ は , 次のどちらかの方法を使用します。 サンプルプログラムの動作 スの中では単なる 0 で初期化された変数に ・ #pragma 文を使用して , 名前付きデー なっています。 タセクションを作成する 前回までのフックのサンプルプログラム ・メモリマッブドファイルなどの共有方 今回は , サンプル 1 プログラム ( Fig. 1 ) , て eturn ( 駅 Windows programming Tips 1 23
00 ①ⅱ囂フログラミングの作法 育った UNIX ・ Linux では , プログラムは ( ソ ースレベルではなくバイナリレベルで ) , 語などに依存しないように実装することが 推奨されています。 List 4 は , 環境変数 LC 工に設定された 値に従って , 表示するメッセージを特定の 言語に切り替えます。 List 5 は , 管理用のメイクファイルです 0 char sz [ 256 FILE * foutput; FILE * finput; if ( fork( ) ! = 0 ) { return ( 1 fprintf ( stderr , if (pipe(inputs) ! = 0 Ⅱ pipe(outputs) 土 n セ output ー 土 n セ input ー int outputs [ 2 土れセ inputs[2]; char * * argv ) { int argc , 盟 ( int #include <unistd. h> #include く stdåo. h> 1 リダイレクト , バイプ pipe failed*n" make updatepo を実行すると , 語力タ ログファイルが存在しない場合には新規の 言語力タログを生成します。すでに存在す る場合には , 既存のカタログファイルと , 更新されたソースコードから抽出した文字 列からなる最新のカタログファイルをマー ジしてくれます。 List 6 に 言語力タログファイルの例をあ げます。生成されたばかりのカタログファ イルは記述が不足しているため , そのまま では用いることができません。適宜 , 編集 して使用します。 フロッピーベースで動くことが前提とな っていた MS - DOS では , ライプラリは通常 , ー共有ライブラリ 環境変数を扱う 2 #include く stdio. h 》 #include <stdlib. h> extern char * * environ ・ int main ( int argc , char * * argv, ch * * env ) { 土 n セ const char * psz; fo て ( 土 = environ[i]; 土十十 ) { printf("%d: 宿 8 n % 土 , environ[i] psz = getenv("LANG"); if (psz) { printf("%s=%s*n", "LANG", psz); ) else { printf("Environment variable Y"LANG%" not defined%n"); printf("environ: Ox な 08X , env: 0x 08X 蕚 n % environ, env); . /stat /etc/fstab Fig. 4 List 3 の実行結果 て e セ n ( 0 input = inputs[O]; close(inputs[l]); close(outputs[01 output = ou セ pu と s [ 1 嵭 finput = fdopen(input, ”て” foutput = fdopen(output, ”″ while (fgets(sz, sizeof (sz) , printf("p: 宿 } e lse ( close(inputs[O]); output = 土 npu し 8 [ 1 input = outpu セ 8 [ 0 嵭 close ( outpu し 8 [ 1 ] dup2(input, 0 dup2(output, 1 リ dup2(output, 2 argv 十十一 execvp(argv[0], fprintf ( stderr , "exec fail ed%n return ( 1 return ( 0 Fig. 3 List 2 の実行結果 0 : PWD=/home/knaka XAUTHORITY=/tmp/Xauthbz6819 HOSTNAME=monet.ayutaya.com 3 : HISTFILESIZE=IOOO . く中略 > TERM=kterm BIN=/home/knaka/bin 44 : 43 : 2 : 1 : finput) ) { Device: 0802 lnode number: 128086 MOde: S—IFREG, S—IRUSR, Number of links : 1 UID: 0 GID: 0 Size: 794 Block size: 4096 Number of ocks : 8 S— I WUSR , S— I RGRP Last access: Sun Jul 15 22 : 21 : 56 2001 Last modification Sun Oct 15 18 : 13 : 53 2000 s セ change: Sun セ 15 18 : 13 : 53 2000 $ . /stat /dev/fd0 Device: 0802 lnode number: 202605 Mode: S—IFBLK, S—IPDIR, Number of links : 1 UID: 0 GID: 19 S—IFCHR, S—IRUSR, S— I WUSR 45 : PATH=/bin: / Ⅱ 8 て / b 土 n : /home/knaka/bin: /home/knaka/dest : /sbin: /usr/*sbin: / us て / X11R6 / b 土 n : / us て月 oc 引 /bin: / us て月 oc 引 /sbin environ : 0xBFFFF9PC , env : 0xBFFFF9FC LANG= ja—JP. eucJP . /a. ou 46 : SSH—TTY=/dev/pts/4 Device major/minor number: 0200 Size: 0 Block size: 4096 Number of ocks : 0 Last access: Wed May 6 05 : 32 : 26 1998 Last change: Sun Feb 27 02 : 56 : 03 2000 Last modification wed May 6 05 : 32 : 26 1998 特集 1 Windows プログラマへ贈る Linux プログラミングの作法 1 5
Java programmingTips a を読み書きする処理です。 List 1- ①のよ 情報とフィールド ID はそれぞれ jclass およ 手順 3 うに a は int 型の非 s ねⅱ c フィールドです。 び jfieIdID という型です。 続いて , C プログラム ( List2 ) をコンパイ a にアクセスするには , まず List 2- ④のよ List 2- ⑤の GetFieIdID() 関数の第 4 引数で ル・リンクして , DLL を作成します。 C コ うに GetObjectCIass() 関数を用いてクラス は " I " を渡していますが , これは int 型のフ ンパイラには , 本誌付録 CD - ROM に収録さ 情報を取得します。次に List2- ⑤のように ィールドを表す「シグネチャ」と呼ばれる文 GetFieldID() 関数を用いて , フィールド ID れている BorIand C + + CompiIer 5.5.1 を利用 字列です。次の List 2- ⑥では char 型を表す しました。以下のコマンドの「 c dkl. 3 」と を取得します。 List 2- ③のように "C" , 続く List 2- ⑦では double 型の配列を表 クラス いうパスは , JDKI. 3 をインストールしたデ Fig. 1 List 1 , 2 の出力結果 ィレクトリに合わせてください。正常にコ C:*work>java JNITest3 ンパイルとリンクが完了すると , JNITest Nat ive Ex d Ⅱというファイルが生成されます。 Java 比 C32 -lc:*jdkl. 3%include;c:*jdkI.3 *inc lude*win32 -WD JNITest3Ext. c これでコンパイルは終了です。以下の コマンドで実行します。 java JNITest3 なお JNITest3Ext. dll は , PATH 環境変数 で指定されたパス , またはカレントディレ クトリに置いておく必要があります。正し く動作すれば , F1g. 1 のようなメッセージが 出力されるはずです。上の行がネイテイプ プログラム (C プログラム ) の出力 , 下の行 が Java プログラムの出力です。 トプログラムの説明 List1, 2 の要点を解説します。まずは簡 単な List 1 から説明しましよう。 Java プログラム (List 1 ) List1- ②はネイテイプメソッド sub() の宣 言です。 sub() の内部では , List 10 で定義 される JN 仼 es オプジェクトのフィールド a, b, c と , List 1- ④で定義される java. awt. Point オプジェクト p にアクセスします。 List 10 のように , オプジェクト p は引数として sub ( ) メソッドに渡します。また , List 1 つ はネイテイプメソッドを含む DLL のロード 処理です。 フィールドの読み書き 次に I-ist2 について説明します。 List2-O, ⑤は , JNITest3 オプジェクトのフィールド List オプジェクト・配列の受け渡しの例 ( C プログラム , JNITest3Ext. c) #include く jn 土 . h 〉 #include "JNITest3. h" #include く stdio. h> - ネイティブメソッドの定義 I p ま vo 坦 - 工 CA い、 04V4 ー est3 ー s 面 ( I 新 v *envm 」 ob 00 ob 扣 ect pYC— / / Java の int 型に対応 jint / / Java の char 型に対応 jchar ノ / va の d 。厄型配列に対応 jdoubleArray 当ノみ 00 厄型配列の内容へのポインタ / / Poi れ t オブジェクトの各フィールドに対応 jint PX,PY; ・ / / va のクラズ情報 jclass clg; ノ / ava のクラスのフィールド IP jfieIdIP 、一 ー Java のクラス情報を取得 0 チⅵつ GetOb ゴ ec c s 引印ち ob ゴリ ー in セ型のフィールドを取得、変更 fid ま (*env)->GetFieIdID(env, els, % 滝 a (*env) → GetIntFieId(env, Obj ′ fid); 。 00 璉ト Se い職引引 env 0 わは坦計工 - char 型の static なフィールドを取得、変更 Eid = (*env) → GetStaticFieldID(env, cls, % % を b = (*env)->GetStaticCharField(env, cls, fid); (*env)»setscaticCharField(enVi の町 tid, b 十工 ) 引 - do 厄型配列のフィールドを取得 fid = (*env)->GetFieIdID(env, cls, nc", / / 配列情報を取得 ary = (*env)->GetObjectFieId(env, 0bj, fid); 。〃配列の内容を取得 0 (*env)—>GetDoubIeArrayElements(env, ary, 0 ) 引 - java. awt. point オブジェクトのフィールドを取得、変更 ~ なクラス情報を取得 0 (*env)-?GeCObjectCIass(envj や 土 d ま (*env)->GetFiéldID(env, 0 , *x / / p ⅸを取得、変更 px (*env)->GetIntField(env, p, fid); . ( * 00 ⅵつ Se した土引 d = ( env, p , = は , p = 十エ 0 = = = 。登、。 ; 0 0 、 = 0 { ゾ = = = = 0 冫 00 、 0 。 = fid = (*env) → GetFieldID(env, cls, y , py = (*env)->GetIntField(env, p, fid); (*env)->SetIntFieId(env, p, fid, py 十 1 - 取得した値の表示 printf ( "Native : a=%d, b='%c', c [ 0 ] = 宅 1. If , c [ 1 ] = 1. If , p = ( 宿宅 d n ” a, b, c[0], c[l), px, py - do 曲厄型配列のフィールドを変更 (*env)->ReIeaseDoubIeArrayEIements(env,aryt 0 , 0 〃内容の変更を反映 〃非 static の場合、 第 2 引数は。墫 / / static の場合、 / / ー第 2 引数は c 垣 ⑦⑧⑨ 〃 p. y を取得、変更 1 2 / Java Programming Tips