私たちはこれまでの連載を通して,セキュリティ・ホールの原因となるバッファ・オーバーラン(BOR)の発生メカニズムやその防止用のコンパイラ・スイッチなどをアセンブラ・レベルで詳しく学習してきました。今回は趣向を多少変え,クラスとセキュリティの関係を具体的なサンプル・プログラムを使って学習してみたいと思います。

 プログラム内でクラスを使用すると,そのプログラムのセキュリティは向上するといわれています。これはいったいどういうことなのでしょうか? それでは早速,今回のサンプル・プログラムを見ていきましょう。


#include <iostream>

#include <string>

#include "HelperNo8.h"

using namespace std;

using namespace ITProToyota;



int main()

{

  string Des [] = { "豊田孝","日経BP IT Pro"} ;

  Server(Des[0]);

  Server(Des[1]);

  return 0;

}

 ご覧のように,このサンプル・プログラムは,これまでのCサンプル・コードをクラスを使って書き直したものにすぎません。といっても,プログラム経験の少ない人にとっては,このコードはかなり難しい部類のものではないかと思います。このサンプルコードは,次のような考え方を基にして作成しています。

  • 標準C++ライブラリを活用する
  • 標準C++名前空間を活用する
  • 自作名前空間を活用する
  • 標準C++文字列配列を活用する
  • 標準出力機能を活用する

 C++プログラミングを設計・実装したBjarne Stroustrup氏は,これからC++を学習する人は,まず標準ライブラリの使い方を習得するようにアドバイスしています。詳しくは,こちらをご覧ください。

 ここで,main関数の実装内容を見てみましょう。この関数の動作内容は,基礎的なプログラミング知識を持っている人は誰でも理解できるほど単純なものです。Serverという外部関数を呼び出し,文字列を渡しています。Server関数の使い方に関する情報は,インクルードしている「HelperNo8.h」というヘッダー・ファイルから次のように分かります。


#include <iostream>

#include <string>

using namespace std;



namespace ITProToyota

{

  unsigned int Server(string);

}

 これは関数宣言情報といわれるものですが,Server関数の使い方を次のように定義しています。

定義1:Server関数は,名前空間ITProToyotaに所属する
定義2:Server関数は,クラスstringのインスタンスを受け取る
定義3:Server関数は,処理完了後,符号なし整数を返す

 これら3種類の情報を理解し,私たちは次のようなコードを記述することになります。


using namespace ITProToyota;



string Des [] = { "豊田孝","日経BP IT Pro"} ;



Server(Des[0]);

 関数からの戻り値を使用するかどうかは関数使用者の判断に任されていますから,定義3に関しては,今回のサンプル・プログラムでは無視しています。

 ここまでの説明内容はプログラミングの基本ですから,理解する上で大きな壁はないと思います。しかし,クラスとセキュリティの関係はどうなっているでしょうか? ここまでの説明をいくら読んでも,「クラスを使用すれば,プログラムのセキュリティが向上する理由」は一切分かりません。それでは,Server関数の実装内容を見てみましょう。次のコードをご覧ください。


#include "HelperNo8.h"



namespace ITProToyota

{

  typedef basic_string<char>mystring;

  mystring buff;

  unsigned int Server(string); // あってもなくても可

  namespace ITProToyotaInternal

  {

    void ShowResults();

  }

}



unsigned int ITProToyota::Server(string inData)

{

  using namespace ITProToyotaInternal;



  buff.append("あなたは ");

  try

  {

    buff.append(inData);

  }

  catch (length_error le)

  {

    cout << le.what() << endl;

    return ((unsigned int)1);

  }

  buff.append(" さんですね");

  ShowResults();

  return (unsigned int) buff.size();

}

 (必要以上に難しく書いてありますから)一見すると大変難しそうに見えます。しかし,次の2つの視点からこの実装コードを眺めると,意外と単純であることがわかります。

視点1:名前空間と関数
視点2:標準stringクラスの再利用

 視点1は,次のように実装されています。


namespace ITProToyota

{

  typedef basic_string<char>mystring;

  mystring buff;

  unsigned int Server(string); // あってもなくとも可

  namespace ITProToyotaInternal

  {

    void ShowResults();

  }

}

 ご覧のように,ITProToyotaという名前空間の実装詳細が定義されています。難しいと感じるところがあるとすれば,名前空間ITProToyota内部で,さらに別の名前空間ITProToyotaInternalを定義していることでしょう。また,basic_string<char>mystring;というコードに注意を取られる人もおられるかもしれません。これは,今後普及すると思われるジェネリック・プログラミングの一種で,目的の型をパラメータを通して柔軟に作成するテクニックです。

 ここで注意していただきたいのは,名前空間ITProToyotaInternal内に定義実装されるShowResults関数は,HelperNo8.hヘッダー・ファイルから公開されてないことです。つまり,このShowResults関数は,Server内部で使用するだけですから,外部から隠ぺいしているのです。

 名前空間,関数,情報隠ぺいなどの表現が登場し,一部の人は混乱されていると思いますが,今回のサンプル・コードは標準C++仕様に準拠するものです。標準規定構を柔軟に運用しているにすぎません。それでは最後にShowResults関数の実装コードを見てみましょう。


void ITProToyota::ITProToyotaInternal::ShowResults()

{

  cout << "\nThe Start ..." << endl;

  cout << "Empy? => " << buff.empty() << endl;

  cout << buff << "\nsize: " << (unsigned int) buff.size() << endl;

  cout << buff << "\nlength: " << (unsigned int) buff.length() << endl;

  cout << buff << "\ncapacity: " << (unsigned int) buff.capacity() << endl;

  cout << buff << "\nmax_size: " << (unsigned int) buff.max_size() << endl;

  cout <<'\n' << endl;

  printf( "C-style string-> ");;

  printf(buff.c_str());

  buff.clear();

  cout << "\nThe End ..." << endl;

  cout <<'\n' << endl;

}

 プログラミング初心者の方は,void ITProToyota::ITProToyotaInternal::ShowResults()というコードを不思議な気持ちで眺めているかもしれません。ここでは,「そういうものか」程度の軽い気持ちで眺めるだけにしてください。学習を進めれば習得できる知識です。Bjarne Stroustrup氏は,プログラミング初心者はコードの詳細を理解しようとするのではなく,大まかな概念(考え方)を把握するようにアドバイスしています。この実装コードでは,次のようなコードが使われています。


buff.size()



buff.length()



buff.capacity()



buff.max_size()

 buffは文字列クラスのインスタンスですから,次のような表現が可能になります。

文字列クラスのインスタンスであるbuffは,自分の大きさを知っている

 この表現をより一般化すれば,次のような文章が完成します。

クラスは自分を知っている

 プログラム内でクラスを使用するとなぜセキュリティが向上するのでしょうか? 回答は簡単ですね。自分を知っている人に仕事を依頼すれば,失敗リスクは少なくなります。これと同じで,自分自身を知っているクラスに文字列処理を依頼すれば,その処理はかなりの確率で正常に実行されることになります。

 私は3年ほど前このような連載を担当しました。クラスを使用すれば,セキュリティが向上するだけではなく,プログラミング学習も楽しくなります。Bjarne Stroustrup氏はこのようなクラスの利点を評価し,C++学習者に標準ライブラリを学ぶことを薦めていると思います。

図1●サンプル・プログラムの実行結果
図2●コンパイラ・オプションを無効にしてもビルド・エラーは出ない
 それでは,サンプル・プログラムの実行結果を紹介しておきます(図1[拡大表示])。出力情報内のsize:などの値を見ると分かるように,stringクラスはその内部で処理する文字列の大きさを管理していますね。

 なお,今回のサンプル・プログラムは標準C++仕様に準拠していますから,Microsoft拡張機能を使用する必要はありません。つまり,図2[拡大表示]のようにコンパイラ・オプションを無効にしてもビルド・エラーは一切出ません。Visual Studio .NETはあくまでも統合開発環境ですから,自分のニーズに応じて適切に活用したいものです。

 今回紹介したサンプル・プロジェクトはこちらからダウンロードできますから,お時間のあるときにぜひご自分で試してください。buff.size()とbuff.length()などは一般にメソッドと呼ばれていますが,機能詳細や機能上の相違点が気になる方は,ぜひデバッガなどを起動して,調査してみるとよいでしょう。情報をいつまでも受身で受け取っているだけでは進歩しません。1行でも1文字でも自分の手で改善してみてください。

今回のまとめ

  • プログラム内でクラスを適切に使用すれば,セキュリティは向上する
  • プログラム内でクラスを適切に使用すれば,プログラミング学習が楽しくなる
  • 統合開発環境はニーズに応じて活用することが大切である

 今回は以上で終了です。次回またお会いいたしましょう。ごきげんよう!