Lesson 7
ポインタ
Lesson 7
Chapter 1
ポインタ
これまでに変数や配列を使って値を格納する方法について学習しました。変数や配列に格納しておいた値は、必要に応じてプログラムの中でいつでも呼び出すことが可能です。また格納された値はコンピュータ上の「メモリ」と呼ばれる場所に保存されていることはすでに学びました。本章ではそれらの知識を前提により深く学習を進め、さらに「ポインタ」という概念に関して説明していきます。
オブジェクトとアドレス
「ポインタ」を理解するためには、まず「メモリ」と「アドレス」について
理解する必要があります。「メモリ」とは、コンピュータ上で数値や文字などの値を保存しておく装置のことです。一方、アドレスとは、メモリ上のどこに保存されているかを示す番地となります。C言語の規格では「その内容によって値を表現することができる実行環境中の記憶域の部分」のことを「オブジェクト」と呼びます。 つまり、値や型を持っていて、メモリに格納されているものが「オブジェクト」になります。
「変数に値を格納する」ということを、コインロッカーをイメージして考えてみましょう。ロッカーを利用して荷物を出し入れする際、必ずロッカー番号が設定されています。最初に空いているロッカーを探すことから始まります。すでに別の荷物が入っているロッカーに荷物を入れることはできません。空いているロッカーを見つけたらロッカーの番号を確認します。
ロッカーから荷物を取り出す際には、ロッカー番号を頼りに自分の荷物を探すことになります。もし、ロッカー番号がなかったとすると、大量にあるロッカーの中から自分の荷物を探すことは、大変な作業になります。
これはコンピュータにとっても同様であり、手がかりがないと大量のデータを保存できるメモリの中から必要な情報を取り出すことができません。コンピュータの場合は「アドレス」と呼ばれる、ロッカー番号に相当する数字を基準に探します。
アドレス演算子
今までは「メモリ上の何番のアドレスに変数が格納されているか」といったことを特に意識してプログラミングを行ってはいませんでした。実践的なプログラミングにおいてもアドレス番号自体を気にする必要はほとんどありません。本Lessonではアドレスという概念をより深く理解するために、数のアドレスを直接確認してみたいと思います。変数が格納されているアドレスを取得するためには、&演算子(アドレス演算子)と呼ばれる単項演算子を利用します。
アドレス演算子の構文
&変数名
上記の形で記述することにより、変数が格納されているアドレスを知ることが可能になります。では、実際に変数aを定義して、そのアドレスを表示するプログラムを書いて確認してみましょう。変換指定氏子は「%p」を使います。
pointer1.c
#include <stdio.h>
int main(void)
int a = 1;
puts("変数aの整数の値");
printf("%d\n", a);
puts("変数aのアドレスの値");
printf("%p\n", &a);
return 0;
}
実行結果
pointer1.exe
変数aの整数の値
1
変数aのアドレスの値
000000000061FE1C
メモリ上の「000000000061FE1C」というアドレスに変数aが格納されていることが分かります。この数字は使用している実行環境によって異なる数字が表示されます。形式も少し異なるかもしれませんが「000000000061FE1C」のように、16進法で出力されます。
もう1つ例を見てみましょう。変数aを定義した後、他の数字を代入して値を変更します。
pointer2.c
#include <stdio.h>
int main(void)
int a = 1;
puts("変数aのアドレスの値");
printf("%p\n", &a);
a = 8;
puts("変数aのアドレスの値");
printf("%p\n", &a);
return 0;
}
実行結果
pointer2.exe
変数aのアドレスの値
000000000061FE1C
変数aのアドレスの値
000000000061FE1C
実行結果より、変数aの値は変わっても、変数aの場所を示すアドレスは変わっていないことが確認できます。これは、プログラムの中で変数aの値が変更されても、その値を格納する場所は変更されないことを示しています。
ポインタ
メモリとアドレスの概念を理解したところで、「ポインタ」の学習を進めます。ポインタとは変数の1種で「アドレスの値を格納するための変数」となります。変数に関しては、今まで使ってきたように、数値や文字など、その変数の型に合わせて、さまざまな値を格納することができました。変数には、格納されている値のほかに「アドレス」という数字を持っていることも学習しました。「ポインタ」という特殊な変数を用いることで、ほかの変数が持つアドレスを格納することが可能です。
ポインタの参照外し
ポインタ変数も、ほかの変数と同様にあらかじめ宣言しておく必要があります。ただし、ほかの変数の宣言とは異なる部分があり、変数を宣言する際に変数名の前に*の記号をつける必要があります。
ポインタ変数の宣言
型 *変数名
実際にコードを書いた方が分かりやすいので、ポインタの宣言の例を紹介します。
ポインタ変数の宣言例
char *ptrA;
int *ptrB;
float *ptrC;
「*ポインタ名」でポインタの指しているアドレスを参照することができます。また、「*」をつけてポインタの中身を取り出すことを「参照外し」と呼びます。
ポインタ名には*がつかない
宣言のときには「*」をつける必要がありますが、生成されるポインタ名は「ptrA」のように*のつかない形となるので、この点に注意してください。
空ポインタ
「空ポインタ」とは、何も指していないポインタのことです。ポインタを使っていると、何も指していないポインタが必要になることがあります。例えば、この先にはもうデータがない(ここがデータの終点である)ことを表す場合などです。こんなときに役立つのが、「空ポインタ」(ヌルポインタ)です。ヌルポインタを作るには、ポインタにNULL (ヌル)または整数の0を格納します。NULLは標準ライブラリで定義されているマクロで、特定のヘッダファイル(stdio.h、stdlib.h、string.hなど)をインクルードすると使えます。NULLや0は、あらゆる型のポインタに格納できます。では、空ポインタを使ってみましょう。int*型のポインタptr1をNULLで初期化、int*型のポインタptr2を「0」で初期化した上で、 それぞれの変数のアドレスを表示するプログラムを書いてください。
nullpointer.c
#include <stdio.h>
int main(void)
int* ptr1 = NULL;
puts("変数ptr1のアドレスの値");
printf("%p\n", ptr1);
int* ptr2 = 0;
puts("変数ptr2のアドレスの値");
printf("%p\n", ptr2);
return 0;
}
実行結果
nullpointer.exe
変数ptr1のアドレスの値
0000000000000000
変数ptr2のアドレスの値
0000000000000000
voidへのポインタ
void型は変数宣言ができない、引数や戻り値がないことを表す型です。しかし、ポインタ変数である「void*型」は、さまざまな用途があります。
ポインタについて簡単に復習すると、ポインタも通常の変数同様に、宣言が必要です。この変数の宣言時には当然 「型」を指定します。C言語では変数を宣言するときに、必ず「型」を指定する必要があります。さらに、ポインタでは他の変数の「アドレス」を格納することができます。例えば int*型のポインタであれば、int型の変数を格納することができます。どんなデータであるか変数の宣言を行っているため、ポインタに異なる型の変数を格納してしまうと、その変数の型とは異なる扱われ方をしてしまう可能性があるので、コンパイル時に警告が出ます。実際にコードを書いてみます。
pointer3.c
#include <stdio.h>
int main(void)
puts("int型のポインタ変数ptrAの宣言");
int *ptrA;
puts("long型の宣言");
long a;
puts("int型のポインタ変数にlong型のアドレスを格納");
ptrA = &a;
return 0;
}
実際にコンパイルすると、警告が表示されてしまい、プログラムを実行することはできません。
void*型とは
「void*型」がどのような型であるか解説します。void*型は単にアドレスを格納する型になります。「どんなデータで、どんな型であるか」定義されていません。void*型変数には、どんな型の変数でも格納できます。こういった特性から、void*型は汎用ポインタと呼ばれることもあります。実際にコードを書いてみましょう。 void*型変数「vptr」にさまざまな型の変数のアドレスを格納しても、警告が表示されないことを確認します。変換指定子は「%p」を使います。
pointer4.c
#include <stdio.h>
int main(void)
void *vptr;
int a;
char b;
double c;
long d;
int *ptrA;
vptr = &a;
puts("変数aのアドレスの値");
printf("%p\n", vptr);
vptr = &b;
puts("変数bのアドレスの値");
printf("%p\n", vptr);
vptr = &c;
puts("変数cのアドレスの値");
printf("%p\n", vptr);
vptr = &d;
puts("変数dのアドレスの値");
printf("%p\n", vptr);
vptr = &ptrA;
puts("変数ptrAのアドレスの値");
printf("%p\n", vptr);
return 0;
}
実行結果
pointer4.exe
変数aのアドレスの値
000000000061FE14
変数bのアドレスの値
000000000061FE13
変数cのアドレスの値
000000000061FE08
変数dのアドレスの値
000000000061FE04
変数ptrAのアドレスの値
000000000061FDF8
全ての変数のアドレスを表示して確認することができました。「void*型」の詳細な用途については、本lessonの後半で解説します。
ポインタ変数の命名規則
「ptrA」のように、ポインタ変数の場合は頭に「ptr」を付けるなど、自分なりにポインタ変数を見分けることができるような命名規則を決めておくとよいと思います。そのようにすることで「&ptrA」のような間違ったコードを書いてしまう回数が減ります。また、ポインタ変数を宣言する記述方法については「int *ptr;」のほかに「int* ptr;」と記述することが可能です。ポインタに慣れるまでは「int *ptr;」のように各変数に*をつけて、見分けがつく宣言方法をおすすめします。

Lesson 7
Chapter 2
関数呼び出しとポインタ
ポインタとは、ある変数が格納されている「アドレス」を記憶しておくための変数であることを学習しました。では次に、ポインタに格納されているアドレスから、そこに格納されている変数の値を取得する方法を学習します。
C言語では「*(間接参照)演算子」と呼ばれる演算子を用いることで、ポインタ側からある変数に格納されている値を取得ことが可能です。
*(間接参照)演算子の構文
*ポインタ変数名
ポインタを関数に渡す
「*(間接参照)演算子」を使って、main関数から、引数を2つ持つptr関数を呼び出して、それぞれの引数の値が2倍になるようにptr関数の中で乗算を行い、結果を表示するプログラムを書いてみましょう。
pointer5.c
#include <stdio.h>
// ptr関数
void ptr(int *ptrX, int *ptrY) {
puts("計算前のポインタ変数ptrXとptrYの値を表示");
printf("ptrX: %d\n", *ptrX);
printf("ptrY: %d\n", *ptrY);
puts("ptr関数がポインタ変数に格納されている値を取得して乗算実行");
*ptrX = *ptrX * 2;
*ptrY = *ptrY * 2;
puts("計算後のポインタ変数ptrXとptrYの値を表示");
printf("ptrX: %d\n", *ptrX);
printf("ptrY: %d\n", *ptrY);
return;
}
// main関数
int main(void) {
int a = 2;
int b = 5;
int *ptrA;
int *ptrB;
puts("計算前の変数aと変数bの値を表示");
printf("a: %d\n", a);
printf("b: %d\n", b);
puts("ポインタ変数ptrAとptrBに変数aとbのアドレスを格納する");
ptrA = &a;
ptrB = &b;
puts("各変数のアドレスの値を表示");
printf("a: %p\n", a);
printf("b: %p\n", b);
printf("ptrA: %p\n", *ptrA);
printf("ptrB: %p\n", *ptrB);
puts("ポインタ変数のptrAとptrBを渡してpt関数呼び出し");
ptr(ptrA, ptrB);
puts("計算結果を表示");
printf("a: %d\n", a);
printf("b: %d\n", b);
printf("ptrA: %d\n", *ptrA);
printf("ptrB: %d\n", *ptrB);
return 0;
}
実行結果
pointer5.exe
(1)計算前の変数aと変数bの値を表示
a: 2
b: 5
(2)ポインタ変数ptrAとptrBに変数aとbのアドレスを格納する
(3)各変数のアドレスの値を表示
a: 0000000000000002
b: 0000000000000005
ptrA: 0000000000000002
ptrB: 0000000000000005
(4)pt関数を呼び出してポインタ変数のptrAとptrBを渡す
(5)計算前のポインタ変数ptrXとptrYの値を表示
ptrX: 2
ptrY: 5
(6)ptr関数がポインタ変数に格納されている値を取得して乗算実行
(7)計算後のポインタ変数ptrXとptrYの値を表示
ptrX: 4
ptrY: 10
(8)計算結果を表示
a: 4
b: 10
ptrA: 4
ptrB: 10
実行結果に表示した処理の流れは、以下のようになります。
(1) 計算前の変数aと変数bの値を表示する
(2) ポインタ変数ptrAとptrBに変数aとbのアドレスを格納する
(3) 各変数のアドレスの値を表示する
(4) pt関数を呼び出してポインタ変数のptrAとptrBを渡す
(5) 計算前のポインタ変数ptrXとptrYの整数の値を表示する
(6) ptr関数がポインタ変数に格納されている値を取得して乗算実行する
(7) 計算後のポインタ変数ptrXとptrYの整数の値を表示する
(8) 計算結果を表示する
(2)と(3)の結果から、ポインタ変数のptrAとptrBに変数aと変数bの「アドレス」が格納されているのが分かります。アドレスを表示すると、それぞれ「0000000000000002」と「0000000000000005」になっており、それぞれ同じアドレスを指しています。(4)で「ptr(ptrA, ptrB)」と記述して引数を渡します。ptr関数の引数ptrXとptrYは、ポインタ変数になっています。つまり「アドレス」が指す先はptr関数内でも同じになります。そして「*ptrX = *ptrX * 2;」で乗算を行います。ここで登場するのが「*(間接参照)演算子」です。*演算子を使って乗算を行い、結果をポインタ変数に再代入しています。このタイミングで変数aと変数bの値も2倍になったことが理解できたでしょうか。*演算子による計算は、ポインタにより取得したアドレスに格納されている値を2倍にすると、変数aとbも同じアドレスを指してるため、値が2倍になります。(8)で計算結果を確認してみると、変数aとbの値も、2倍になっていることが分かります。
「ポインタ」は、少し難しい印象を受けたかと思います。処理の流れは分かっても、C言語の記述が複雑なため、コードが読みにくいのです。なので、ポインタを使った計算の仕組みや記述方法を理解して、苦手意識を持たないように学習を進めましょう。

Lesson 7
Chapter 3
ポインタと配列
Lesson4で「配列」について学習しましたが、本Lessonで学習している「ポインタ」と、非常に密接な関係にあります。ここでは配列とポインタやアドレスの関係について学習します。
ポインタと配列
C言語の中で配列のアドレスがどのように扱われているのか、今までに学習した内容を用いて、改めて確認してみましょう。int型の配列stockを要素数5で初期化するプログラムを書きます。
pointer6.c
#include <stdio.h>
int main(void) {
int stock[5] = {92, 77, 85, 63, 98};
printf("stock[0]のサイズ: %d\n", sizeof stock[0]);
printf("stock[0]のアドレス: %p\n", &stock[0]);
printf("stock[1]のアドレス: %p\n", &stock[1]);
printf("stock[2]のアドレス: %p\n", &stock[2]);
printf("stock[3]のアドレス: %p\n", &stock[3]);
printf("stock[4]のアドレス: %p\n", &stock[4]);
printf("stockのアドレス: %p\n", stock);
printf("stockのアドレス: %p\n", stock + 1);
printf("stockのアドレス: %p\n", stock + 2);
printf("stockのアドレス: %p\n", stock + 3);
printf("stockのアドレス: %p\n", stock + 4);
return 0;
}
実行結果
pointer6.exe
stock[0]のサイズ : 4
stock[0]のアドレス: 000000000061FE00 ← 下2桁「00」 同じアドレス
stock[1]のアドレス: 000000000061FE04 ← 下2桁「04」(先頭から+4)
stock[2]のアドレス: 000000000061FE08 ← 下2桁「08」(先頭から+8)
stock[3]のアドレス: 000000000061FE0C ← 下2桁「0C」(先頭から+12)
stock[4]のアドレス: 000000000061FE10 ← 下2桁「10」(先頭から+16)
stockのアドレス : 000000000061FE00 ← 下2桁「00」 同じアドレス
このプログラムは、5つの配列要素を持つ配列stockを初期化式により作成して、stock[0]からstock[4]までの各配列要素が格納されているアドレスを、&演算子を使って表示したものになります。また、実行結果より、各配列要素のアドレスは4つずつ値がずれていることがわかります。16進数で表示されており、stock[4]は先頭から「+16」で桁が繰り上がり、下2桁が「10」になっています。この4という数字は、int型変数のサイズです。配列の場合、各配列要素のアドレスはメモリ上で、必ず隣接しています。
注目ポイントは、stock[0]のアドレスと、配列名stockのアドレスが同じであることです。これはC言語の仕様であり、配列名である「stock」のアドレスは「配列の先頭要素へのポインタに暗黙的型変換」が行われるためです。なので、「stock」のアドレスと、配列の先頭要素である「stock[0]」のアドレスは同じアドレスになります。
間接演算子と添字演算子
&stock[1]のように、各配列要素の前に&を付けることによって、値が格納されているアドレスを知ることができます。配列の場合は、配列名の演算によっても各配列要素のアドレスを知ることが可能です。配列の演算のコードの例を確認してみましょう。
pointer7.c
#include <stdio.h>
int main(void) {
int stock[5] = {92, 77, 85, 63, 98};
printf("&stock[0] のアドレス: %p\n", &stock[0]);
printf("stock のアドレス: %p\n", stock);
puts("");
printf("&stock[1] のアドレス: %p\n", &stock[1]);
printf("stock+1 のアドレス: %p\n", stock + 1);
puts("");
printf("&stock[2] のアドレス: %p\n", &stock[2]);
printf("stock+2 のアドレス: %p\n", stock + 2);
puts("");
printf("&stock[3] のアドレス: %p\n", &stock[3]);
printf("stock+3 のアドレス: %p\n", stock + 3);
puts("");
printf("&stock[4] のアドレス: %p\n", &stock[4]);
printf("stock+4 のアドレス: %p\n", stock + 4);
return 0;
}
実行結果
pointer7.exe
stock[0] のアドレス: 000000000061FE00
stock のアドレス: 000000000061FE00
stock[1] のアドレス: 000000000061FE04
stock+1 のアドレス: 000000000061FE04
stock[2] のアドレス: 000000000061FE08
stock+2 のアドレス: 000000000061FE08
stock[3] のアドレス: 000000000061FE0C
stock+3 のアドレス: 000000000061FE0C
stock[4] のアドレス: 000000000061FE10
stock+4 のアドレス: 000000000061FE10
上記の結果から、配列の演算でもアドレスを取得できることが分かりました。
また、変数の場合と同様に、配列要素もアドレスの惰報から格納されている値を取得することが可能です。使う演算子も変数の場合と同様に、間接演算子*を利用します。早速、間接演算子を使って、アドレスから格納されている値を取得するプログラムを書いてみましょう。記述する上での注意点は「*stock + 1」と書いてしまうと「*stock」の部分が先に計算されてしまうので「*(stock + 1)」と()で囲む必要があります。
pointer8.c
#include <stdio.h>
int main(void) {
int stock[5] = {92, 77, 85, 63, 98};
printf("stock[0] の値: %d\n", stock[0]);
printf("stock の値: %d\n", *stock);
puts("");
printf("stock[1] の値: %d\n", stock[1]);
printf("*(stock+1) の値: %d\n", *(stock + 1));
puts("");
printf("stock[2] の値: %d\n", stock[2]);
printf("*(stock+2) の値: %d\n", *(stock + 2));
puts("");
printf("stock[3] の値: %d\n", stock[3]);
printf("*(stock+3) の値: %d\n", *(stock + 3));
puts("");
printf("stock[4] の値: %d\n", stock[4]);
printf("*(stock+4) の値: %d\n", *(stock + 4));
return 0;
}
実行結果
pointer8.exe
stock[0] の値: 92
stock の値: 92
stock[1] の値: 77
stock+1 の値: 77
stock[2] の値: 85
stock+2 の値: 85
stock[3] の値: 63
stock+3 の値: 63
stock[4] の値: 98
stock+4 の値: 98
ポインタによる配列要素の操作
C言語では、配列の要素数がコンパイル時に決定しなければいけません。そのような場合は「malloc()」を使うことで、実行時に必要なサイズの配列を確保し、かつそれを継続して保持し続けることができます。最初にscanf関数を使用して配列のサイズを入力します。そのサイズだけの配列を「malloc()」で確保しています。その配列に値をセットして配列の内容を表示します。コードの例を確認してみましょう。
pointer9.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 要素数を可変にするためのポインタ変数
int *data;
// 配列の要素数
int num;
// 配列の要素番号
int e;
printf("配列の要素数を入力してください");
scanf("%d", &num);
// malloc関数でメモリー領域を動的確保する
data = (int*)malloc(sizeof(int) * num); // int型のスペースを要素数の分だけ確保
puts("配列に格納する値を入力してください");
// 要素数の分だけループする
for (e = 0; e < num; e++) {
printf("data[%d]の入力:", e);
scanf("%d", &data[e]); // 整数値の入力
}
puts("配列に格納された値");
// 要素数の分だけループする
for (e = 0; e < num; e++) {
printf("data[%d] = %d\n", e, data[e]); // 整数値の出力
}
// メモリー領域の解放
free(data);
return(0);
}
実行結果
pointer9.exe
配列の要素数を入力してください5
配列に格納する値を入力してください
data[0]の入力:32
data[1]の入力:46
data[2]の入力:76
data[3]の入力:12
data[4]の入力:76
配列に格納された値
data[0] = 32
data[1] = 46
data[2] = 76
data[3] = 12
data[4] = 76
「malloc関数」を使用するため「#include <stdlib.h>」を定義しています。「malloc関数」を使うことで、動的メモリ確保が可能になります。詳細は次のChapterで解説します。
関数間の多次元配列の値渡し
多次元配列をmain関数から別の関数に渡したいとき、引数はどのように記述すればいでしょうか。コードのイメージは以下のようになります。「?」の仮引数の部分を考えてみましょう。
pointer10.c
#include <stdio.h>
// 2次元配列を表示する関数
void dispArray(int ? ) {
…
return;
}
// main関数
int main(void) {
int stock[3][5] = {{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}};
dispArray(stock);
return(0);
}
正解は以下の書き方になります。
(*stock)[5]
「(*stock)[5]」という記述は「(サイズが5の配列)のポインタ」ということを示しています。2次元配列がメモリ上に格納される場合も、配列は密接した状態でアドレス上に格納されます。つまり、配列の開始地点となるアドレスを渡せばよいことになります。
pointer11.c
#include <stdio.h>
// 2次元配列を表示する関数
void dispArray(int (*stock)[5]) {
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 5; j++) {
printf("%d ", stock[i][j]);
}
puts("");
}
return;
}
// main関数
int main(void) {
int stock[3][5] = {{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}};
disparray(stock);
return(0);
}
実行結果
pointer11.exe
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15

Lesson 7
Chapter 4
オブジェクトの動的な生成
自動記憶域期間と静的記憶域期間
変数には記憶域期間と呼ばれる生存期間があります。
「自動記憶域期間」の変数は、ブロックに入ったときに生存期間が始まり、ブロックから出たときに生存期間が終了します。ここで「ブロックから出る」というのは、ブロックの末尾に到達した場合だけではなく、ブロックの途中でreturn文を使って呼び出し元に戻った場合なども含みます。いずれの場合も、関数内で宣言した変数は、呼び出し元に戻ったときには生存期間が終わっています。同じ関数をもう一度呼び出しても、前回の呼び出しで変数に格納した値は失われているので、改めて初期化や代入が必要になります。
「静的記憶域期間」の変数は、プログラムの起動時(main関数を実行する前)に生存期間が開始され、プログラムの終了時に生存期間が終了します。プログラムの実行中は、変数に格納した値が保持されます。「静的記憶域期間」の変数を初期化した場合は、プログラムの起動時に初期化されます。初期化しなかった場合は、自動的に0で初期化されます。
動的記憶域期間
C言語では「動的記憶域期間」や「割付け記憶域期間」と呼ばれています。「割付け記憶域期間」を持つ変数は、主にmalloc関数によって割り付けられたメモリ内で値を保持します。free関数によって解放されると生存期間が終了します。ソースコード上のブロックなどとは完全に独立した記憶域機関を持つため、静的記憶域間と同じく関数やブロックから抜けても勝手になくなることはありません。その代わり、自分で明示的に解放しないといけないので、解放忘れや二重解放といった不具合につながりやすい欠点もあります。
配列オブジェクトの動的生成
動的メモリ確保とは、ポインタの重要な用途の一つです。動的メモリ確保とは、プログラムの実行時にサイズ(バイト数)を指定して、メモリを確保する機能です。この機能は、実行時にならないと必要なメモリのバイト数が分からない場合に役立ちます。
malloc関数
動的にメモリを確保するには、malloc (エムアロック、マロック)関数を使います。malloc関数を使うには、標準ライブラリのstdlib.hをインクルードします。
プログラムの実行時まで要素数が決まらない配列を使いたいときには、動的メモ確保が役立ちます。次のように書くと、指定した型と要素数を持つ配列に相当するバイト数のメモリを確保し、アドレスをポインタに格納できます。このポインタに間接演算子や添字演算子を組み合わせて、配列の要素を読み書きします。
配列用のメモリを確保(初期化)
型* ポインタ名 = malloc(sizeof (型) * 要素数);
配列用のメモリを確保(代入)
ポインタ = malloc(sizeof (型) * 要素数);
実行時に配列の要素数を決めるプログラムを書いてみましょう。キーボードから入力した要素数を持つint型の配列を、動的メモリ確保を使って作成した上で、キーボードから入力した複数の整数を格納し、それらの値を出力するプログラムを言いてください。
pointer12.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// 要素数を入力
int count;
printf("count: ");
scanf("%d", &count);
// メモリを確保
int* p = malloc(sizeof(int)*count);
// 値を入力してメモリに格納
for (int i = 0; i < count; i++) scanf("%d", p + i);
// アドレスと値を出力
for (int i = 0; i < count; i++) printf("%p %d\n", p + i, p[i]);
// メモリを解放
free(p);
return 0;
}
実行結果
pointer11.exe
count: 5
345
623
824
213
456
0000000000646DD0 345
0000000000646DD4 623
0000000000646DD8 824
0000000000646DDC 213
0000000000646DE0 456
上記のプログラム例では、確保したメモリのアドレスをポインタpに格納します。配列の要素については、アドレスは「p+i」で、値は「p[i]」(あるいは「*(p+i)」、「*(i+p)」、「i[p]」)で得られます。最後にfree関数を使って、確保したメモリを解放します。
malloc関数
確保したメモリのサイズを変更するには、realloc (リアロツク)関数を使います。realloc関数は次のように使います。引数には、malloc関数などで確保したメモリのアドレスを格納したポインタと、変更後のバイト数を指定します。
確保したメモリのサイズを変更
ポインタ = realloc (ポインタ,バイト数)
メモリリーク
C言語においては、malloc関数で確保したメモリをfree関数で解放し忘れた場合、メモリリークが起きます。ただしプログラムが終了した場合には、多くのOSはプログラムが使っていたメモリを解放するので、あまり問題になりません。問題になるのは、メモリリークが起きたまま、プログラムの実行が続く場合です。プログラマは「プログラムがメモリの確保と解放を繰り返すので、使用メモリ量の最大値は無難な値に収まる」という見込みでも、解放が適切に行われないと、使用メモリ量が増大することがあります。空きメモリが不足して、プログラムやOSの性能を落としてしまうかもしれません。メモリリークヘの対策は、動的に確保したメモリを忘れずに解放することが基本です。
