第二回-04 : メモリとポインタ

本ページではstrtype クラスについて解説し、その過程でメモリとポインタについて学ぶ。


ソースコード全体は以下のようになる。
//クラス宣言部
#include <iostream>  // cout などを使うため
#include <cstring>   // strlen 関数、strcpy 関数を使うため
#include <cstdlib>   // malloc を使うため
using namespace std;

class strtype {
  char *p;
  int len;
public:
  strtype(char *ptr);  // コンストラクタ
  ~strtype(); // デストラクタ
  void show();
};
//クラス実現部
// 文字列オブジェクトを初期化する (コンストラクタ)
strtype::strtype(char *ptr){   // (※1)
  len = strlen(ptr);           // (※3)
  p = (char *) malloc(len+1);  // (※4)
  if(!p)                       // (※6)
  {
     cout << "メモリ割り当てエラー\n";
     exit(1);
  }
  strcpy(p,ptr);               // (※5)
}
// 文字列オブジェクトを破棄する際にメモリを解放する (デストラクタ)
strtype::~strtype()
{
  cout << "pを解放する\n";
  free(p);
}

void strtype::show()
{
  cout << p << " - 長さ: " << len;
  cout << "\n";
}
//クラス利用部
int main()
{
  strtype s1("This is a test."), s2("I like C++."); // (※2)

  // s1.show();  // 以下の三行は次のページ (第二回-05) で有効にすること
  // s2.show();
  // s2=s1; 

  s1.show();
  s2.show();
  return 0;
}

strtype クラス概要

まず、strtype クラスは文字列型 (string type) を表すクラスである。
通常、C 言語では文字列 (文字の集まり) は char 型の配列で表される。 例えば、「char s1[] = "Hello";」で char 型の配列 s1[] を定義すると、 以下の図のように配列が利用される。



文字列の終端には終端文字としてヌル文字 (¥0 または\0) が付加される。

strtype クラスは、このような文字列を扱うときに、配列を意識しなくて済むような クラスを目標としている。

メンバ変数「char *p;」は文字列の実体を指すポインタである。詳細は後述するが、上記解説の配列の代わりと思って良い。
メンバ変数「int len;」はセットした文字列の長さを格納する整数変数である。

strtype クラス実現部の解説

コンストラクタの解説

まず、コンストラクタの働きを解説する。

「strtype::strtype(char *ptr) // (※1)」 にあるように、strtype クラスのコンストラクタは引数 char *ptr をとる。
これは、正確には「文字列の先頭を指すポインタを引数としてとる」ということであるが、ひとまずここでは「文字列を引数としてもつ」と考えよう。
このコンストラクタは、クラス利用部において 「s1("This is a test.") // (※2)」などのように strtype クラスのオブジェクトを宣言したときに呼び出される。

次に、「len = strlen(ptr); // (※3)」 を考える。
これは、<cstring> で定義されている strlen (string length の意味) を呼び出しているが、
この関数により、len には文字列 ptr の長さが格納される。「This is a test.」であれば空白なども入れて len=15 となる。

次に「p = (char *) malloc(len+1); // (※4)」に着目しよう。
これは、len+1 バイトのメモリ領域を確保し (「This is a test.」なら、len+1=16 バイト)、ポインタ p がそのメモリ領域を 指すようにした文である。
これを下図を用いて解説しよう。いま main 関数内で「strtype s1("This is a test."); // (※2)」なる宣言によって strtype クラスのオブジェクト s1 が生成されたとする。



まずメモリ上に s1 が配置される。s1 は strtype クラスのオブジェクトであるから、上図の左のように、メンバ変数 p と len を持つ。
一般にポインタは「変数やオブジェクトを指し示すもの」として用いられるが、 この時点ではポインタ p がどこを指しているか不定である。
すなわち、初期化していないポインタは使用してはならない。

ここでコンストラクタが呼ばれ命令「p = (char *) malloc(len+1); // (※4)」が実行されると、メモリのヒープ領域に len+1=16 バイトの領域が確保 (allocate) され、
ポインタ p が確保された領域を指すようになる。これにより、確保された領域は p[0]~p[15] の配列として用いることができるようになる。

最後に 「strcpy(p,ptr); // (※5)」が実行され、コンストラクタは終了する。
この関数 strcpy (string copy の意味) は「ポインタ ptr が指す文字列をポインタ p の指すメモリ領域にコピーする」働きをする。

なお、len バイトではなく len+1 バイトを確保する理由は、図からわかるように 「ヌル文字 (¥0 または\0)」のためである。

また、「if(!p) // (※6)」の部分は「ポインタ p がヌルポインタであったら」、すなわち「メモリの確保に失敗したら」と解釈すると良い。

デストラクタの解説

次にデストラクタの解説をしよう。stack クラスの場合はデストラクタは必ずしも必要ではなかったが、strtype クラスではデストラクタの記述は必須である。
下図に従ってその解説をする。



いま、デストラクタが存在しない時に strtype クラスのオブジェクト s1 が破棄されたと考えよう。
このとき、s1 のメンバ p や len は破棄されるが、malloc によって確保された 16 バイトの領域は破棄されずにメモリ上に残ってしまう。
この領域を削除するには、あらかじめ「free(p);」という命令によって領域を解放しておかねばならない。malloc と free は対にして覚えよう

では、いつ、どこで free(p) を呼び出せば良いか?
答えは、オブジェクト s1 が破棄されるタイミング、すなわち、デストラクタ内である。
デストラクタ内で free(p) を呼び出すことで、s1 と 16 バイトの領域が破棄されることが保証される。

以上をもとに、strtype クラスの働きを理解せよ。



←第二回-03 : コンストラクタとデストラクタ第二回-05 : オブジェクトのコピー→

第二回演習用 Web ページへ

クラスから入る C++ へ戻る