ポインタと動的メモリ確保

C言語勉強会 第十一回

kumar
September 24, 2013
引用 : Programming Place Plus

今回の内容

構造体へのポインタ

構造体へのポインタ

参照先のメンバにアクセスするには、.ではなく、 アロー演算子-> を使う。

文法

構造体へのポインタ->メンバ名

typedef struct {
    char name[20];
    float height;
    float weight;
} Human;

/* Human構造体のポインタを引数に取る関数 */
void printHuman(Human *p) {
    printf(" 名前: %s", p->name);
    printf(" 身長: %f", p->height);
    printf(" 体重: %f¥n", p->weight);
}   

constポインタ

まずは const変数 をおさらい

指定した変数が定数であることを指定する。
定数とは、初期化出来ても書き換え出来ない変数のことである。

文法

const 型名 変数名; または 型名 const 変数名;

これに対して、constポインタはポインタ自身でなく、参照先の書き換えを禁じる。

constポインタ

そのポインタから参照先を書き換えることを禁じる

文法

const 型 *変数名
または
型 const *変数名

int 型変数 x が宣言されているとする。

int *pointer = &x;              /* ポインタを宣言 */
const int *const_pointer = &x;  /* constポインタを宣言 */

int bar = 10 + *pointer;        /* 可能 */
int foo = 10 + *const_pointer;  /* 可能 */

*pointer = 100;                 /* 可能 */
*const_pointer = 100;           /* 不可能。コンパイルエラー */

constポインタでできること

ミスが減る。 特に配列を(ポインタにして)引数にとる関数を書く際に、配列を誤って書き換えることがない

constポインタを引数にとる関数の例

int strlen(const char *str) {
    int len = 0;
    while(str++ != '¥0')
        len++;
    return len;
}

コラム

i++ と ++i

インクリメントやデクリメントは、演算子を変数名の前に書くか後ろに書くかで名前、性質が変わる。

文法

iを何らかの変数とする。

++i前置インクリメント
i++後置インクリメント

御存知の通り、インクリメントは「変数に1足す」という意味で、デクリメントは「変数から1引く」という意味である。だが、演算子が前置か後置かで、式の中で演算が行われるタイミングが異なる。

前置インクリメント・デクリメント

式の評価より先に インクリメント、またはデクリメントを行う。

後置インクリメント・デクリメント

式の評価の後で インクリメント、またはデクリメントを行う。

int n, i = 0;

n = i++;    // n = 0, i = 1

i = 0;

n = ++i;    // n = 1, i = 1

ちなみに、前置の方が後置より微妙に高速である。すごいプログラマは前置と後置、どちらを使っても良い状況で、前置を好む傾向にある。(気がする)

size_t i;
for(i = 0; i < 100; ++i)
{
    unko unko unko...
}

こんなかんじです。

ポインタの複数宣言

ポインタを一行で一度に宣言するとき、

int *x, y, z;

と書きたくなるかと思うが、こう書くと、xだけがポインタになり、yとzは通常の変数になってしまう。正しくは

int *x, *y, *z;

こう書くのが正しい

わざわざ構造体をポインタ渡しする理由

先の例で、構造体をわざわざ関数にポインタ渡ししていたが、ポインタを使わず普通に渡してもいいのでは?(普通に渡すことを、 値渡し という)と思った人も居るかもしれない。

/* Human構造体を値渡しで引数に取る関数 */
void printHumanValue(Human v) {
    printf(" 名前: %s", v.name);
    printf(" 身長: %f", v.height);
    printf(" 体重: %f¥n", v.weight);
}

/* Human構造体のポインタを引数に取る関数 */
void printHumanPointer(Human *p) {
    printf(" 名前: %s", p->name);
    printf(" 身長: %f", p->height);
    printf(" 体重: %f¥n", p->weight);
}

だが、値渡しより、 ポインタ渡しの方が高速 である。何故なら 値渡しでは構造体のメンバを全てコピーして渡す のに対して、 ポインタ渡しでは、ポインタのみをコピーして渡す だけであるからだ。これは32bit環境ならたったの4バイトであり、構造体がいくら巨大であろうと固定である。

しつこい話をする。

簡単のため省略していたが、

void printHumanPointer(Human *p);

は、 p の参照先のメンバを画面に出力するだけで、書き換えるつもりのない関数だ。だから、 p は const 修飾すべきだ。

void printHumanPointer(const Human *p);

アロー演算子は糖衣構文

アロー演算子も配列の添字と同じく、糖衣構文(シンタックスシュガー)である。

文法

ps->x

におけるアロー演算子は

(*ps).x

の糖衣構文(シンタックスシュガー)である

ポインタとconst修飾子

以下の例はconstポインタの宣言の文法である

const int *x;
int const *x;

だが以下の例はそうではない。

int *const x: 

動的メモリ確保

動的メモリ確保のまえに、静的メモリ確保についておさらい

静的メモリ確保とは、今まで扱ってきた、配列宣言のこと。

int arr[ 10 ];

実は以下の例はC言語では動かないのだ。(例外有り。後述)

int n = ?;  // ? は任意の数字
int arr[ n ];   // コンパイルエラー

何故なら、配列の要素数は定数で無くては宣言できないからだ。言い換えると、 コンパイルするタイミングで、配列の長さが決定されている必要がある 。 これが静的メモリ確保だ。 この欠点を補うのが動的メモリ確保である。

動的メモリ確保

実行時にメモリ領域の確保を行うこと。

今までの静的メモリとは違い、 実行時に自由な大きさの配列を作ることができる。

なお、以降の関数を利用するためには、標準ライブラリstdlib.hがインクルードされている必要がある。これを忘れた場合、なんとコンパイルエラーとはならず、本来とは違う挙動を行うことがあるので要注意である。

配列の宣言とは全く違い、関数を使って確保を行う。 直感的でないのだが、古い言語なので我慢して欲しい。

動的メモリ確保を担う関数は以下のとおりだ。

確保したメモリを解放する関数は以下のとおりだ。

malloc

void *malloc(size_t size);

size バイト分の連続したメモリ領域を確保し、その領域へのポインタを返す。

エラー時にはNULLを返す。

この関数はvoidポインターが戻り値である。voidポインターは汎用ポインタと呼ばれ、どの型にも勝手にキャストされる。

簡単な例

int *p = malloc( 10 * sizeof(int) );

要素数10のint型配列を作った。 32bit環境ではsizeof(int)は4なので、mallocに 40 を渡して、 40バイト分、メモリを確保した。

簡単のため要素数は10にしたが、このように要素数が定数の場合は静的メモリ確保でいいと思う。 実際には定数でなく変数を渡すのが普通だ。

そして、このようにメモリを確保したら必ず、必ず、次に説明するfreeで、確保したメモリ領域を開放し無くてはならない。

free

void free(void *pointer)

pointerの指すメモリ領域を開放する。

malloc等で動的確保したメモリは、この関数を使わない限り、プログラムが終了するまで開放されない。メモリの無駄遣いを防ぐため、 必ず使い終わったメモリはこの関数で開放すること 。メモリの解放をし忘れることを、 メモリリーク と呼び、大量にメモリリークすると、システムの全てのメモリを食いつぶしてしまい、 最悪OSを巻き込んで停止してしまう。

長さ ? のint型配列を作る。

size_t n = ?;

/* 動的メモリ確保する */
int *p = malloc( n * sizeof(int) );

/* 配列と同じように使える */
p[0] = 100;
printf("%d¥n", p[0]);

/* 使い終わったら */
free( p );

calloc

void *calloc(size_t nelements, size_t bytes)

bytesのサイズのメモリ領域をnelements個格納できるメモリ領域を確保する。

これは以下の様な解釈で問題ない。

void *calloc(size_t nelements, size_t bytes) {
    return malloc(nelements * bytes);
}

当然、mallocと同じくfreeで開放し無くてはならない。

realloc

void *realloc(void *pointer, size_t bytes)

pointerが指すメモリブロックをリサイズする。

具体的には、bytesバイトのメモリ領域を新たに確保して、pointerメモリブロックにあったデータはできる限りコピーする。

以前より大きくすることも、小さくすることも可能である。

当然、mallocと同じくfreeで開放し無くてはならない。

追伸:C99以降では静的配列の宣言と同じ文で動的配列作れます

{
    int n = ?;
    int arr[ n ];   // C99以降のみ可能

    unko unko unko...

}   // ブロックを抜けた時に自動で解放してくれる

このようにできます。しかもfreeを呼ぶ必要なし。freeし忘れるミスを減らせるので積極的に使っていけ

でもmallocちゃん達もまだまだ現役なので、ちゃんと覚えてね。

次回

次回予告(多分最終回)