第 10 回 動的なメモリ確保

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

10-1. 動的なメモリ確保

変数とメモリ

C 言語では情報処理は変数により行います。 変数はプログラム実行時にメモリ上に配置されます。 変数のメモリ上のアドレスは前述したように &変数名で得ることが できます。 一方、変数がメモリを使用する量は、それぞれの型により変わります。 sizeof 演算子は引数に型名、あるいは変数名を与えることで、占有するメモ リ量をバイト数で返します。

例10-1

以下のプログラムでは基本的な型とそのポインタ型のメモリ使用量を表示しま す。 なお、下記のように #define でカッコで引数を付けて定義すると、定義を呼 び出すときに定義内にある引数部分が引数に置き換えられます(マクロ機 能)。 また、#y のように引数に # を付けると、置き換え時に""(ダブルクォーテショ ンマーク)で括られます。


#include <stdio.h>
#define printsize(y) printf("%sのメモリ使用量は %d Byte\n",#y,sizeof(y));
int main(void){
  printsize(char);
  printsize(int);
  printsize(float);
  printsize(double);
  printsize(char*);
  printsize(int*);
  printsize(float*);
  printsize(double*);
  return 0;
}

例10-2

以下のプログラムでは構造型とそのポインタ型のメモリ使用量を表示しま す。


#include <stdio.h>
#define printsize(y) printf("%sのメモリ使用量は %d Byte\n",#y,sizeof(y));
typedef struct s1 {
  double d1;
} S1;
typedef struct s2 {
  double d1,d2;
} S2;
typedef struct s3 {
  double d1,d2,d3;
} S3;
int main(void){
  printsize(S1);
  printsize(S2);
  printsize(S3);
  printsize(S1*);
  printsize(S2*);
  printsize(S3*);
  return 0;
}

このように、それぞれの型の占有メモリ量は異なります。 しかし、ポインタ型の占有メモリ量は、どの型に対してもポインタはアドレス の計算のみなので、全て同じ量になります。

動的なメモリの確保

malloc 関数はプログラムの実行中にメモリを確保する関数です。 malloc 関数に必要なバイト数を引数にして呼ぶと、OS にメモリを確 保させ、確保したメモリの先頭の番地を値として返します。 一方、 free 関数はメモリの番地を引数とすると、そのメモリを OS に返します。 なお、利用できるメモリがない場合に malloc 関数が呼ばれた場合、メモリは 確保されず、 NULL が返されます。 NULL が返された時に返ってきた値(NULL)を用いて処理を続けるとプログラム が異常動作を起こし、最悪プログラムが暴走(操作不能な誤動作)を起こします。 そのため、malloc 関数の戻り値が NULL かどうかを検査する必要があります。

なお、 malloc 関数、 free 関数を使うには stdlib.h ヘッダファイルをイン クルードする必要があります。

通常の変数

malloc 関数で確保したメモリの領域を C 言語の変数として使うことができま す。 そのためには次の操作をします。

  1. 変数の型を定め、その型のポインタ型の変数をあらかじめ宣言しておきま す。
  2. malloc 関数でその型に必要なメモリ量を確保します。 malloc 関数は確保したメモリの先頭番地を (void *)型で返しま す。ポインタ型変数に代入すると、自動的に型変換されます。
  3. あとはそのポインタ型変数を使って malloc で確保した領域にアクセスします。

例10-3

malloc の使用例を次に示します。


#include <stdio.h>
#include <stdlib.h>
int main(void){
  char *c;
  int *i;
  double *d;
/* 実際に文字、整数、実数を入れられる場所はまだない */
  if((c=malloc(sizeof(char)))==NULL){
    fprintf(stderr,"メモリーが確保できませんでした\n");
    return 1;
  }
  if((i=malloc(sizeof(int)))==NULL){
    fprintf(stderr,"メモリーが確保できませんでした\n");
    free(c);
    return 1;
  }
  if((d=malloc(sizeof(double)))==NULL){
    fprintf(stderr,"メモリーが確保できませんでした\n");
    free(i);
    free(c);
    return 1;
  }
  *c='A';
  *i=10;
  *d=3.14;
  printf("%c %d %f\n",*c,*i,*d);
  free(d);
  free(i);
  free(c);
  return 0;
}

例10-4

malloc で構造体を作る例を示します。


#include <stdio.h>
#include <stdlib.h>
typedef struct st {
  char c;
  int i;
  double d;
} ST;
int main(void){
  ST *s;
  if((s=malloc(sizeof(ST)))==NULL){
    fprintf(stderr,"メモリーが確保できませんでした\n");
    return 1;
  }
  s->c='A';
  s->i=10;
  s->d=3.14;
  printf("%c %d %f\n",s->c,s->i,s->d);
  free(s);
  return 0;
}

ポインタ変数を宣言しても、通常は単独ではポインタ先の内容を変数として使 えません。 しかし、このように malloc 関数でメモリを確保することにより、変数領域を 確保することができます。

10-2. 配列として使用

malloc でメモリを確保する際に、型に必要なメモリ量の整数倍の領域を確保 すると、得られた領域を配列変数として使うことができます。 ポインタであっても、y[x] と配列の表現も使用できます。

例10-5


#include <stdio.h>
#include <stdlib.h>
#define N 50
int main(void){
  int x;
  int *y;
  if((y=malloc(N*sizeof(int)))==NULL){
    fprintf(stderr,"メモリを確保できませんでした\n");
    return 1;
  }
  for(x=0; x<N; x++){
    printf("%d ",y[x]=x);
  }
  printf("\n");
  for(x=0; x<N; x++){
    printf("%d ",++y[x]);
  }
  printf("\n");
  free(y);
  return 0;
}

二次元配列

二次元配列は一次元配列を複数管理する物と考えることができます。 一次元配列、つまり通常の配列は特定の型の連続領域ですので、その型に対す るポインタ型の変数で制御します。 つまり、 int 型の配列は int* 型の変数で制御します。 したがって、二次元配列はそのポインタ型の変数を複数持つような配列を用意 すれば可能です。 つまり、 int 型の二次元配列を作るには、まず int* に対するポインタ、つ まり int** 型の配列を作り、その 配列の要素に int の配列を指すポインタ(int* 型)を入れていけば良いことに なります。

例10-6

二次元配列の例を示します。 free する際、確保した領域を開放順に気を付けなければなりません。 一次元の後に二次元、つまり、管理される側を先にし、管理する側は後にしま す。 もし、逆に管理する側を先に free してしまうと、管理される側をアクセスで きなくなります。


#include <stdio.h>
#include <stdlib.h>
int main(void){
  int **mat;
  int i,j,n;
  printf("行列の大きさを入れてください ");
  scanf("%d",&n);
  if((mat=malloc(sizeof(int*)*n))==NULL){
    fprintf(stderr,"メモリーが確保できませんでした\n");
    return 1;
  }
  for(i=0;i<n;i++){
    if((mat[i]=malloc(sizeof(int)*n))==NULL){
      fprintf(stderr,"メモリーが確保できませんでした\n");
      for(j=0;j<i;j++){
	free(mat[j]);
      }
      free(mat);
      return 1;
    }
  }
  for(i=0;i<n;i++){
    for(j=0;j<n;j++){
      mat[i][j]=i*n+j;
    }
  }
  for(i=0;i<n;i++){
    for(j=0;j<n;j++){
      printf(" %d",mat[i][j]);
    }
    printf("\n");
  }
  for(j=0;j<n;j++){
    free(mat[j]);
  }
  free(mat);
  return 0;
}

10-3. 演習問題

演習10-1

次のようなプログラムを作りなさい。

  1. データ数を scanf で質問します
  2. 整数値をその数だけ入力します
  3. すると入力した数値を逆順に出力します

演習10-2

文字列二つを結合するプログラムを作りなさい。但し、結合した結果を入れる 文字配列は malloc で作りなさい。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void){
  char x[]="abcdef";
  char y[]="ghijklmn";
  char *z;
  int i,j;
  int lx,ly;
  lx=strlen(x);
  ly=strlen(y);
  printf("文字列 x=%s(長さ%d) と文字列 y=%s(長さ%d)を結合すると",x,lx,y,ly);
  /* z の領域を確保して、x と y をコピーする*/
  printf("z=%s(長さ%d)になる\n",z,strlen(z));
  return 0;
}

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科