2018年5月
« 4月   6月 »
 12345
6789101112
13141516171819
20212223242526
2728293031  

最近の投稿(電子情報)

アーカイブ

カテゴリー

mallocとfree

前回の講義での、「長いかもしれない名前」を覚える処理は、最悪の場合をどう扱うかでメモリのムダが発生する。
ここで、前回講義で説明した、大きな配列を少しづつ分けて使う処理を考える。

大きな配列を少しづつ貸し出す処理

char str[ 10000 ] ;
char* sp = str ;
char entry( char* s ) {
   char* ret = sp ;
   strcpy( sp , s ) ;
   sp += strlen( s ) + 1 ;
   return ret ;
}
int main() {
   char* names[ 10 ] ;
   names[ 0 ] = entry( "saitoh" ) ;
   names[ 1 ] = entry( "tomoko" ) ;
   return 0 ;
}
// str[] s a i t o h ¥0 t o m o k o ¥0
//       ↑             ↑
//     names[0]        names[1]

このプログラムでは、貸し出す度に、sp のポインタを後ろに移動していく。

スタック

この貸し出す度に、末尾の場所をずらす方式にスタックがある。

int stack[ 100 ] ;
int* sp = stack ;
void push( int x ) {
   *sp = x ;    // 1行で書くなら
   sp++ ;       // *sp++ = x ;
}
int pop() {
   sp-- ;
   return *sp ; // return *(--sp) ;
}
int main() {
   push( 1 ) ;
   push( 2 ) ;
   push( 3 ) ;
   printf( "%d¥n" , pop() ) ;
   printf( "%d¥n" , pop() ) ;
   printf( "%d¥n" , pop() ) ;
   return 0 ;
}


スタックは、最後に保存したデータを最初に取り出せる(Last In First Out)から、LIFO とも呼ばれる。
このデータ管理方法は、最後に呼び出した関数が最初に終了することから、関数の戻り番地の保存や、最後に確保した局所変数が最初に不要となることから、局所変数の管理に利用されている。

スタック上の動的メモリ確保 alloca

最初のプログラム例のような方法で、スタック上にメモリを確保する関数として、alloca() がある。

// C言語では、配列サイズに変数を使えない。
int size = ... ;
int array[ size ] ;

// これを alloca で書くと...
int size = ... ;
int* array ;
array = (int*)alloca( sizeof( int ) * size ) ;
if ( array != NULL ) { // スタック溢れはNULLで検知できないか...
   :
   // array[]を使った処理
   :
}

ただし、alloca はスタック領域を使用するため、数MBといった巨大なデータを確保するのには使えない。
この領域は、スタックのように末尾だけを覚えておけばいいので、管理が簡単である。一方で、関数の局所変数として確保して、「この場所を使ってこの計算してね」的な使い方をしなければならない。「この場所を返すから後は自由に使って」的な使い方はできない。

malloc()とfree()

alloca を使うような処理は、スタックのように「最後に確保したものが最初に不要となる」という状況でしか使えない。
確保した領域が不要となる順序が判らない場合には、malloc() を使う必要がある。

ポインタ = malloc( 確保するbyte数 ) ;
   メモリ不足で malloc に失敗したら NULL を返す。
free( ポインタ ) ;
   確保したメモリ領域を解放する。
   解放されたメモリは、mallocで再利用してくれる。

最初に説明した、入力された文字を次々と保存する処理を malloc で記述すると以下のようになる。

char* names[ 100 ] ;
char buff[ 1000 ] ;
int size ;

// データ入力
for( size = 0 ; size < 100 ; size++ ) {
   fgets( buff , sizeof( buff ) , stdin ) ;
   names[ size ] = (char*)malloc( strlen( buff ) + 1 ) ;
   if ( names[ size ] == NULL )
      break ;
   strcpy( names[ size ] , buff ) ;
}
// データを使う処理
for( int i = 0 ; i < size ; i++ ) {
   // names[] を使う処理...
   printf( "%s" , names[ i ] ) ;
}
// データ領域をfreeで解放
for( int i = 0 ; i < size ; i++ )
   free( names[ i ] ) ;

malloc() で確保したメモリ領域は、free() で解放しない場合、メモリ領域は使われないムダな領域が蓄積して、最終的にはメモリ不足で止まるかもしれない。また、大量のムダなメモリ領域ができると、仮想メモリが使われ処理速度の低下が発生するかもしれない。
このような、解放されないメモリ領域が発生することは、メモリーリークと呼ばれる。

確保したメモリは、プロセス毎に管理されているので、長時間動きっぱなしのプログラムでメモリリークが発生すると問題となる。
ただし、プロセス終了と共に確保されているメモリはOSに回収されるので、処理が終わってすぐにプロセス自体も終わるのであれば、free() を書き忘れても問題は発生しない。