アドレス
備考 : 変数や関数等がメモリに保存されている場所
#include <stdio.h>
int main()
{
int tmp = 0;
// &(アンパサンド)を宣言後の変数の前につけると、
// アドレスが確認できる
printf("変数 tmp のアドレス : %p\n", &tmp);
return 0;
}
ポインタ
用途 : アドレス(変数や関数等がメモリに保存されている場所)を格納する
- 宣言
型 * 変数名;
- 定義
変数名 = アドレス(&変数);
- アドレスに格納されている値
*変数名
配列とポインタ
#include <stdio.h>
int main()
{
int iArray[5] = { 10, 8, 6, 4, 2, };
// 配列の添え字番号を指定しない場合、
// 変数は先頭のアドレス(今回はiArray[0])になる
// ex)iArray == &iArray[0]
int* p = iArray;
// 配列の先頭から順に要素のアドレスと値を出力
for (int i = 0; i < 5; i++, p++)
{
printf("アドレス : %p\n", p);
printf("値 : %d\n", *p);
}
// 配列の先頭から、数字 * 型のサイズ分(iArray[0 + 数字])アドレスの値を出力
printf("配列[0 + 数字] の値 : %d\n", *(iArray + 数字));
return 0;
}
構造体とポインタ
#include <stdio.h>
int main()
{
struct Test
{
int a;
int b;
char c;
float d;
bool e;
};
struct Test t;
Test* pTest = &t;
pTest->a = 0;
pTest->b = 10;
pTest->c = '0';
pTest->d = 0.0f;
pTest->e = true;
return 0;
}
※構造体のポインタは、.(ドット)ではなく、->(アロー)を用いる
アドレス渡し
関数は通常、渡された引数の値をコピーして関数内に渡します(値渡し)。
ただし、関数内で引数の値を変更したい場合は、アドレス渡しを用います。
#include <stdio.h>
/// <summary>
/// アドレスに格納されている値を1増やす
/// </summary>
/// <param name="_tmp">tmpのポインタ</param>
void Add(int* _tmp)
{
*_tmp += 1;
}
int main()
{
int tmp = 10;
// アドレス渡し
Add(&tmp);
printf("tmp = %d\n", tmp);
return 0;
}
参照(C++のみ)
型& 変数名
int a = 10;
int &b = a; // bがaと同じメモリ領域にアクセス
b++; // aが11になる
抽象的に言うと、bとaは一心同体
参照渡し(C++、C#のみ)
- 備考
引数に型& 変数名
を定義して、変数のメモリ上のアドレスを渡すことで、
関数内でその変数の値を直接変更できるようにする手法。
アドレス渡しよりも手順が簡潔、かつ、アドレスの誤操作がなくなるため、
安全性が高くなる。 - C++
型 関数名(型& 変数名)...
- C#
型 関数名(ref 型 変数名)...
メモリ領域(動的確保の前に...)
-
グローバル領域・スタティック領域
グローバル変数やstatic(静的)な変数
が保存される -
スタック領域
自動変数
(関数内で宣言される変数等)が保存される -
ヒープ領域
動的確保
によって確保される
※ヒープ領域はプログラマー自身が明示的に解放しない限り確保され続ける
解放を忘れると解放せずに終了(メモリーリーク)を起こす
また、解放したアドレスをプログラム上で触るとエラー
が起きる
動的確保(C/C++のみ)
用途 : プログラマー自身で実行中のメモリを柔軟に管理する
ex)スコープからの独立、複数の関数でメモリ領域の共有、メモリ領域を必要分のみ確保etc...
mallocとfree
#include <stdio.h>
#include <stdlib.h>
int main()
{
// 動的確保
// malloc : 確保されたメモリのアドレスをvoidポインタとして返す
int* iPtr = (int*)malloc(sizeof(int));
char* str = (char*)malloc(sizeof(char) * 32);
if (iPtr == NULL)
{
printf("memory error");
return -1;
}
*iPtr = 10;
printf("%d\n", *iPtr);
if (str == NULL)
{
printf("memory error");
return -1;
}
strcpy_s(str, 32, "Hello World");
printf("%s\n", str);
// メモリ領域の解放
free(str);
free(iPtr);
return 0;
}
newとdelete(C++、C#)
#include <iostream>
int main()
{
// 動的確保
// new : 確保したオブジェクトのポインタを返す
int* iPtr = new int;
char* str = new char[32];
if (iPtr == nullptr)
{
printf("memory error");
return -1;
}
*iPtr = 10;
std::cout << *iPtr << std::endl;
strcpy_s(str, 32, "Hello World");
std::cout << str << std::endl;
// メモリ領域の解放
delete[] str; // 配列なので delete[]
delete iPtr; // 単一の要素なので delete
return 0;
}
※C#の場合、ガベージコレクションがあるので、deleteは不要
スマートポインタ(C++11以上のみ)
ポインタからの変更点 : メモリ管理の自動化(メモリリークの防止)
スマートポインタには主に以下の三種類を指す
- std::unique_ptr(ユニークポインタ)
- std::shared_ptr(シェアードポインタ)
- std::weak_ptr(ウィークポインタ)
インクルード(#include)
#include <memory>
std::unique_ptr / std::shared_ptr共通
- 宣言
std::〇〇_ptr<型> 変数名;
- 定義
変数名.reset(new 型(値));
変数名.reset(new 型[要素数]); // 配列対応(C++17以降)
変数名 = std::make_〇〇<型>(値); // std::make_uniqueはC++14、std::make_sharedはC++11から
- 宣言・定義
std::〇〇_ptr<型> 変数名(new 型(値));
// 配列
std::unique_ptr<型[]> 変数名(new 型[要素数]);
std::shared_ptr<型> 変数名(new int[要素数], std::default_delete<int[]>());
std::unique_ptr<型[]> 変数名 = std::make_unique<int[]>(要素数); // 明示的な書き方(C++14以降)
std::shared_ptr<型[]> 変数名(new int[要素数]); // 配列対応(C++17以降)
std::shared_ptr<型[]> = std::make_shared<int[]>(10); // 明示的な書き方(C++20以降)
// テンプレート型の推論補助(C++17以降)
std::〇〇_ptr 変数名 = std::make_〇〇<型>(値);
- アドレスの取得
変数名.get();
- 明示的に解放(書かなくても良い)
変数名.reset();
std::unique_ptr
備考 : あるメモリに対する所有権を持つポインタが、ただ一つであることを保証するスマートポインタ
// 所有権の譲渡
// ※uPtr〇はstd::unique_prt型とする
uPtr2 = std::move(uPtr1);
std::shared_ptr
備考 : 同一のメモリの所有権を複数で共有できるようにしたスマートポインタ
// 共有数の取得
// ※sPtrはstd::shared_prt型とする
sPtr.use_count();
循環参照
std::shared_ptrのメモリ解放
を行う場合、共有数が0ではない場合、解放できない。
それに伴い、所有権を相互的に参照
(循環参照)した場合、メモリリークが発生する。
std::weak_ptr
用途 : 循環参照を防ぐ
※循環参照を防ぐ安全性よりパフォーマンスを求める場合、生ポインタを推奨
(オーバーヘッド等の理由により、生ポインタよりはパフォーマンスが下がるため)
#include <iostream>
#include <memory>
int main()
{
// 弱参照
std::shared_ptr<int> sPtr = std::make_shared<int>(10);
std::weak_ptr<int> wPtr(sPtr);
// 参照先のメモリが解放されていないなら
if (!wPtr.expired())
{
// 所有権を共有する
std::shared_ptr<int> sPtrTmp = wPtr.lock();
}
// 明示的な参照の終了(書かなくても良い)
wPtr.reset();
return 0;
}
スマートポインタの参考記事
コメント
ポインタの仕様は複雑怪奇ですので、
何かご意見あれば、どしどしコメントお願いします!