ホーム » 「ヒープメモリ」タグがついた投稿

タグアーカイブ: ヒープメモリ

2022年8月
 123456
78910111213
14151617181920
21222324252627
28293031  

最新の投稿(電子情報)

アーカイブ

カテゴリー

malloc()とfree()

前回の授業で説明した、alloca() は、スタック領域にデーターを覚えるので、allocaを実行した関数の終了ともに配列領域が消えてしまう。しかし、関数が終わってもそのデータを使いたいといった場合には、malloc()+free()を使う必要がある。

malloc()とfree()

malloc() は、動的(ヒープ領域)にメモリを確保する命令で、データを保存したい時に malloc() を実行し、不要になった時に free() を実行する。

malloc() では、alloca() と同じように、格納したいデータの byte 数を指定する。また、malloc() は、確保したメモリ領域の先頭を返すが、ヒープメモリが残っていない場合 NULL ポインタを返す。処理が終わってデータ領域をもう使わなくなったら、free() で解放する必要がある。

基本的には、確保したメモリ領域を使い終わった後 free() を実行しないと、再利用できないメモリ領域が残ってしまう。こういう処理を繰り返すと、次第にメモリを食いつぶし、仮想メモリ機能によりハードディスクの読み書きで性能が低下したり、最終的にOSが正しく動けなくなる可能性もある。こういった free() 忘れはメモリーリークと呼ばれ、malloc(),free()に慣れない初心者プログラマーによく見られる。

ただし、ヒープメモリ全体は、プロセスの起動と共に確保され(不足すればOSから追加でメモリを分けてもらうこともできる)、プログラムの終了と同時にOSに返却される。このため、malloc()と処理のあとすぐにプロセスが終了するようなプログラムであれば、free() を忘れても問題はない。授業では、メモリーリークによる重大な問題を理解してもらうため、原則 free() は明記する。

文字列を保存する場合

#include <stdlib.h>
char* names[ 10 ] ;
char  buff[ 1000 ] ;

// 名前を10件読み込む
void inputs() {
   for( int i = 0 ; i < 10 ; i++ ) {
      if ( fgets( buff , sizeof( buff ) , stdin ) != NULL ) {
         names[ i ] = (char*)malloc( strlen(buff)+1 ) ;
         if ( names[ i ] != NULL )
            strcpy( names[ i ] , buff ) ;
      }
   }
}
// 名前を出力する
void prints() {
   for( int i = 0 ; i < 10 ; i++ )
      printf( "%s" , names[ i ] ) ;
}
void main() {
   // 文字列の入力&出力
   inputs() ;
   prints() ;
   // 使い終わったら、free() で解放
   for( int i = 0 ; i < 10 ; i++ )
      free( names[ i ] ) ;
}

文字列を保存する場合には、上記の names[i] への代入のような malloc() と strcpy() を組み合わせて使うことが多い。しかし、この一連の処理の関数として、strdup() がある。基本的には、以下のような機能である。

char* strdup( char* s ) {
   char* p ;
   if ( (p = (char*)malloc( strlen(s)+1 )) != NULL )
      strcpy( p , s ) ;
   return p ;
}

また、入力した文字列をポインタで保存する場合、以下のようなプログラムを書いてしまいがちであるが、図に示すような状態になることから、別領域にコピーする必要がある。

char  buff[ 1000 ] ;
char* name[10] ;
for( int i = 0 ; i < 10 ; i++ ) {
   if ( fgets( buff , sizeof(buff) , stdin ) != NULL )
      name = buff ;
      // ここは、name = strdup( buff ) ; と書くべき。
}

配列に保存する場合

基本的な型の任意サイズの配列を作りたい場合には、malloc() で一括してデータの領域を作成し、その先頭アドレスを用いて配列として扱う。

#include <stdlib.h>
void main() {
   int size ;
   int* array ;
   // 処理するデータ件数を入力
   scanf( "%d" , &size ) ;

   // 整数配列を作る
   if ( (array = (int*)malloc( sizeof(int) * size )) != NULL ) {
      int i ;
      for( i = 0 ; i < size ; i++ )
         array[i] = i*i ; // あんまり意味がないけど
      for( i = 0 ; i < size ; i++ )
         printf( "%d¥n" , array[i] ) ;

      // mallocしたら必ずfree
      free( array ) ;
   }
}

構造体の配列

同じように、任意サイズの構造体(ここではstruct Complex)の配列を作りたいのであれば、mallocの引数のサイズに「sizeof( struct Complex ) * データ件数」を指定すればいい。

後半の array2[] では、ポインタの配列を使った例を示す。この例では、1つの構造体毎に1つのmallocでメモリを確保している。

#include <stdlib.h>
struct Complex {
   double re , im ;
} ;

// 指定した場所にComplexを読み込む。
int input_Complex( struct Complex* p ) {
   return scanf( "%lf %lf" , &(p->re) , &(p->re) ) == 2 ;
}

// 指定したComplexを出力
void print_Complex( struct Complex* p ) {
   printf( "%lf+j%lf¥n" , p->re , p->im ) ;
}
void main() {
   int size ;
   struct Complex* array ;
   struct Complex** array2 ;

   // 処理する件数を入力
   scanf( "%d" , &size ) ;
   // 配列を確保して、データの入力&出力
   if ( (array = (struct Complex*)malloc(
                    sizeof(struct Complex) * size )) != NULL ) {
      int i ;
      for( i = 0 ; i < size ; i++ )
         if ( !input_Complex( &array[i] ) )
            break ;
      for( i = 0 ; i < size ; i++ )
         print_Complex( &array[i] ) ;
         // or printf( "%lf + j%lf\n" ,
         //            array[ i ].re , array[ i ].im ) ;

      // mallocしたら必ずfree
      free( array ) ;
   }

   // ポインタの配列で保存
   if ( (array2 = (struct Complex**)malloc(
                     sizeof(struct Complex*) * size)) != NULL ) {
      int i ;
      for( i = 0 ; i < size ; i++ ) {
         // 各データごとにmalloc()
         array2[ i ] = (struct Complex*)malloc( sizeof( struct Complex ) ) ;
         if ( array2[ i ] != NULL ) {
            array2[ i ]->re = (double)i ;
            array2[ i ]->im = (double)i ;
         }
      }
      // 保存した構造体をすべて表示
      for( i = 0 ; i < size ; i++ )
         print_Complex( array[ i ] ) ;
      // 各データごとに free
      for( i = 0 ; i < size ; i++ )
         free( array[ i ] ) ;
      // ポインタの配列を free
      free( array2 ) ;
   }
}

(おまけ)C++の場合

C言語における malloc() + free () でのプログラミングは、mallocの結果を型キャストしたりするので、間違ったコーディングの可能性がある。このため、C++ では、new 演算子, delete 演算子というものが導入されている。

// 同じ処理をC++で書いたら
// 文字列の保存
char  str[] = "ABCDE" ;
char* pc = new char[ strlen( str ) + 1 ] ;
strcpy( pc , str ) ;
// pcを使った処理
delete[] pc ;  // new型[]を使ったらdelete[]

// int配列の保存
int  data[] = { 11 , 22 , 33 } ;
int* pi ;
pi = new int[ 3 ] ;
for( int i = 0 ; i < 3 ; i++ )
   pi[ i ] = data[ i ] ;
// piを使った処理
delete[] pi ;

// 構造体の保存
struct Person {
   char name[ 10 ] ;
   int  age ;
} ;
Person* pPsn ;
pPsn = new Person ;
strcpy( pPsn->name , "t-saitoh" ) ;
pPsn->age = 55 ;
// pPsnを使った処理
delete pPsn ; // new型ならdelete

注意すべき点は、malloc+freeとの違いは、mallocがメモリ確保に失敗した時の処理の書き方。返り値のNULLをチェックする方法は、呼び出し側ですべてでNULLの場合を想定した書き方が必要になり、処理が煩雑となる。C++の new 演算子は、メモリ確保に失敗すると、例外 bad_alloc を投げてくるので、try-catch 文で処理を書く。(上記例はtry-catchは省略)

様々なメモリ確保

前回の授業で説明していたような、必要に応じて確保するメモリは、動的メモリと呼ばれる。

動的メモリも、局所変数やalloca()を用いたスタック領域と、malloc()とfree()を使うヒープメモリ領域に分類される。

strdup

前回の文字列の確保の説明では、malloc()とstrcpy()をあわせて実行していたが、C言語ではこういった処理が多いので、専用の関数 strdup() がある。

char str[] = "abcdefg" ;
char*pc ;
if ( (pc = (char*)malloc( strlen( str ) + 1 )) != NULL ) {
   strcpy( pc , str ) ;
}
// おなじことを strdup では...
pc = strdup( str ) ;

様々なメモリ確保

自分で定義した構造体を、malloc で領域確保しながら使う場合、1次元配列や2次元配列を作る場合、色々な確保の方法がある。

// 複素数を例に
struct Complex {
   double re ;
   double im ;
} ;
// 基本
struct Complex a ;
a.re = 1.0 ;
a.im = 2.0 ;
// ポインタで確保
struct Complex* b ;
b = (struct Complex*)malloc( sizeof( struct Complex ) ) ;
if ( b != NULL ) {
   b->re = 1.0 ;
   b->im = 2.0 ;
}
// 一次元配列
struct Complex c[ 2 ] ;  // 通常の使い方
c[0].re = 2.0 ;
c[0].im = 3.0 ;
c[1].re = 4.0 ;
c[1].im = 5.0 ;
// 一次元配列を動的に確保
struct Complex* d ;      // Complexの配列
d = (struct Complex*)malloc( sizeof( struct Complex ) * 2 ) ;
if ( d != NULL ) {
    d[0].re = 2.0 ; d[0].im = 3.0 ;
    d[1].re = 4.0 ; d[1].im = 5.0 ;
}
// 一次元のポインタ配列
struct Complex* e[ 2 ] ; // Complexのポインタの配列
e[0] = (struct Complex*)malloc( sizeof( struct Complex ) ) ;
if ( e[0] != NULL ) {
    e[0]->re = 2.0 ; e[0]->im = 3.0 ;
}
e[1] = (struct Complex*)malloc( sizeof( struct Complex ) ) ;
if ( e[1] != NULL ) {
    e[1]->re = 4.0 ; e[1]->im = 5.0 ;
}

C++での new, delete 演算子

複雑なデータ構造のプログラムを作成する場合には、このような malloc() , free() をよく使うが煩雑であるため、C++ではこれらをすっきりと記述するために、new 演算子、delete 演算子があり、それぞれ malloc(), free() に相当する。

// 単独
Complex* b = new Complex ;
b->re = 1.0 ;
b->im = 2.0 ;
delete b ;
// 配列
Complex* d = new Complex[2] ;
d[0].re = 2.0 ;
d[0].im = 3.0 ;
d[1].re = 4.0 ;
d[1].im = 5.0 ;
delete[] d ;  // 配列のdeleteには[]が必要
// ポインタの配列
Complex* e[2] ;
e[0] = new Complex ;
e[0]->re = 2.0 ;
e[0]->im = 3.0 ;
e[1] = new Complex ;
e[1]->re = 4.0 ;
e[1]->im = 5.0 ;
delete e[0] ;
delete e[1] ;

2次元配列

2次元配列の扱いでも、注意が必要。

int cs = 何らかの値 ; // データ列数
int rs = 何らかの値 ; // データ行数
int a[ rs ][ cs ] ;  // C言語ではエラー
a[ y ][ x ] = 123 ;

// 1次元配列を2次元配列のように使う
int* b ;
b = (int*)malloc( sizeof( int ) * rs * cs ) ;
b[ y * cs + x ] = 123 ;  // b[ y ][ x ] への代入

// 配列へのポインタの配列
int** c ;
c = (int**)malloc( sizeof( int* ) * rs ) ;  // NULLチェック省略
c[0] = (int*)malloc( sizeof( int ) * cs ) ;
c[1] = (int*)malloc( sizeof( int ) * cs ) ;
:
c[ y ][ x ] = 123 ;

レポート課題

メモリの動的確保の理解のために、自分の理解度に応じて以下のプログラムのいずれかを作成せよ。
ただし、プログラム作成時には、配列サイズは未定で、プログラム起動時に配列サイズを入力するものとする。

  • 固定長の名前で、人数が不明。
  • 長い名前かもしれない名前で、人数も不明
  • 複素数のデータで、データ件数が不明。
  • 名前と電話番号のデータで、データ件数が不明。

このような状況で、データを入力し、検索などの処理を通して自分のプログラムが正しく動くことを検証せよ。
レポートには、プログラムリスト、プログラムの説明、動作確認した結果、考察を記載すること。

C++のvectorクラスを使ったら

// C++であればvectorクラスを使えば配列なんて簡単
#include <vector>
int main() {
   // 1次元配列
   std::vector<int> a( 10 ) ;
   for( int i = 0 ; i < 10 ; i++ )
      a[ i ] = i ;
   // 2次元配列
   std::vector< std::vector<int> > b( 9 , std::vector<int>(9) ) ;
   //                           ↑ ここの空白は重要
   for( int i = 0 ; i < 9 ; i++ ) {    // ">>" と書くとシフト演算子
      for( int j = 0 ; j < 9 ; j++ ) { // "> >" と書くと2つの">"
         b[i][j] = (i+1) * (j+1) ;
      }
   }
   return 0 ;
}