ホーム » 「malloc」タグがついた投稿

タグアーカイブ: malloc

2018年12月
« 11月    
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

最近の投稿(電子情報)

アーカイブ

カテゴリー

様々なメモリ確保

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

動的メモリも、局所変数や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 ;
}

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() を書き忘れても問題は発生しない。