二次元配列の動的確保

Copyright(C) 14May2003 coskx

大きな二次元配列や、プログラムが起動するまで、大きさの決まらない二次元配列の取り方を解説する。

実際には二次元配列ではなく、動的に確保された一次元配列の先頭アドレスを格納するポインタの配列を用いて、二次元配列のように扱えるようにするという意味となる。

要点 ポインタを用いて,一度動的確保を行なうと,最初から宣言された2次元配列のように扱える。

1.ポインタ変数の配列を用いたint型二次元配列確保の例

List 1 二次元配列の動的確保のプログラム

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

static int **array2d=NULL; /*この変数は配列が確保された後,二次元配列として扱えるようになる*/
static int arrayrow=0,arraycolumn=0;

void closeIntArray(void)
{
    int i;
    for (i=0;i<arrayrow;i++) {
        free(array2d[i]);
    }
    if (0<arrayrow) free(array2d);
    arrayrow=0;
    arraycolumn=0;
}

/*row×column(縦×横)のint型二次元配列をつくる*/
void openIntArray(int row,int column)
{
    int i,j;
    closeIntArray();
    array2d=(int **)malloc(sizeof(int *)*row);
    if (array2d==NULL) {
        printf("** error ----  out of memory  **\n");
        exit(1);
    }
    for (i=0;i<row;i++) {
        array2d[i]=(int *)malloc(sizeof(int)*column);
        if (array2d[i]==NULL) {
            printf("** error ----  out of memory  **\n");
            exit(1);
        }
        for (j=0;j<column;j++) array2d[i][j]=0;
    }
    arrayrow=row;
    arraycolumn=column;
}

int main()
{
    int i,j;
    openIntArray(4,7);
    /*ここより先ではarray2dはint型の二次元配列として使える*/

    for (i=0;i<4;i++) for (j=0;j<7;j++) array2d[i][j]=i*10+j;
    for (i=0;i<4;i++) {
        for (j=0;j<7;j++) printf("%4d",array2d[i][j]);
        printf("\n");
    }
    closeIntArray();
    /*確保した二次元配列の開放 (必ず行なう習慣を)*/
    return 0;
}

実行結果
0   1   2   3   4   5   6
10  11  12  13  14  15  16
20  21  22  23  24  25  26
30  31  32  33  34  35  36

int **array2d:
array2dは,「「int型変数のアドレスを格納するポインタ変数」のアドレス」を格納するポインタ変数の意味である。
あるいは **array2は「「array2に格納されている値をアドレスとするポインタ変数」に格納されているアドレス」に格納されているint型変数の値を表すと言っても良い.
この変数int **array2dは次のように使われる。

int型変数のアドレスを格納するポインタ変数を指すポインタ変数 array2d

アドレスa

int型変数を指すポインタ変数 名前はないがアドレスaに存在

アドレスb

int型変数 名前はないがアドレスbに存在

整数値

malloc(sizeof(int *)*row):
int型のアドレスを格納するポインタのサイズでrow個のメモリを確保してその先頭番地を返す関数呼び出し

(int **)malloc(sizeof(int *)*row):
先頭番地を受け取ったらそれはポインタ変数の配列なので、ポインタ変数のアドレスをしまうポインタ変数型へキャスト

malloc(sizeof(int)*column):
int型のサイズでcolumn個のメモリを確保してその先頭番地を返す関数呼び出し

メモリ確保終了時の擬似二次元配列は次のような構造になっている

ポインタ変数を指すポインタ変数 array2d

アドレスa

int型変数を指すポインタ変数の配列 名前はないがアドレスaに存在
この配列はmallocで確保される

要素番号

0

1

2

3

アドレスb

アドレスc

アドレスd

アドレスe

int型変数配列 名前はないがアドレスbに存在
この配列はforループ中のmallocで確保される

要素番号

0

1

2

3

4

5

6

未定

未定

未定

未定

未定

未定

未定

int型変数配列 名前はないがアドレスcに存在
この配列はforループ中のmallocで確保される

要素番号

0

1

2

3

4

5

6

未定

未定

未定

未定

未定

未定

未定

int型変数配列 名前はないがアドレスdに存在
この配列はforループ中のmallocで確保される

要素番号

0

1

2

3

4

5

6

未定

未定

未定

未定

未定

未定

未定

int型変数配列 名前はないがアドレスeに存在
この配列はforループ中のmallocで確保される

要素番号

0

1

2

3

4

5

6

未定

未定

未定

未定

未定

未定

未定

2.ポインタ変数の配列を用いた汎用型二次元配列確保の例

次の例は,いろいろな型の二次元配列の動的確保のプログラム例である。関数openArray(),関数closeArray()は読みにくいので,最初はこれらの関数の中身を理解しないで使ってみる方がよい。main()などからいろいろな型の変数の二次元配列(動的確保)を使う方法をこのプログラムの関数main()を見て修得しよう。使い方を理解すれば細かいことは考えなくても使える。構造体の二次元配列も作れるはずである。
次の段階として,関数openArray(),関数closeArray()の作り方を理解しないと気持ち悪いと考えたら,じっくりと理解しよう。

List 2 二次元配列の動的確保のプログラム

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

/*row(縦)の指定された二次元配列を解放する*/
void closeArray(void **array,int row)
{
    int i;
    for (i=0;i<row;i++) {
        free(array[i]);
    }
    if (0<row) free(array);
}

/*row×column(縦×横)個の二次元配列をつくる*/
/*ただし1要素あたりのバイト数はsizeofVariableである*/
void **openArray(int row,int column,int sizeofVariable)
{
    int i,j;
    void **array=malloc(sizeof(void *)*row);
    if (array==NULL) {
        printf("** error ----  out of memory  **\n");
        exit(1);
    }
    for (i=0;i<row;i++) {
        array[i]=malloc(sizeofVariable*column);
        if (array[i]==NULL) {
            printf("** error ----  out of memory  **\n");
            exit(1);
        }
        for (j=0;j<sizeofVariable*column;j++) ((char *)array[i])[j]=0;
    }
    return array;
}

int main()
{
    int **array2d=NULL; /*この変数は配列が確保された後,二次元配列として扱えるようになる*/
    double **array2dd=NULL; /*この変数は配列が確保された後,二次元配列として扱えるようになる*/
    int i,j;
    array2d=(int **)openArray(4,7,sizeof(int));
    /*ここより先ではarray2dは4×7のint型二次元配列として使える*/
    for (i=0;i<4;i++) for (j=0;j<7;j++) array2d[i][j]=i*10+j;
    for (i=0;i<4;i++) {
        for (j=0;j<7;j++) printf("%4d",array2d[i][j]);
        printf("\n");
    }
    closeArray(array2d,4);
    array2dd=(double **)openArray(6,4,sizeof(double));
    /*ここより先ではarray2ddは6×4のdouble型二次元配列として使える*/
    for (i=0;i<6;i++) for (j=0;j<4;j++) array2dd[i][j]=1.0/(i*10+j+1);
    for (i=0;i<6;i++) {
        for (j=0;j<4;j++) printf("%8.4f",array2dd[i][j]);
        printf("\n");
    }
    closeArray(array2dd,6);
    return 0;
}

実行結果
   0   1   2   3   4   5   6
  10  11  12  13  14  15  16
  20  21  22  23  24  25  26
  30  31  32  33  34  35  36
  1.0000  0.5000  0.3333  0.2500
  0.0909  0.0833  0.0769  0.0714
  0.0476  0.0455  0.0435  0.0417
  0.0323  0.0313  0.0303  0.0294
  0.0244  0.0238  0.0233  0.0227
  0.0196  0.0192  0.0189  0.0185

array2d=(int **)openArray(4,7,sizeof(int)):
「int型で4×7の配列を確保し,その配列の先頭アドレスを返してもらって,aray2dに保存」と読めばよい。
「int array2d[4][7];」が使えるようになったと思えばよい。

array2dd=(double **)openArray(6,4,sizeof(double)):
「double型で6×4の配列を確保し,その配列の先頭アドレスを返してもらって,aray2ddに保存」と読めばよい。
「double array2dd[6][4]」が使えるようになったと思えばよい。

「void型のポインタ」には,特定の型の変数のアドレスではなく,アドレスなら何でも保存できる便利なポインタ変数である。しかし,これはアドレスの保存用に使われるだけで,そのアドレスが指す値を参照したり書き換えるには別のポインタ変数に代入したり,キャストしなければならない。

もし次のような構造体の配列を20×8確保したいのなら次のようにするとよい。
typedef struct {
    int xxxxx;
    int yyyyy;
    char sssss[16];
} mystruct_t;

mystruct_t **structarray2d;

structarray2d==(mystruct_t **)openArray(20,8,sizeof(mystruct_t)):

3.行列とベクトルの例 (どうしてポインタが配列として扱えるのか)

行列やベクトルを変数として扱う時は,上記の方法を使うと行列やベクトルの次元を後から決まることができる。
次のプログラムは,どうしてメモリを確保したポインタが配列として扱えるかがわかる例である。

要点
ptrをポインタ変数(たとえばdouble *ptr)とすると
「*(ptr+3)」と「ptr[3]」はまったく同じ機械語に変換される。


驚くことに「*(3+ptr)」と「3[ptr]」もまったく同じ機械語になるので,

「*(ptr+3)」と「ptr[3]」と「*(3+ptr)」と「3[ptr]」

は同じ変数をさすことになる。

「*(ptr+3)」は「{ptr(アドレス値)+3×その型の変数の占有するバイト数}の指す変数}の意味
「ptr[3]」も「{ptr(アドレス値)+3×その型の変数の占有するバイト数}の指す変数}の意味
「*(3+ptr)」は「{3×その型の変数の占有するバイト数+ptr(アドレス値)}の指す変数}の意味
「3[ptr]」も「{3×その型の変数の占有するバイト数+ptr(アドレス値)}の指す変数}の意味
なのでこの4つは同じ変数をさすことになる。


同様にpptrをポインタのポインタ変数(たとえば double **pptr)とすると
「pptr[2][3]」と「*(*(ptr+2)+3)」は同じになる。

List 3 行列とベクトルのプログラム

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

double **createMatrix(int dimension)
{
    int i;
    double **mat;
    mat = (double**)malloc( dimension * sizeof(double*) ) ;
    for( i = 0 ; i < dimension ; i++ ) {
        mat[i] = (double*)malloc( dimension * sizeof(double) ) ;
    }
    return mat;
}

void deleteMatrix(double **matrix, int dimension)
{
    int i;
    for( i = 0 ; i < dimension ; i++ ) {
        free(matrix[i]);
    }
    free(matrix);
}

void deleteVector(double vector[])
{
    free(vector);
}

double *createVector(int dimension)
{
    int i;
    double *vec;
    vec = (double*)malloc( dimension * sizeof(double) ) ;
    return vec;
}

int main()
{
    double **mat;
    double *vec;
    int i,j;
    mat=createMatrix(3);
    vec=createVector(3);
    for (i=0;i<3;i++) for(j=0;j<3;j++) mat[i][j]=i+j;
    for (i=0;i<3;i++) vec[i]=(1+1)*10;
   
    printf("*(*(mat+1)+2) =%f\n",*(*(mat+1)+2));
    printf("*(2+*(mat+1)) =%f\n",*(2+*(mat+1)));
    printf("*(2+*(1+mat)) =%f\n",*(2+*(1+mat)));
   
    printf("mat[1][2]     =%f\n",mat[1][2]);
    printf("2[mat[1]]     =%f\n",2[mat[1]]);
    printf("2[1[mat]]     =%f\n",2[1[mat]]);
   
    printf("*(vec+2)=%f\n",*(vec+2));
    printf("*(2+vec)=%f\n",*(2+vec));
   
    printf("vec[2]  =%f\n",vec[2]);
    printf("2[vec]  =%f\n",2[vec]);
    return 0;
}

実行結果

*(*(mat+1)+2) =3.000000
*(2+*(mat+1)) =3.000000
*(2+*(1+mat)) =3.000000
mat[1][2]     =3.000000
2[mat[1]]     =3.000000
2[1[mat]]     =3.000000
*(vec+2)=20.000000
*(2+vec)=20.000000
vec[2]  =20.000000
2[vec]  =20.000000