動的に配列を確保する関数の作り方

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ンアーッ!

動的に配列を確保する関数の作り方

#1

投稿記事 by ンアーッ! » 9年前

いつもお世話になってます。

今日は二次配列(または一次)を動的に確保することに関して、お聞きしたい点があります。
動的に確保する、ということは、mallocを使うということになるのですが、これを関数を使って実現できないかと疑問に思っています。

つまり、
--------
~~~
int a[m][n];
~~~
--------

という記述を

--------
~~~
int **a;
makearray(m,n,a);//makearrayは自作の関数
~~~
--------

という様な書き方で実現できないか、ということです。
以下の試しに書いてみた、二次元配列を確保するコードでは、関数が3つありますが、main関数のforループの部分(正確にはa[j]の部分)でコアダンプが発生します。



コード:

#include<stdio.h>
#include<malloc.h>

/*配列を上書き*/
void print(int m,int n,double **a)
{
        int i,j;

        printf("--------------------------\n");
        for(i=0;i<5;i++)
        {
                for(j=0;j<4;j++)
                {
                        a[i][j]=1;
                        printf("%f\n",a[i][j]);
                }
        }
}

/*配列を動的に確保する関数*/
void makear(int m,int n,double **a)
{
        int i,j;

        /*********************/
        a=malloc(sizeof(double *)*m);
        for(i=0;i<m;i++)
        {
                a[i]=malloc(sizeof(double)*n);
        }
        /**********************/

        for(i=0;i<5;i++)
        {
                for(j=0;j<4;j++)
                {
                        a[i][j]=j;
                        printf("%f\n",a[i][j]);
                }
        }

        print(m,n,a);
}
int main()
{
        int m=5,n=4;


        /*コメントで挟まれている部分を、a[m][n]という記述と同じ意味にしたい*/
        /***********/
        double **a;
        makear(m,n,a);
        /***********/

        int i,j;

        for(i=0;i<5;i++)
        {
                for(j=0;j<4;j++)
                {
                       /*ここでコアダンプ*/
                        a[i][j]=j;
                        printf("%f\n",a[i][j]);
                }
        }


}


アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: 動的に配列を確保する関数の作り方

#2

投稿記事 by h2so5 » 9年前

「多次元配列」と「ポインタの配列」を混同していませんか?
int a[] でも int a[][] でも int a[][][] でも次元に関わらずキャスト可能なポインタは int* です。 int ** や int *** ではありません。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 動的に配列を確保する関数の作り方

#3

投稿記事 by みけCAT » 9年前

C言語/C++では、関数の(参照でない)仮引数を書き換えても、呼び出し元の実引数には影響を与えないはずです。
簡単なコードで試してみましょう。

コード:

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

void makear(double **a) {
	a=(double**)malloc(sizeof(double*)*5);
    printf("%p\n",(void*)a); // 2番めに出力されるはず
}

int main(void) {
	double **a;
	printf("%p\n",(void*)a); // 適当な値が出力されるはず
	makear(a);
	printf("%p\n",(void*)a); // 最初と同じ値が出力されるはず
	return 0;
}
というわけで、
ンアーッ! さんが書きました:--------
~~~
int a[m][n];
~~~
--------

という記述を

--------
~~~
int **a;
makearray(m,n,a);//makearrayは自作の関数
~~~
--------

という様な書き方で実現できないか、ということです。
これを実現するには、C++で参照を用いると良いでしょう。

コード:

#include<stdio.h>
#include<malloc.h>

/*配列を上書き*/
void print(int m,int n,double **a)
{
        int i,j;

        printf("--------------------------\n");
        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                        a[i][j]=1;
                        printf("%f\n",a[i][j]);
                }
        }
}

/*配列を動的に確保する関数*/
void makear(int m,int n,double **&a)
{
        int i,j;

        /*********************/
        a=(double **)malloc(sizeof(double *)*m);
        for(i=0;i<m;i++)
        {
                a[i]=(double *)malloc(sizeof(double)*n);
        }
        /**********************/

        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                        a[i][j]=j;
                        printf("%f\n",a[i][j]);
                }
        }

        print(m,n,a);
}
int main()
{
        int m=5,n=4;


        /*コメントで挟まれている部分を、a[m][n]という記述と同じ意味にしたい*/
        /***********/
        double **a;
        makear(m,n,a);
        /***********/

        int i,j;

        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                       /*ここでコアダンプ*/
                        a[i][j]=j;
                        printf("%f\n",a[i][j]);
                }
        }


}
ついでに、せっかくm, nがあるのにfor文でマジックナンバーが使用されているのが気になったので、直しておきました。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ンアーッ!

Re: 動的に配列を確保する関数の作り方

#4

投稿記事 by ンアーッ! » 9年前

>>h2so5さん
多次元配列

int a[][];

ポインタの配列

int (*a)[];


こんな感じでしょうか
正直あまり自信がなく、

int **a;

のような書き方は例えば、int aaaのポインタint *aa、のポインタint **aという認識くらいしかありません

mallocの返り値をint *型にキャストして返す、ということでしょうか?



>>みけCATさん
申し訳ない…自分はC++は使ったことがなく、Cでしかコードがかけません…
実はすでに普通の配列を使った最短経路問題を解くプログラムを書いたのですが、課題として動的に配列を確保することが条件であるので、どうにか関数として作っておけないかと思ったのです(各関数内ではすべてa[][]という書き方なので、配列の確保の仕方によって、書き方を変えたくないためです)

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 動的に配列を確保する関数の作り方

#5

投稿記事 by みけCAT » 9年前

ンアーッ! さんが書きました:>>みけCATさん
申し訳ない…自分はC++は使ったことがなく、Cでしかコードがかけません…
実はすでに普通の配列を使った最短経路問題を解くプログラムを書いたのですが、課題として動的に配列を確保することが条件であるので、どうにか関数として作っておけないかと思ったのです(各関数内ではすべてa[][]という書き方なので、配列の確保の仕方によって、書き方を変えたくないためです)
縛りの内容がよくわからないですが、素直に確保する領域へのポインタを格納する領域のポインタを確保用関数に渡せばいいと思います。

コード:

#include<stdio.h>
#include<malloc.h>

/*配列を上書き*/
void print(int m,int n,double **a)
{
        int i,j;

        printf("--------------------------\n");
        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                        a[i][j]=1;
                        printf("%f\n",a[i][j]);
                }
        }
}

/*配列を動的に確保する関数*/
void makear(int m,int n,double ***a)
{
        int i,j;

        /*********************/
        /* 本来はちゃんと確保できたか(戻り値がNULLでないか)チェックしたほうがいいが、省略 */
        (*a)=(double **)malloc(sizeof(double *)*m);
        for(i=0;i<m;i++)
        {
                (*a)[i]=(double *)malloc(sizeof(double)*n);
        }
        /**********************/

        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                        (*a)[i][j]=j;
                        printf("%f\n",(*a)[i][j]);
                }
        }

        print(m,n,*a);
}
int main()
{
        int m=5,n=4;
        int i,j; /* C言語なので変数の宣言を関数定義の最初に移動 */


        /*コメントで挟まれている部分を、a[m][n]という記述と同じ意味にしたい*/
        /***********/
        double **a;
        makear(m,n,&a);
        /***********/


        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                       /*ここでコアダンプ*/
                        a[i][j]=j;
                        printf("%f\n",a[i][j]);
                }
        }

        /* メモリリークさせるのはよくない */
        for(i=0;i<m;i++)free(a[i]);
        free(a);

        return 0; /* 戻り値がvoidでない関数はきちんと値を返すべきである */
}
ただし、これでは指定された書き方になりません。
C言語で指定された書き方を実現するのは無理かもしれませんが、関数ではなくマクロを用いればできます。

コード:

#include<stdio.h>
#include<malloc.h>

/*配列を上書き*/
void print(int m,int n,double **a)
{
        int i,j;

        printf("--------------------------\n");
        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                        a[i][j]=1;
                        printf("%f\n",a[i][j]);
                }
        }
}

/*配列を動的に確保するマクロ*/
#define makearray(m,n,a) \
{ \
        int i,j; \
\
        /*********************/ \
        /* 本来はちゃんと確保できたか(戻り値がNULLでないか)チェックしたほうがいいが、省略 */ \
        (a)=(double **)malloc(sizeof(double *)*(m)); \
        for(i=0;i<(m);i++) \
        { \
                (a)[i]=(double *)malloc(sizeof(double)*(n)); \
        } \
        /**********************/ \
 \
        /*for(i=0;i<5;i++)*/ \
        for(i=0;i<(m);i++) \
        { \
                /*for(j=0;j<4;j++)*/ \
                for(j=0;j<(n);j++) \
                { \
                        (a)[i][j]=j; \
                        printf("%f\n",(a)[i][j]); \
                } \
        } \
 \
        print((m),(n),(a)); \
}
int main()
{
        int m=5,n=4;
        int i,j; /* C言語なので変数の宣言を関数定義の最初に移動 */


        /*コメントで挟まれている部分を、a[m][n]という記述と同じ意味にしたい*/
        /***********/
        double **a;
        makearray(m,n,a);
        /***********/


        /*for(i=0;i<5;i++)*/
        for(i=0;i<m;i++)
        {
                /*for(j=0;j<4;j++)*/
                for(j=0;j<n;j++)
                {
                       /*ここでコアダンプ*/
                        a[i][j]=j;
                        printf("%f\n",a[i][j]);
                }
        }

        /* メモリリークさせるのはよくない */
        for(i=0;i<m;i++)free(a[i]);
        free(a);

        return 0; /* 戻り値がvoidでない関数はきちんと値を返すべきである */
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ンアーッ!

Re: 動的に配列を確保する関数の作り方

#6

投稿記事 by ンアーッ! » 9年前

>>みけCATさん

ありがとうございます。おかげさまでどうやら関数の引数を少し変更(int (*a)[n] → int **a)するだけで、元のプログラムを実行することができそうです



二重ポインタのアドレスを関数に渡し、makear側で元のポインタにささせる領域を確保するわけですね

(*a)=(double **)malloc(sizeof(double *)*m);

この記述が、double *型をm個分のサイズで確保したメモリ、のアドレスをdouble **型でキャストして、main関数のint **aに指させている、という意味だというところまでは理解できるのですが、

(*a)=(double *)malloc(sizeof(double)*n);

これの右辺で、double型をn個分のサイズで確保したメモリ、のアドレスをdouble *型でキャストしたもの、を何に指させているかが分かりません…
おそらく一次元配列へのポインタであるべきなのでしょうが、左辺についてもう少し教えていただけないでしょうか?

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: 動的に配列を確保する関数の作り方

#7

投稿記事 by みけCAT » 9年前

ンアーッ! さんが書きました:(*a)=(double *)malloc(sizeof(double)*n);

これの右辺で、double型をn個分のサイズで確保したメモリ、のアドレスをdouble *型でキャストしたもの、を何に指させているかが分かりません…
おそらく一次元配列へのポインタであるべきなのでしょうが、左辺についてもう少し教えていただけないでしょうか?

まず(*a)でdouble**型の「変数」が得られます。(int*型の変数aについて、*aとするとint型の変数のようにアクセスできるのと同じ)
それにをつけると、double*型の「配列」の要素にアクセスできます。(int*型の変数aについて、aのように書くのと同じ)
すなわち、*((*a)+i)という意味になります。
aがdouble***型、*aがdouble**型、*((*a)+i)がdouble*型になるので、
double型のデータを格納する領域のアドレスを(*a)に入れられます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

ンアーッ!

Re: 動的に配列を確保する関数の作り方

#8

投稿記事 by ンアーッ! » 9年前

>>みけCATさん

丁寧な説明ありがとうございます…int *型のときの対比と、*((*a)+i)という書き方でようやく理解できました。ありがたいです
大変良い勉強になりました。

またお世話になると思いますが、よろしくお願いします。

お二方とも大変ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る