CやC++を扱う上で避けて通れない「ポインタ」。
ポインタのポインタ(ダブルポインタ)とかも出てきて、学びたての頃は何がなんだか・・・
筆者はずっとC#で開発を行ってきたのでメモリ管理を細かく意識したことはなく、たまにC++でコーディングする時とかに未だに苦戦します。
この記事では、備忘録がてらに、ポインタ、ダブルポインタを用いて、メモリ確保(malloc)を行うサンプルを紹介したいと思います。
ダブルポインタの使い方を学びたい・思い出したい方は読んでみてください。
ダブルポインタとmalloc
メモリを確保した後に、確保領域に整数を書き込むという処理を行ってみます。
サンプルコードを追ってみていきます。
#include <iostream>
// 【③】引数のダブルポインタ
// (ダブルポインタはポインタのアドレスを値として持つことができる)
void MemoryAllocation(int** pp) {
// 【④】 int型3つ分のメモリ領域を確保し、その先頭アドレスを③に代入
// (int*) とあるのは型変換で、そのメモリ領域を添え字で見ていくときにintのデータサイズごとになる
*pp = (int*)malloc(sizeof(int) * 3);
// 【⑤】 ④で代入した先頭アドレスを表示
std::cout << *pp << std::endl; // 015CF860
// 【⑥】 ④の領域に、3個のint型のデータを代入していく。
(*pp)[0] = 111;
(*pp)[1] = 222;
(*pp)[2] = 333;
// 【⑦】値を出力
std::cout << (*pp)[0] << std::endl; // 111
std::cout << (*pp)[1] << std::endl; // 222
std::cout << (*pp)[2] << std::endl; // 333
// 【⑧】アドレスを出力
std::cout << &(*pp)[0] << std::endl; // 015CF860
std::cout << &(*pp)[1] << std::endl; // 015CF864
std::cout << &(*pp)[2] << std::endl; // 015CF868
}
int main()
{
// 【①】 int型のポインタを宣言
int* p;
// 【②】 int型のポインタ変数(①)のアドレスを関数に渡す
MemoryAllocation(&p);
// 【⑨】
std::cout << p << std::endl; // 015CF860
std::cout << p + 1 << std::endl; // 015CF864
std::cout << p + 2 << std::endl; // 015CF868
// 【⑩】
std::cout << *p << std::endl; // 111
std::cout << *p + 1 << std::endl; // 222
std::cout << *p + 2 << std::endl; // 333
}
【①】int型ポインタ変数 p を宣言します。
【②】宣言したポインタ変数のアドレスを関数に渡します。変数のアドレスを表すためには、アンパサンド(&)を変数の頭につけます。
【③】関数の引数にはint型ダブルポインタ変数の pp が待ち構えています。ダブルポインタは、「ポインタのポインタ」という言われ方もします。その名の通りポインタ変数のアドレスを持つことができます。ここでは、①のポインタ変数のアドレスが入ります。
【④】int型変数3つ分のメモリ領域を確保し、その先頭アドレスを③が持つアドレス先のポインタ変数に代入します。つまり、①のポインタ変数に代入されたということになります。
【⑤】そのアドレスを確認してみると、015CF860でした。
【⑥】④で確保した3つ分の領域に、1コずつ、整数を代入していきます。ここでカッコを付けずに「*pp[0] = 111;」と書くと、ダブルポインタ自体のアドレスを指すようになり、確保されていないメモリ領域を参照することになるので間違いです。今ここで参照したいのはダブルポインタが持つポインタのアドレス先です。
【⑦】値を出力します。ちゃんと代入した値が入っています。
【⑧】アドレスも出力します。int型のサイズである4byteずつ、連番のアドレスが振られていることがわかります。先頭アドレスが⑤と同じになっていることもわかります。
【⑨】さて、関数を抜けたので①の値を見てみます。ちゃんと⑧と同じ結果になっており、参照渡しに成功したことがわかります。
【⑩】ついでに①のアドレスの先の値も見てみましょう。ちゃんと⑥で代入した値が入っていますね。
以上です。なかなかややこしいですね。
今回の場合、普通のポインタはアドレス先の値に整数が入っていますが、ダブルポインタの場合はアドレス先の値にアドレスが入ってると考えるとイメージしやすいです。
int** pp;
int* p;
int num = 999;
p = #
pp = &p;
std::cout << *p << std::endl; // 999
std::cout << p << std::endl; // 00BEFD64
std::cout << *pp << std::endl; // 00BEFD64
この違いさえ理解しておけばなんとかコードを追えるようになると思います。
コメント