C言語編 第35章 ポインタ⑤(動的なメモリ割り当て)

先頭へ戻る

この章の概要

この章の概要です。


動的記憶域期間

これまでに、記憶域期間の種類として、自動記憶域期間と静的記憶域期間の2つが登場しました。記憶域期間とは、あるオブジェクトがいつからいつまでメモリ上に存在しているかのルールです。これは言い換えると、そのオブジェクトのメモリアドレスが有効である期間を表しているとも言えます。

オブジェクトとは、ある型のある値を持つ、メモリ上の一部分のことを言います。これまでの章では、これはつまり変数のことでした。しかし、この章で説明するように、変数を宣言することなく、メモリ上に値を置く方法があるため、正確には、オブジェクトと表現するのが適切です。

あるオブジェクトの記憶域期間の範囲外に、そのオブジェクトへアクセスした場合は未定義の動作です。このことは当然、ポインタを経由した場合にもいえることです。すでに記憶域期間を終えているオブジェクトを、ポインタを経由してアクセスした場合も未定義の動作です。

この章では、3つ目の記憶域期間として、動的記憶域期間(割付け記憶域期間)を取り上げます。

規格上、「allocated」という単語で表現されるため、訳語としては「動的」ではなく「割付け」の方が適切です。しかし、「静的」という言葉に対応して、「動的」という言葉を使う機会が多いため、当サイトでは、動的記憶域期間の方で統一します。

動的記憶域期間を持つオブジェクトは、任意のタイミングでメモリ上に置かれ、任意のタイミングで削除できます。例えば、ある関数内でメモリ上に置き、別の関数内で消すといった、非常に自由度が高い制御が行える反面、慎重なプログラミングが要求されます。また、ポインタを使うことが必須になるので、ポインタの正しい理解が必要です。

メモリの使われ方

オブジェクトを記憶しておくとき、メモリをどのように使うかという観点でも、記憶域期間による違いが見られます。ただし、以下の説明はあくまで、「多くの環境で」の話であるということに注意してください。規格上は、記憶域期間ごとのメモリの使い方に関して、具体的な規定はありません。

自動記憶域期間を持つオブジェクトは、メモリ内のスタック領域に配置されます。スタック領域は、メモリの中の比較的狭い範囲に固定的に用意されており、ここを使いまわすように利用されます。

「使いまわす」という部分がポイントで、ある関数の中で定義されたローカル変数のメモリアドレスと、異なる関数の中で定義されたローカル変数のメモリアドレスは一致する可能性があります。同時に必要になることはないのですから、メモリアドレスが一致してしまっても何ら問題がありません。また、同じ関数の同じローカル変数であっても、関数を呼び出すたびに、異なるメモリアドレスに配置される可能性もあります。

静的記憶域期間を持つオブジェクトは、メモリ内の専用の領域(静的領域グローバル領域)に配置されます。静的記憶域期間を持つオブジェクトは、プログラムの実行が開始されたときから、実行が終了するまで消えることがないので、メモリアドレスは常に固定されています。

静的領域は、ビルドを行って実行ファイルを生成する過程の中で、必要な大きさが確定できるため、無駄な領域を取らないように割り当てられます。

動的記憶域期間を持つオブジェクトは、メモリ内のヒープ領域に配置されます。

ヒープ領域を使うためには、OS に「これだけの大きさの領域が欲しい」という要求を出さなくてはなりません。OS は要求に応じて必要な領域を確保し、そのメモリアドレスを返してくれます。また、その領域を使い終えて必要なくなったときには、OS に「この領域はもういらない」と伝えます。すると OS は、その領域を誰にも使われていない状態に戻します。

このように、プログラムの実行中にメモリを確保することを、動的メモリ割り当てとか、ダイナミックアロケーションなどと呼びます。また、使い終わったときにメモリを返却する過程を、メモリを解放(デアロケート)すると表現します。

また、細かいことですが、「確保」という言葉が、「メモリ領域を確保」と「オブジェクトを確保」の両面で区別せず使われていることが多いです。メモリ領域がなければ、オブジェクトを置く場所がない訳ですから、両方を含めて言っている訳ですが、それぞれを別の過程としてイメージする癖を付けておくと、理解が深まります。まず、メモリ領域が確保され、そこにオブジェクトが置かれるという流れです。

malloc関数と free関数

では実際に、動的メモリ割り当てを行い、動的記憶域期間を持つオブジェクトを作ることを体験してみましょう。まずは、malloc関数を紹介します。malloc関数は、<stdlib.h> に次のように宣言されています。

void* malloc(size_t size);

malloc関数は、動的にメモリ領域を割り当て、そのメモリアドレスを返す関数です。確保されたメモリ領域には、不定値を持ったオブジェクトが置かれた状態です。いつものように、きちんと正しい値を入れてから使うようにしてください。このようにして確保されたオブジェクトは、動的記憶域期間を持ちます。

引数には、確保したい領域の大きさを指定します。少し感覚的にずれを感じる人もいるかもしれませんが、ここで指定するのはオブジェクトの「型」のような情報ではなく、オブジェクトを置くために必要十分な「大きさ」です。そのため、例えば double型のオブジェクトを置くための領域が欲しいのなら、「sizeof(double)」を渡します。

戻り値は、確保されたメモリ領域の先頭のメモリアドレスが返されます。もし何らかの要因で(普通は、メモリ不足です)確保に失敗した場合には、ヌルポインタが返されます。

確保するオブジェクトの型は事実上何でもありなので、戻り値は汎用ポインタ(第34章)で返されます。これでは間接参照できないので、適切な型のポインタ変数で受け取るようにします。

int* p1 = malloc( sizeof(int) );         /* int 1個分 */
int* p2 = malloc( sizeof(int) * 100 );   /* int 100個分 */
char* p3 = malloc( sizeof(char) * 101 ); /* 100文字分 */

malloc関数の実引数は、確保するメモリ領域の大きさなので、単独のオブジェクトのための領域なら sizeof が返す値をそのまま渡せばよいです。配列の場合は要素数を掛け合わせて合計の大きさを渡します。

文字列を確保する場合には特に注意が必要です。末尾のヌル文字('\0') も格納できるだけの領域がなければなりませんから、必ず 1文字分多く確保することを忘れないようにしてください。

malloc関数は失敗する可能性があることを考慮しなければなりません。動的なメモリ割り当てに失敗するということは、ほとんどの場合、メモリが足りておらず、プログラムの実行を続けることができない状況のはずです。そのため、原則的には、プログラムを正しく終わらせるようにします。

int* array = malloc( sizeof(int) * 10000 );
if( array == NULL ){
    exit( EXIT_FAILURE );
}

exit関数は、プログラムを強制終了させる標準ライブラリ関数で、<stdlib.h> にあります。実引数に、EXIT_FAILURE というオブジェクト形式マクロを使うと、何らかの問題が起きてプログラムを終わらせようとしていることを意味します。

もちろん、可能な限りのことはしたいところではあります。例えば、プログラムを実行しているユーザーに対して、何らかの問題が起きたことを伝えるだとか、開発者が対応できるような情報をファイルなどに書き出しておくだとかといったことが考えられます。とはいえ、メモリが足りない以上、できることも限られており、限界があると言わざるを得ません。

どうせ exit関数で終了させるのなら、失敗の確認なんてしなくてもいいと思う人もいるかもしれません。しかし、失敗を確認しなかったら、正常時のコードを実行してしまう訳なので、恐らく、ヌルポインタを経由した間接参照を行うコードを実行するはずです。これは未定義の動作ですから、「何が起こるか分かりません」。exit関数は「何が起きているか分かったうえでの終了」なので、まったく意味が違います。いかなる場面でも、未定義の動作は引き起こしてはなりません。

malloc関数の失敗を調べるにしても、malloc関数を呼ぶたびにチェックするのは面倒なので、独自の関数を用意することもあります。例えば、次のような関数を用意し、常にこれを使うようにしていれば、後から失敗時の対応の方針を変えることも容易です。

void* xmalloc(size_t size)
{
    void* p = malloc( size );
    if( p == NULL ){
        exit( EXIT_FAILURE );
    }
    return p;
}

さて、動的に確保されたメモリ領域は、使い終わったら解放しなければならないのでした。解放には、free関数を使います。

void free(void* ptr);

引数には、malloc関数が返したポインタを渡します。動的に確保した領域以外を指すポインタを渡してはいけません。ただし、ヌルポインタを渡した場合には、何も起こらないことが保証されています

free関数で解放が行われると、オブジェクトは動的記憶域期間を外れます。記憶域期間の範囲外にオブジェクトへアクセスする行為は未定義の動作ですから、free関数で解放されたオブジェクトへのアクセスは行ってはなりません

free関数で解放することを忘れても、OS がきちんと管理している多くの環境では、プログラム終了時に自動的に解放されます。ただし、C言語の立場としては、これは必ずそうであるとは言えません。プログラム内で解放することを勧めます。

さて、それでは実際のプログラムを見てみましょう。

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

int main(void)
{
    int* p;

    p = malloc( sizeof(int) );
    if( p == NULL ){
        exit( EXIT_FAILURE );
    }

    *p = 123;
    printf( "%d\n", *p );

    free( p );

    return 0;
}

実行結果:

123

以下の点を確認してください。

プログラム例を示しておいて何ですが、通常、int型変数 1個のために、動的なメモリ割り当てを使うことはあり得ません。動的なメモリ割り当ては、実行速度の面でも、メモリ使用量の面でも不利に働きます。

動的なメモリ割り当てを使う価値があるのは、プログラムを実行してからでないと、どれだけの大きさのメモリがあれば十分なのか分からない場合です。

例えば、標準入力からデータを受け取る際、そのデータ総数が何個あるか不明な場合などです。こういう場合には、動的なメモリ割り当てを行って、配列を動的に確保します。

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

int main(void)
{
    char buf[40];
    int dataNum;
    int* dataArray;
    int i;


    /* データ件数を受け取る */
    puts( "データの件数を入力してください。" );
    fgets( buf, sizeof(buf), stdin );
    sscanf( buf, "%d", &dataNum );

    /* データ件数が 0件以下なら終了 */
    if( dataNum <= 0 ){
        return 0;
    }

    /* 件数に合わせて、領域を確保する */
    dataArray = malloc( sizeof(int) * dataNum );
    if( dataArray == NULL ){
        exit( EXIT_FAILURE );
    }

    /* データを受け取る */
    for( i = 0; i < dataNum; ++i ){
        puts( "データを入力してください。" );
        fgets( buf, sizeof(buf), stdin );
        sscanf( buf, "%d", &dataArray[i] );
    }

    /* 結果を出力 */
    for( i = 0; i < dataNum; ++i ){
        printf( "%d: %d\n", i+1, dataArray[i] );
    }

    free( dataArray );

    return 0;
}

実行結果:

データの件数を入力してください。
5
データを入力してください。
35
データを入力してください。
-50
データを入力してください。
95
データを入力してください。
20
データを入力してください。
-45
1: 35
2: -50
3: 95
4: 20
5: -45

このプログラムは、入力されるデータの総数が一定ではありません。プログラム作成時点ではデータ数が不明な場合、静的に領域を用意することが難しいです。用意する領域が少な過ぎると、膨大な量の入力に対応できないことになってしまいます。逆に、膨大な量の入力に備えて巨大な配列を作ると、実際のデータ数が少なかった場合、ほとんどのメモリが無駄になってしまいます。

動的に確保された配列を使う場合、ポインタを通して扱うという事情から、動的な配列は、sizeof演算子を使って配列全体の大きさを知ることができない点に注意が必要です。

char static_array[100];
char* dynamic_array = malloc( sizeof(char) * 100 );

printf( "%u\n", sizeof(static_array) );   /* 100 */
printf( "%u\n", sizeof(dynamic_array) );  /* 4 */

ポインタ経由で扱う以上、sizeof演算子が返す値は、常にポインタ変数の大きさにしかならない訳です。このため、配列の要素数を調べる SIZE_OF_ARRAYマクロのようなマクロを用意していても、動的な配列には使用できません。どうしても後から要素数を知る必要がある場合には、管理が煩雑になりますが、malloc関数を呼び出したついでに、別の変数に要素数を保存しておくしかありません。

calloc関数

配列の動的な確保に関しては、calloc関数を使う方法もあります。calloc関数も <stdlib.h> にあります。

void* calloc(size_t n, size_t size);

第1引数に要素数を、第2引数に要素1つ分の大きさを指定します。戻り値は、malloc関数と同様に、動的に確保されたメモリ領域のメモリアドレスが返されます。失敗した場合の戻り値はヌルポインタです。

malloc関数と異なり、確保された領域の全ビットが自動的に 0 で埋められます。整数型であれば 0 で初期化されていると考えて良いですが、他の型の場合は想定と異なる意味を持つかもしれません。例えば、ポインタの場合、「全ビットが 0」という状態が、ヌルポインタを表すとは限りませんし、浮動小数点型の場合、「全ビットが 0」=「0.0」とはならないかもしれません。

なお、calloc関数で確保した領域も、free関数で解放します

calloc関数は、malloc関数で確保して、memset関数で 0 を埋めるのと同じ結果を生みます。

大抵は気にする必要はありませんが、さまざまな要因によって、行っていることが異なることがあり、効率面においても差が開くことがあります。

calloc関数の使用例を挙げます。

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

int main(void)
{
    const int size = 10;
    int i;
    int* array;

    array = calloc( size, sizeof(int) );
    if( array == NULL ){
        exit( EXIT_FAILURE );
    }

    for( i = 0; i < size; ++i ) {
        printf( "%d\n", array[i] );
    }

    free( array );

    return 0;
}

実行結果:

0
0
0
0
0
0
0
0
0
0

注意事項のまとめ

malloc関数、calloc関数、free関数を正しく使うには、いろいろと注意すべき点があります。慣れるまで大変なので、ここにまとめておきます。

malloc関数、calloc関数に関して、

free関数に関して、

これだけのことを考慮するのは、結構、骨が折れます。労力を減らすために有効な手法の1つとして、解放のための関数形式マクロを定義しておくというものがあります。

#define SAFE_FREE(ptr)		if(ptr != NULL ){ free(ptr); ptr = NULL; }

free関数を直接呼び出す代わりに、このマクロを使って解放するようにします。

int* array = malloc( sizeof(int) );
SAFE_FREE( array );

こうすると、解放後にポインタ変数が NULL で上書きされます。ちなみに、free関数にヌルポインタを渡しても何も起こらないので、ヌルポインタかどうかのチェックはなくても構いませんが、効率面を考えてチェックすることが多いです。

このようなマクロを使うことで、以下のような利点が生まれます。

ただし、同じメモリ領域を指し示すポインタが 2つ以上あるような状況を作ってしまっていると、これだけで確実とはいえません。

動的なメモリ割り当てを覚えると、やたらと使いたがる人もいるようですが、それはできません。ここまでの内容を見ても明らかなように、バグを作ってしまう可能性が一気に増えます。実際、プログラムのバグの原因として、動的なメモリ割り当ての扱いを失敗しているケースは非常に多いです。

また、確保も解放もそれなり処理時間が掛かるものなので、処理速度の面でも不利です。

さらに、メモリの利用効率の面でも不利です。例えば、8バイトの領域を確保したつもりでも、実際には OS側の都合などで、管理情報(数十バイト程度)が付加されるため、普通はもっと多くの領域を消費します。ある程度まとまった大きさの確保であれば、管理情報の大きさは、相対的に小さくなり、無視できる程度になりますが、細かい領域を大量に確保するような使い方は避けた方が良いでしょう。

このような理由から、動的なメモリ割り当ては、明確な必要性がない限りは避けるべきです。


realloc関数

realloc関数は、動的に確保した領域の大きさを、後から変更するものです。realloc関数は、<stdlib.h> に次のように宣言されています。

void* realloc(void* ptr, size_t size);

第1引数に、既に動的に確保済みの領域を指すポインタを渡します。既に動的に確保済みな領域というのは、malloc関数や calloc関数、あるいは realloc関数によって確保された領域ということです。なお、第1引数にヌルポインタを指定することができ、この場合、malloc関数と同じ動作になります

第2引数には、領域の新しい大きさを指定します。以前の大きさよりも、小さくても大きくても、同じでも構いません。なお、第2引数に 0 を指定した場合、free関数と同じ動作になります。これは非常に分かりづらい仕様であり、注意が必要です。

戻り値は、新しい大きさに変更された後の領域を指すポインタが返されます。失敗した場合にはヌルポインタが返されます。成功した場合は、その領域が必要なくなった後、free関数で解放する必要があります。

realloc関数は少々複雑なので、イメージをつかみづらいかもしれません。実際にどんなことをしているのか、具体的な実装例をお見せしましょう。

void* realloc(void* ptr, size_t size)
{
    char* p;

    if( ptr == NULL ){
        return malloc( size );  /* 第1引数が NULL の場合、malloc関数と同じ動作になる */
    }

    if( size == 0 ){
        free( ptr );            /* 第2引数が 0 の場合、free関数と同じ動作になる */
        return NULL;            /* 新たに確保される領域はないので NULL を返す */
    }

    p = malloc( size );             /* malloc関数を使って、新しい方の大きさの領域を確保する */
    if( p == NULL ){
        return NULL;            /* malloc関数が失敗した場合、NULL を返す。元の領域はそのまま残る */
    }

    memcpy( p, ptr, size );         /* 元の領域にあるデータを、新しく確保した領域にコピーする */
    free( ptr );                    /* 元の領域は解放する */

    return p;                       /* 新しい領域の先頭メモリアドレスを返す */
}

上記のサンプルプログラムは、realloc関数がどのように実装されているかの一例を示すものであって、必ずこの程度に実装されているということではありません。しかし、標準規格に従っていれば、行っている内容は同等のはずです。

まず、最初の if文では、第1引数がヌルポインタの場合の処理を行っています。前述したように、第1引数が ヌルポインタの場合、malloc関数と同じことを行います。

2つ目の if文では、第2引数が 0 の場合の処理を行っています。これも前述したように、第2引数が 0 の場合、free関数と同じことを行います。

このような仕様は、よくない設計の代表格とされています。つまり、「処理を詰め込みすぎ」です。極論すれば、malloc関数も free関数も必要なくて、realloc関数だけで賄えてしまえる訳ですが、関数名には、処理の内容を説明する効果もあるので、できるだけ特化した関数の方が分かりやすく、間違いにくいです。第1引数がヌルポインタのときの挙動はまだマシで、使いどころもありますが、第2引数が 0 のときに free関数の意味になるのは「realloc」という関数名の「alloc」の部分がまったく活きていません。

その後、新しい大きさの領域を動的に割り当てています。これは当然、もともと確保されていた領域とは別のメモリアドレスに作られています。ここでメモリ不足などで失敗する可能性がありますが、その際には realloc関数自身の失敗としてヌルポインタを返して終了します。この場合には、もともと確保されていた領域には、何ら変化は起きていません。

もともと確保されていた領域にあった内容は、新しい領域に引き継がせたいので、内容をコピーする必要があります。memcpy関数でこれを行っています。

コピーを終えたら、もともと確保されていた領域は必要なくなるので、free関数を使って解放します。

処理の流れをまとめると、以下のとおりです。

  1. malloc関数で、新しいメモリ領域を割り当てる
  2. memcpy関数で、古いメモリ領域にあったデータを、新しいメモリ領域へコピーする
  3. free関数で、古いメモリ領域を解放する
  4. 新しい領域のメモリアドレスを返す

従って、古いメモリ領域にあったデータは、新しいメモリ領域にきちんと移動します。しかし、新しく確保しなおしているのですから、メモリアドレスは変わってしまいます。これは、古いメモリ領域にあったオブジェクトを指していたポインタを使って間接参照を行ってはならないということです。

また、realloc関数が成功したのであれば、古いメモリ領域は、realloc関数内で解放済みですから、自分で解放する必要はありません。

ではここで、実際に使用感を確かめておきましょう。

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

int main(void)
{
    char buf[40];
    int num;
    int i;
    int* tmp;
    int* array = NULL;
    size_t size = 0;
    size_t i;


    while( 1 ){
        puts( "次のデータを整数で入力してください。負数を入力すると終了します。" );
        fgets( buf, sizeof(buf), stdin );
        sscanf( buf, "%d", &num );

        if( num < 0 ){
            break;
        }

        size++;

        /* 領域を拡張する */
        tmp = realloc( array, sizeof(int)*size );
        if( tmp == NULL ){
            /* realloc関数が失敗した場合、元の領域は解放されずに残されている */
            /* 自分で free関数を呼び出して終了する */
            free( array );
            exit( EXIT_FAILURE );
        }
        array = tmp;
        tmp = NULL;  /* 安全策。確保された領域を指すポインタを array だけに限定する */

        /* 入力されたデータを格納 */
        array[size-1] = num;
    }

    /* 結果を出力 */
    for( i = 0; i < size; ++i ){
        printf( "%d\n", array[i] );
    }

    free( array );

    return 0;
}

実行結果:

次のデータを整数で入力してください。負数を入力すると終了します。
3
次のデータを整数で入力してください。負数を入力すると終了します。
5
次のデータを整数で入力してください。負数を入力すると終了します。
7
次のデータを整数で入力してください。負数を入力すると終了します。
10
次のデータを整数で入力してください。負数を入力すると終了します。
-1
3
5
7
10

標準入力から受け取ったデータを出力するだけのプログラムです。

realloc関数を使うときには、第1引数に指定するポインタ変数と、戻り値を受け取るポインタ変数を、別々の変数にするべきです。同じ変数を使って、以下のように書いたとします。

array = realloc( array, sizeof(int)*size );

こうしてしまうと、realloc関数が失敗した場合にヌルポインタが返されますから、ポインタ変数 array にはヌルポインタが代入されます。しかし、もともと array が指していたメモリ領域はまだ解放されていません。realloc関数が失敗した場合、realloc関数内の free関数は実行されないためです(前述の実装例を確認してください)。

ポインタ変数array がもともと持っていたメモリアドレスの情報を上書きして失ってしまったため、free関数を呼ぶことができず(free関数の実引数に何を渡せばいいのか?)、メモリ領域を解放する術がなくなってしまうのです。

そのため、realloc関数の戻り値は、第1引数に渡すポインタ変数とは異なるポインタ変数で受け取り、realloc関数が成功したときに、あらためて array へ代入しなおすようにします。

サンプルプログラム内で「安全策」というコメントがあります。realloc関数の戻り値を受け取るために使ったポインタ変数(tmp) は、本来代入すべきポインタ変数(array) に値をコピーした後には不要になります。使わなければよいだけのことではありますが、間違って「free(tmp);」と「free(array);」を両方書いてしまうようなミスを犯すと、解放済みのメモリ領域を再度解放するという未定義の動作になってしまいます。

そこで、もう使わなくなったポインタ変数にはヌルポインタを入れておくという手を取っています。これで間違って「free(tmp);」を書いたとしても、何も起こりませんから安全です。

ところで、先ほどのサンプルプログラムのように、データが 1件増えるたびに、realloc関数を呼ぶような実装は、非常に効率が悪い可能性があります。新しいメモリ領域を割り当てることや、そこへデータをコピーすることは、実行速度への影響が大きいためです。
そこで例えば、領域が足りなくなったら、領域を倍々に増やしていくなどして、新しい領域の割り当てやコピーを、できるだけ一括で行うようにする効率改善策が取られることがあります。


練習問題

問題① 次のプログラムの誤りを指摘してください(メモリ不足への対策を行っていない点は無視してください)

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

#define NAME_LEN          64        /* 名前の最大長 */
#define PHONE_NUMBER_LEN  16        /* 電話番号の最大長 */

/* 個人情報 */
typedef struct {
    char	name[NAME_LEN];                 /* 名前 */
    char	phone_number[PHONE_NUMBER_LEN]; /* 電話番号 */
} PersonalInfo;

int main(void)
{
    char buf[40];
    int dataNum;
    PersonalInfo* infoArray;
    int i;


    /* データ件数を受け取る */
    puts( "データの件数を入力してください。" );
    fgets( buf, sizeof(buf), stdin );
    sscanf( buf, "%d", &dataNum );

    /* データ件数が 0件以下なら終了 */
    if( dataNum <= 0 ){
        return 0;
    }

    /* 件数に合わせて、領域を確保する */
    infoArray = malloc( sizeof(PersonalInfo) * dataNum );
    if( infoArray == NULL ){
        exit( EXIT_FAILURE );
    }

    /* データを受け取る */
    for( i = 0; i < dataNum; ++i ){
        puts( "名前を入力してください。" );
        fgets( buf, sizeof(buf), stdin );
        sscanf( buf, "%s", infoArray[i].name );

        puts( "電話番号を入力してください。" );
        fgets( buf, sizeof(buf), stdin );
        sscanf( buf, "%s", infoArray[i].phone_number );
    }

    /* 特に手を加えないので、領域を解放する */
    free( infoArray );

    /* 結果を出力 */
    for( i = 0; i < dataNum; ++i ){
        printf( "%s: %s\n", infoArray[i].name, infoArray[i].phone_number );
    }

    return 0;
}

問題② 次のプログラムの誤りを指摘してください(メモリ不足への対策を行っていない点は無視してください)

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

void SetSequentialNumber(int* array, size_t size);
void PrintArray(int* array, size_t size);

int main(void)
{
    int* values;
    size_t size;


    size = 50;
    values = malloc( sizeof(int) * size );
    SetSequentialNumber( values, size );
    PrintArray( values, size );

    size = 100;
    values = calloc( size, sizeof(int) );
    SetSequentialNumber( values, size );
    PrintArray( values, size );

    free( values );

    return 0;
}

/*
    配列に連番をセットする。
    引数:
        array:	対象配列のメモリアドレス。
        size:	対象配列の要素数。
*/
void SetSequentialNumber(int* array, size_t size)
{
    size_t i;

    for( i = 0; i < size; ++i ){
        array[i] = i;
    }
}

/*
    配列の中身を標準出力へ出力する。
    引数:
        array:	対象配列のメモリアドレス。
        size:	対象配列の要素数。
*/
void PrintArray(int* array, size_t size)
{
    size_t i;

    for( i = 0; i < size; ++i ){
        printf( "%d\n", array[i] );
    }
}

問題③ 標準入力から、int型に収まる整数値が繰り返し入力されるとして、全ての入力を受け取った後、受け取ったすべての数値を標準出力へ出力するプログラムを作成してください。負数が入力されると終了します。(これは realloc関数のところのサンプルプログラムと同様です)。
ただし、メモリ領域が倍々の大きさで確保されるようにしてください。最初の大きさは任意で決めて構いません。

問題④ 標準入力から、long型に収まる整数値が繰り返し入力されるとして、全ての入力を受け取った後、入力された数値の平均値を出力するプログラムを書いてください。入力件数は不明ですが、最大でも 1000件とし、負数が入力されると終了します。
ただし、必要なメモリ領域はプログラムの開始直後にまとめて確保し、最後に realloc関数を使って、使われなかった領域を切り詰める方法で実装してください。

問題⑤ realloc関数を次の条件でラップした関数を作成してください。

問題⑥ 次のような関数を作成しました。

char* strDuplicate(const char* s)
{
    char* dup = malloc( strlen( s ) + 1 );
    strcpy( dup, s );

    return dup;
}

この関数は何をしているか説明してください。また、この関数を実際に使用したプログラムを作成してください。


解答ページはこちら

参考リンク



更新履歴

'2018/6/1 第38章から練習問題⑤を移動してきて、練習問題⑥とした。

'2018/5/29 第34章の内容を移動してきて統合。
「実行効率の改善」の項は、realloc関数のところで少し触れるようにしたうえで、練習問題で扱うようにした。
「フラグメンテーション」の項を削除。
章のタイトルを変更(「ポインタ⑤(動的なメモリの再割り当て)」->「ポインタ⑤(動的なメモリ割り当て)」)

'2018/5/14 章のタイトルを変更(「ポインタ⑤ 動的なメモリ割り当て②」->「ポインタ⑤(動的なメモリの再割り当て)」)

'2018/4/20 「NULL」よりも「ヌルポインタ」が適切な箇所について、「ヌルポインタ」に修正。

'2018/3/11 全面的に文章を見直し、修正を行った。

≪さらに古い更新履歴を展開する≫



前の章へ (第34章 ポインタ④(バイト単位の処理))

次の章へ (第36章 ポインタ⑥(データ構造の構築))

C言語編のトップページへ

Programming Place Plus のトップページへ


はてなブックマーク Pocket に保存 Twitter でツイート Twitter をフォロー
Facebook でシェア Google+ で共有 LINE で送る rss1.0 取得ボタン RSS
管理者情報 プライバシーポリシー