C言語関係掲示板

過去ログ

No802 2次元配列のrealloc

[戻る] [ホームページ]
No.10033

2次元配列のrealloc
投稿者---bouz.lab(2003/10/26 10:54:48)


2次元配列のメモリーを確保した後に、その領域が足りなく
なって再確保するときの方法がよくわかりません。realloc
をするときのアドレスがあやしいのかな?ポインタについて
あまりわかってないってことですね。とりあえず書いたプロ
グラムを誰か見てください。よろしくお願いします。

main(){
	void TEST(int **list,int **f);
	int ** create_array(int row, int  rank);
	int ** create_array(int row, int  rank);
	int **f,**list;
	int i,j;

	FILE *fp1;

	if((fp1=fopen("testmalloc.txt","w"))==NULL){
		printf("%s file not open!\n");
		exit(0);
	}

	f=create_array(2,5);
    list=f;

	TEST(list,f);

	for(i=0;i<2;i++){
		for(j=0;j<10;j++){
			fprintf(fp1,"list[%d][%d]=%d\n",i,j,list[i][j]);
		}
	}
 
	free(f);
	fclose(fp1);
	return(0);
}

/*------------------メモリー確保-----------------*/
/*---------2次元配列 行row  列rank ------------*/

int ** create_array(int row, int  rank)
{
	int i;
	int **a1;
	int *ptr1;

	a1=(int **)malloc(row*sizeof(int *));
	if(a1==NULL){
		perror("malloc:");
		fprintf(stderr,"memory allocation error\n");
		exit(1);
	}

	ptr1=(int *)malloc(row*rank*sizeof(int));
	if(ptr1==NULL){
		perror("malloc:");
		fprintf(stderr,"memory allocation error\n");
		exit(1);
	}

	for(i=0;i<row;i++){
		a1[i]=ptr1+i*rank;
	}

	return(a1);
}
/*---------------メモリー再確保-----------------*/
/*----------------行row、列rank-----------------*/
int ** re_array(int **a2,int row, int rank)
{
	int i;
	int *ptr2;

	ptr2=(int *)realloc(*a2,row*rank*sizeof(int));
	if(ptr2==NULL){
		perror("remalloc:");
		fprintf(stderr,"memory reallocation error\n");
		exit(1);
	}

	for(i=0;i<row;i++){
		a2[i]=ptr2+i*rank;
	}

	return(a2);
}


/*------------------------------------------------------*/

void TEST(int **LIST,int **F)
{
	int i,j,p,n;
	p=0;
	n=0;
	for(i=0;i<2;i++){
		for(j=0;j<10;j++){  //はじめlist[2][5]の大きさしかなかったが
			LIST[i][j]=n; //list[2][10]必要になってlist[2][15]
			n++;     //再確保する。
			if(j==4+p*10){
				F=re_array(F,2,5+p*10);   
				LIST=F;
				p++;
			}
		}
	}
}


ねらった実行結果はlist[0][0]〜list[0][9]=0,1,2…9
         list[1][0]〜list[1][9]=10,11…19

No.10035

Re:2次元配列のrealloc
投稿者---YuO(2003/10/26 12:26:51)


>2次元配列のメモリーを確保した後に、その領域が足りなく
>なって再確保するときの方法がよくわかりません。

多次元配列だと,realloc自体が使いにくいと思います。
reallocが使えるのは最上位の配列要素の数の変更のみで,
他の配列要素は変更できません。

とりあえず書いてみると次のようになりました。
#非常に面倒くさい……。
動作チェックは行っていません。
こういう面倒くさいことは,C++を導入して
std::vector<std::vector<int> >
を使って処理してしまうのが楽です。

int ** alloc2D (int row, int col)
{
    int ** p1;
    int  * p2;
    int    i;

    /* rowまたはcolが0の時は空ポインタを返す */
    if (row == 0 || col == 0) return 0;

    /* メモリの確保 */
    p1 = malloc(row * sizeof(int *));
    p2 = malloc((row * col + 2) * sizeof(int));
    /* メモリ確保に失敗 */
    if (p1 == 0 || p2 == 0) {
        free(p1);
        free(p2);
        return 0;
    }

    /* メモリの先頭にrowとcolを保持しておく */
    p2 += 2;
    p2[-2] = row;
    p2[-1] = col;

    /* ポインタの設定 */
    for (i = 0; i < row; ++i, p2 += col) {
        p1[i] = p2;
    }
    return p1;
}
void free2D (int ** p1)
{
    int * p2;

    /* 空ポインタに対しては何もしない */
    if (p1 == 0) return;

    /* 実体のポインタの取得 */
    p2 = p1[0] - 2;

    /* 解放 */
    free(p1);
    free(p2);
}
int ** realloc2D (int ** array, int row, int col)
{
    int ** np;
    int oldrow, oldcol, i, j, rowmin, colmin;

    /* realloc互換動作のための処理 */
    if (row == 0 || col == 0) {
        free2D(array);
        return 0;
    }
    if (array == 0) retrun alloc2D(row, col);

    /* 行・列を取得 */
    oldrow = array[0][-2];
    oldcol = array[0][-1];
    rowmin = oldrow > row ? row : oldrow;
    colmin = oldcol > col ? col : oldcol;

    /* 新たに領域を割り当てる */
    np = alloc2D(row, col);
    if (np == 0) return 0;

    /* 値をコピー */
    for (i = 0; i < rowmin; ++i) {
        for (j = 0; j < colmin; ++j) {
            np[i][j] = array[i][j];
        }
    }

    /* 古い領域を解放 */
    free2D(array);

    return np;
}



No.10054

Re:2次元配列のrealloc
投稿者---bouz.lab(2003/10/26 17:17:33)


レスありがとうございます。プログラム、非常の参考になりました。
realloc自体あまりわかっていなかったみたいですね。

No.10036

Re:2次元配列のrealloc
投稿者---Tolilor(2003/10/26 12:28:32)


あのですね。二次元配列っていっても、Cの場合はメモリに一直線に確保されますから、
そのままrealloc()で伸ばすことができますよ。

その場合、

int size = ...;
int ( *a )[ 2 ];

a = malloc( sizeof( int ) * size );
このようにすれば二次元配列同様に扱えます。
というよりも、コレがCの二次元配列といってもいいでしょう。
例えば、以下のようなプログラムがかけます。

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

#define  ARRAY  2

int main( void )
{
    int i, j;
    int size = 4;
    int ( *a )[ ARRAY ];

    a = malloc( sizeof( int ) * ( ARRAY * size ) );

    for( i = 0; i < ARRAY; i ++ )
        for( j = 0; j < size; j ++ )
            a[ i ][ j ] = 0;

    for( i = 0; i < ARRAY; i ++ )
        for( j = 0; j < size; j ++ )
            printf( "%d", a[ i ][ j ] );

    printf( "\n" );

    return 0;
}


また、上記の初期化部分は、

for( i = 0, p = *a; i < ARRAY * size; i ++, p ++ )
            *p = 0;


とも書くことが出来ますね。

ところで、
int ** create_array(int row, int rank);
int ** create_array(int row, int rank);

って、プロトタイプ宣言ですか?
なぜそんな微妙なところに書くのです?

ついでに、malloc() で確保された領域は、キャストする必要はありません。
戻り値は、void * ですから。

create_array() ですが、何かとてつもないことをやっていますね・・・。

a1[i]=ptr1+i*rank;

とか・・・。
ptr1 って、ポインタでしょ?その指している先が見たいのならば、* つけなきゃ。
というか、先があったらの話ですが。

ついでにその領域の確保はこうするべきです。
ptr1 = malloc( row * rank * sizeof( int ) );

ptr1=(int *)malloc(row*rank*sizeof(int));
だと、ptr1 はポインタのポインタでなければだめでしょ。


とりあえず、意味不明です。
もっと簡単なプログラムで実験してみてはいかがですか?

No.10041

Re:2次元配列のrealloc
投稿者---YuO(2003/10/26 12:49:04)


>int size = ...;
>int ( *a )[ 2 ];
>a = malloc( sizeof( int ) * size );
>このようにすれば二次元配列同様に扱えます。

はい?
a = malloc(sizeof(int [2]) * ...);
のようにすべきでは?

あと,bouz.labさんが求めているのは,
int a[row][col];
のrowを可変にするのではなく,colを可変にしたいということですから,
int (*a)[2];
は不適です。


    int size = 4;
    int ( *a )[ ARRAY ];

    a = malloc( sizeof( int ) * ( ARRAY * size ) );

    for( i = 0; i < ARRAY; i ++ )
        for( j = 0; j < size; j ++ )
            a[ i ][ j ] = 0;

iとjの添字が逆です。
int (*a)[2];
と宣言されている以上,a[i][2]やa[i][3]へのアクセスは,許されるとしてもどうかと思います。
#まぁ,私も確信犯的に使いましたが>#10035:array[0][-1]等。


>create_array() ですが、何かとてつもないことをやっていますね・・・。
>a1[i]=ptr1+i*rank;
>とか・・・。

何か問題が?
一般的な方法ですが。


>ptr1 って、ポインタでしょ?その指している先が見たいのならば、* つけなきゃ。

はい?
a1はint **型です。
つまり,a1[i]はint *型です。そして,
ptr1もint *型,iとrankはint型ですから,ptr1 + i * rankはint *型になります。

これは非常に一般的な方法です。


>ついでにその領域の確保はこうするべきです。
>ptr1 = malloc( row * rank * sizeof( int ) );
>ptr1=(int *)malloc(row*rank*sizeof(int));
>だと、ptr1 はポインタのポインタでなければだめでしょ。

何故ですか?
mallocの戻り値をint *型にキャストしているのだから,
bouz.labさんの書かれたコードの=の右辺はint *型になります。

たぶんこの掲示板で前に書いたことがあるのですが,
T * p;
p = malloc(size * sizeof(T));
または
T * p;
p = (T * )malloc(size * sizeof(T));
がmallocの利用に関する基本的な形です。


Tolilorさんの言っていることは,残念ながら的を外しています。
ちゃんと型を認識してコードを読むようにした方がよいですよ。


No.10042

Re:2次元配列のrealloc
投稿者---Tolilor(2003/10/26 13:06:58)


すみません。

T * p;
p = (T * )malloc(size * sizeof(T));

いままで、malloc のキャストはしたことがなかったんので、
馬鹿なことを書いてしまいました。

T * p;
p = ( T )malloc(size * sizeof(T));

だと思っていました。
ご指摘ありがとうございます。

No.10043

Re:2次元配列のrealloc
投稿者---kikk(2003/10/26 13:35:20)


ども。


提示されたコード
>	ptr1=(int *)malloc(row*rank*sizeof(int));

>	for(i=0;i<row;i++){
>		a1[i]=ptr1+i*rank;
>	}

のような方法で、値を格納する領域を確保した場合、
http://www9.plala.or.jp/sgwr-t/c/sec09.html
の2番目の図のような状態になります(各行が連続している)。

この状態で、後ろを伸ばして、ポインタの配列a2[]の各要素の指す場所を
変えても、(当然ながら)実際のデータは動いてはくれません。

個々の行がメモリ上で連続している必要はないのであれば、行ごとに
malloc()/realloc()するように変更すればいいと思います。値の移動は
不要です。以下のような感じで(確保の例)。

int **a;
a=malloc(sizeof(int*)*row);
for(i=0; i<row; i++) {a[i]=(int*)malloc(sizeof(int)*rank);}

なお、上記のように行ごとに個別にメモリ確保をした場合、行の数を
少なくする時(前)に、削る行を1つ1つfree()する必要があります。

あと、ポインタの配列を指しているポインタ(ポインタのポインタ)をfree()
しても個々のポインタが指している領域まではfree()してくれませんので、
その辺も手当てが必要です。たとえば、
void delete_array(int **a, int row)
みたいなのを用意して。

ただし、この方法を使った場合、当然ながら各行のメモリ上での連続性を
仮定しているようなコードは書けなくなります。あと、速度が落ちるかも
しれません。


では。

No.10057

Re:2次元配列のrealloc
投稿者---bouz.lab(2003/10/26 17:33:34)


レスありがとうございます。
1000行500列ぐらいを扱いたいんですが
mallocとreallocを繰り返してるとメモリー
が足りなくなっちゃいますよねー。


No.10062

Re:2次元配列のrealloc
投稿者---YuO(2003/10/26 19:00:11)


>1000行500列ぐらいを扱いたいんですが
>mallocとreallocを繰り返してるとメモリー
>が足りなくなっちゃいますよねー。

環境にもよりますが,sizeof(int) * 0.5MB程度の容量が使えないと言うのは,
相当厳しい気がします。

5年前のPCであっても,4MB程度なら問題なく確保できるはずです。
#1000 * 500 * sizeof(int) * 2


メモリが不足するのは,メモリリークが起きているから,ということはありませんか?


No.10045

Re:2次元配列のrealloc
投稿者---mtk(2003/10/26 14:33:46)


>2次元配列のメモリーを確保した後に、その領域が足りなく
>なって再確保するときの方法がよくわかりません。

ナンセンスなプログラムになってしまいましたが、これを見て誰かが良いアイディアを
思いついてくれることを祈っておきます。

この問題は、実行時に型を変換しなければならないので、
やはり void* が筋かなと思いました。

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

// #define A      ((int (*)[col])a)
#define CAS(x) ((int (*)[x])a)
#define ACC(x) case x: return &CAS(x)[i][j];

int *access(void *a, int col, int i, int j)
{
    switch (col) {
        ACC(1);  ACC(2);  ACC(3);  ACC(4);  ACC(5);
        ACC(6);  ACC(7);  ACC(8);  ACC(9);  ACC(10);
        ACC(11); ACC(12); ACC(13); ACC(14); ACC(15);
        ACC(16); ACC(17); ACC(18); ACC(19); ACC(20);
    }
    return NULL;
}

int main(void)
{
    void *a;
    int row = 2, col = 3;

    a = realloc(NULL, row * col * sizeof(int));

    /* 可変長配列が使えるコンパイラならOK(gcc とか) */
//    printf("%d\n", A[1][1]);
//    A[1][1] = -9;
//    printf("%d\n", A[1][1]);

    printf("%d\n", *access(a, col, 1, 1));
    *access(a, col, 1, 1) = -9;
    printf("%d\n", *access(a, col, 1, 1));

    a = realloc(a, ++row * ++col * sizeof(int));
    free(a);
    return 0;
}



No.10046

Re:2次元配列のrealloc
投稿者---mtk(2003/10/26 14:46:00)


>#define CAS(x) ((int (*)[x])a)
>#define ACC(x) case x: return &CAS(x)[i][j];

マクロって出来上がった後から定義したりして、タイピングの節約に
なっていない事が多いですよね〜(自分の場合)
# 最初は CAS だけ使用していて ACC を追加したら、無駄になっていた。

#define ACC(x) case x: return &((int (*)[x])a)[i][j]

というわけで、いつもの訂正です...(^^;


No.10047

Re:2次元配列のrealloc
投稿者---mtk(2003/10/26 15:55:26)


>ナンセンスなプログラムになってしまいましたが、これを見て誰かが良いアイディアを
>思いついてくれることを祈っておきます。

ナンセンスすぎるので、すべて無視してください。(アホ丸出し)

>この問題は、実行時に型を変換しなければならないので、
>やはり void* が筋かなと思いました。

キャストしているので、何でも良かったです。

ふぅ。。。

# 最初に可変長配列の方が分かったので、あのような
# アホ丸出しプログラムになってしまいました。

No.10049

Re:2次元配列のrealloc
投稿者---mtk(2003/10/26 16:24:46)


># 最初に可変長配列の方が分かったので、あのような
># アホ丸出しプログラムになってしまいました。

「アホ丸出し」というのは、二次元配列なので、a[i][j] という風に
やりたいのに、なぜか関数呼出しになってたことです。

# 空回りしてました。

No.10055

Re:2次元配列のrealloc
投稿者---bouz.lab(2003/10/26 17:26:49)


レスありがとうございます。
すいません、今の私のレベルでは解読できませんでした。
でもまだ勉強中なので理解できるようにいつかなります。


No.10059

Re:2次元配列のrealloc
投稿者---mtk(2003/10/26 18:43:09)


>レスありがとうございます。

こちらこそ、レスありがとうございます。
今のようなレスのつけかただと、質問者が大変なので以後気を付けます。

# このコメントにはレスつけなくて結構です。