[Work/Class/C++11の場合/CPPBasic]

C++11の場合 - ポインタとmalloc,freeによる動的配列確保

ポインタ

ポインタとは,メモリ内に存在するインスタンスを指し示す変数である.Javaでは「インスタンスを保持している変数」と説明していたが,C言語,C++言語ではこれを「ポインタ」と呼称する.

Javaはintやfloatなどのプリミティブ型の変数以外,つまりインスタンスを保持している変数は全て「ポインタ」なのだが,Cではポインタではない場合もある.しかし実践的に使っていくと全てをポインタにした方が(Javaとの混同もなく)わかりやすい.

ポインタはint* int_pointer;のように宣言する.(実際にはint*のポインタを単体で使うことはなく,後述の動的配列やオブジェクトインスタンスを保持する方法として使われる.(つまり使い方はJavaの変数と一緒)

malloc

今までの授業内容で,C言語の配列は,通常の変数と同じく

int array[10];

のように宣言していた.この場合,要素数である「定数10」はプログラムコンパイル時に決定されるため,定数の代わりに変数を使って要素数を増やしたり減らしたりすることはできない.(これを配列の静的確保と呼ぶ)

そこで「動的確保」という手法を使うと,変数により要素数を決めることができる.

int* array = (int*)malloc(sizeof(int) * 要素数が入った変数);

int変数で定められている容量(計算機やOSによって違う)をsizeof()関数により呼び出して,それと要素数を掛け合わせて総容量を算出し,malloc()関数の引数に入れ,malloc関数の戻り値はポインタ,つまり配列のインスタンスであるので,それをポインタ変数で保持する,という流れである.

動的確保した配列は,通常の配列と同じく,

for(int i=0; i<要素数; i++){
    array[i] += i;
}

というように,添字でアクセスすることができる.

free

Javaは,確保した配列やインスタンスは,必要がなくなったら勝手に解放されるが(自動メモリ解放の仕組みをガベージコレクションと呼ぶ),CやC++では,自分で解放しなければならない.

一応プログラム終了後には自動的に解放されるようにOSが操作を行うが,例えば展示作品などずっとプログラムが走り続けている状態では,解放しなければいつまでも残り続けて最終的にメモリが足りなくなり,クラッシュする.メモリが残り続けることを「メモリリーク」と呼称する.クラッシュを防ぐために必要がなくなったメモリを解放する必要がある.

解放にはfree()関数使う.

free(配列インスタンスを保持しているポインタ);

という書法で解放できる.

気をつけるべきは「インスタンスを保持しているポインタをfreeしないうちに,中身を書き換えてしまわないこと」である.Javaでは,使わなくなったインスタンスは放っておけばいつかは解放されるが,CやC++では自動的に解放されない.そのため,インスタンスを保持しているポインタの中身を書き換えてしまうと,インスタンスがメモリ中のどこにあるのかがわからなくなってしまい,メモリリークが発生する.

設置型メディアアートインスタレーションでメモリリークが発生すると,頻繁にメモリがいっぱいになりクラッシュを引き起こす.OpenFrameworksなどの取り扱いでは,mallocとfreeに特に気をつける必要がある.

コード例

#include <stdlib.h>
#include <stdio.h>

int main(){
  int array_size = 20; //変数に要素数を入れる
  // mallocを使って配列を動的確保
  float* array = (float*)malloc(sizeof(float) * array_size);
  
  for(int i=0; i<array_size; i++){
    // 0〜定数RAND_MAXまでのランダムな値を発生させるrand()関数
    // rand関数から出てきた値を定数RAND_MAXで割ると (floatにキャストしていることに注意)
    // 0.0〜1.0までのランダムな数字が得られる.
    array[i] = (float)rand() / (float)RAND_MAX;
    printf("Random value: %f\n", array[i]);
  }

  float sum = 0.0;
  for(int i=0; i<array_size; i++){
    sum += array[i];
  }
  printf("Sum of 20 random values is : %f\n", sum);

  // 必ずfreeする癖をつける
  free(array);

  return 0; // C言語,C++言語では「正常終了した」という意味で0を返す.
}

ポインタの配列

ポインタは「Javaでいうところのインスタンスを保持する変数」と説明したが,インスタンスの配列を作ることができるように,ポインタの配列を作ることもできる.malloc()関数の引数であるsizeof()関数の引数に,ポインタを与えればよい.

次項で出てくるクラスのインスタンスをポインタで保持する時に,配列の動的確保をする場合はこのポインタの配列が必要になる.

この時mallocから返されるポインタは「ポインタのポインタ」となることに注意.

応用例としては,プリミティブ型に適用すると,2次元配列と同じように扱うことができる.

#include <stdlib.h>
#include <stdio.h>

int main(){
  int each_array_size = 20; //変数に要素数を入れる
  int all_array_size = 10;
  // mallocを使って「ポインタの配列」を動的確保
  // 「ポインタのポインタ」になるので,「**」と二つ必要
  float** array = (float**)malloc(sizeof(float*) * all_array_size);

  for(int i=0; i<all_array_size; i++){
    // ポインタの配列の各要素に,mallocをしてfloatの配列を確保する
    // これで2次元配列ができる
    array[i] = (float*)malloc(sizeof(float) * each_array_size);
    for(int j=0; j<each_array_size; j++){
      // 各要素にランダムで生成した0.0〜1.0の値を入れる
      array[i][j] = (float)rand() / (float)RAND_MAX;
      printf("%f, ", array[i][j]);
    }
    printf("\n");
  }

  float sum = 0.0;
  for(int i=0; i<all_array_size; i++){
    for(int j=0; j<each_array_size; j++){
      sum += array[i][j];
    }
  }
  printf("Sum of 10 * 20 random values is : %f\n", sum);

  // freeする時には「中身」からfreeして,最後に外側をfreeする
  // そうしないと迷子になる
  for(int i=0; i<all_array_size; i++){
    // まず内側をfreeして
    free(array[i]);
  }
  // 最後に外側をfreeする
  free(array);

  return 0;
}