55章 動的メモリ確保

http://www.geocities.jp/ky_webid/c/055.html

さてさてまたまた難解な話になりそうです。

最後に、動的に確保されたメモリ領域は、通常の変数定義で確保される領域とは場所が異なります。単にメモリ領域といっても役割に応じて名称があります。動的なメモリ割り当てで使う領域は、ヒープ領域 と呼ばれています。これに対し、通常の変数はスタック領域という場所に確保されます。ヒープ領域に確保された変数は、それを確保したスコープから抜けても、存在し続けます。例えば、ある関数の中で、動的に変数を作った場合、その関数を抜けだしても、ヒープ領域に存在し続けることになります。

大事な話なので覚えておくように。

とりあえずこの章でのポイントをまとめると

  • ヒープ領域とスタック領域
  • malloc関数とfree関数
  • calloc関数
  • realloc関数


となりますか。

とにかく物凄く大切なところだと思うのでしっかり頭に叩きこみます。がんばれ俺。

問題1

80文字までの文字列をキーボードから入力させ、それを画面に逆順に表示させるプログラムを作ります。文字列を受け取る変数を、動的なメモリ割り当てを使って確保するようにして下さい。

動的に確保するぞー。

int main () {
    char *str;
    size_t i,len;
    
    str = (char*)calloc(81,sizeof(char));
    if ( str == NULL ) {
        return 1;
    }
    
    fgets(str,sizeof(char)*81,stdin);
    
    len = strlen(str);
    for(i=0;i<len;++i) {
        printf("%c",str[len-1-i]);
    }
    
    free(str);
    str = NULL;
    
    return 0;
}
$ main
asdfasdf

fdsafdsa

できました。が、ひとつ問題が。

fgetsする時の指定するサイズが動的に取れないです。strはポインタなので4バイトになってしまいます。

これは上記でやってるように「sizeof(char)*81」とするしかないんでしょうか・・・。うーんなんとかしたいなぁ。

答え合わせ

あってました。やはりfgetsの第二引数はそのまま数値を渡していますね。これはどうしようもないということなんでしょうね。

問題2

次のプログラムを realloc関数を使って書き直して下さい。

int main(void){
    int *p;
    int i;
    
    p = (int*)malloc(sizeof(int));
    *p = 234;
    printf( "%d\n", *p );
    free( p );
    
    p = (int*)calloc(5, sizeof(int));
    for(i=0; i<5; ++i)    {
        p[i] = i * 10;
    }
    printf( "%d\n", p[3] );
    free( p );
    return 0;
}

なんでもできちゃうrealloc関数を使うわけですね。了解です。

int main () {
    int *p;
    int i;
    
    p = (int*)realloc(NULL,sizeof(int));
    *p = 234;
    printf( "%d\n", *p );
    realloc( p, 0 );
    
    p = (int*)realloc(NULL,5*sizeof(int));
    for(i=0; i<5; ++i)    {
        p[i] = i * 10;
    }
    printf( "%d\n", p[3] );
    realloc( p, 0 );
    
    return 0;
}

第一引数がNULLの場合はmallocと同じ。

第二引数が0の場合はfreeと同じ。

ということでこんな感じになりました。

答え合わせ

realloc関数を使うと、同じポインタ変数を使い回せ、また途中で解放を挟む必要がなくなります(実際には解放は realloc関数内で行われている)

なるほど。確かにそこも最適化できますね。見落としてました。悔しいです!

問題3

5×5の int型の二次元配列、あるいはそのような役割を果たす構造を、動的確保することはできるでしょうか?

できるような気はしますね。試してみます。

int main () {
    int **p;
    int i,j;
    
    p = (int**)malloc(sizeof(int)*5*5);
    if ( p == NULL ) {
        return 1;
    }
    
    for(i=0;i<5;++i) {
        for(j=0;j<5;++j) {
            p[i][j] = i*j;
        }
    }
    
    for(i=0;i<5;++i) {
        for(j=0;j<5;++j) {
            printf("%d ",p[i][j]);
        }
        puts("");
    }
    
    free(p);
    p = NULL;
    
    return 0;
}

直感でやってみたら見事にクラッシュしました。

なんとなくこういう書き方できそうなの癖にできないのね。ちょっと残念。

ということで多次元配列はできない?のかな。他にやり方が思いつかない。

答え合わせ

ポインタの配列を作ってから、各列に行を割り当てていく訳です。割り当て方と解放の仕方が分かりにくいですが、使うときは、普通の二次元配列と同じように扱えます。

とりあえず僕がやったような感じでは無理だということはわかりました。

解答例にあるように、ゴリ押しでやればできないこともないという感じですね。かなり面倒臭いですがw