はじめに
この実験では、C++ における動的メモリ割り当ての扱い方を学びます。まずは new 演算子を使ってメモリを割り当て、その後 delete 演算子でメモリを解放します。また、動的配列の作成、shared_ptr や unique_ptr のようなスマートポインタの使用、およびメモリリークの確認についても学びます。これらのスキルは、C++ プログラミングにおける効果的なメモリ管理に不可欠です。
この実験では、以下の主要なステップがカバーされます。new 演算子を使ったメモリ割り当て、delete 演算子を使ったメモリ解放、new[] を使った動的配列の作成、delete[] を使った配列の削除、shared_ptr スマートポインタの実装、排他的な所有権のための unique_ptr の使用、メモリリークの確認、およびメモリ割り当て失敗の処理。
new 演算子を使ってメモリを割り当てる
このステップでは、C++ における new 演算子を使って実行時に動的にメモリを割り当てる方法を学びます。動的メモリ割り当てにより、プログラム実行中にサイズが決定される変数や配列を作成でき、静的メモリ割り当てと比べてより柔軟性が高くなります。
WebIDE を開き、~/project ディレクトリに dynamic_memory.cpp という新しいファイルを作成します。
touch ~/project/dynamic_memory.cpp
dynamic_memory.cpp に以下のコードを追加します。
#include <iostream>
int main() {
// new を使って整数型のメモリを動的に割り当てる
int* dynamicInteger = new int;
// 動的に割り当てた整数型に値を代入する
*dynamicInteger = 42;
// 動的に割り当てた整数型の値を表示する
std::cout << "Dynamically allocated integer value: " << *dynamicInteger << std::endl;
// 動的に割り当てた整数型のメモリアドレスを表示する
std::cout << "Memory address: " << dynamicInteger << std::endl;
return 0;
}
重要な概念を解説しましょう。
int* dynamicInteger = new int;:new演算子は整数型のメモリを割り当てます。- 割り当てられたメモリへのポインタを返します。
- ヒープ上に変数を作成し、スタック上ではなくなります。
*dynamicInteger = 42;:- 参照演算子
*を使って値を代入します。 - 動的に割り当てたメモリに値 42 を格納します。
- 参照演算子
コードをコンパイルして実行します。
g++ dynamic_memory.cpp -o dynamic_memory
./dynamic_memory
実行結果の例:
Dynamically allocated integer value: 42
Memory address: 0x55f4e8a042a0
new 演算子に関する重要なポイント:
- 実行時に動的にメモリを割り当てます。
- 割り当てられたメモリへのポインタを返します。
- メモリはヒープ上に割り当てられます。
- サイズは実行時に決定できます。
- より柔軟なメモリ管理が可能になります。
注:次のステップでは、この動的に割り当てたメモリを適切に解放してメモリリークを防ぐ方法を学びます。
delete 演算子でメモリを解放する
このステップでは、C++ において delete 演算子を使って動的に割り当てたメモリを適切に解放する方法を学びます。メモリを解放しないとメモリリークが発生し、プログラムのパフォーマンスに問題を引き起こす可能性があります。
WebIDE を開き、~/project ディレクトリに memory_release.cpp という新しいファイルを作成します。
touch ~/project/memory_release.cpp
memory_release.cpp に以下のコードを追加します。
#include <iostream>
int main() {
// 整数型のメモリを動的に割り当てる
int* dynamicInteger = new int;
// 動的に割り当てた整数型に値を代入する
*dynamicInteger = 42;
// メモリを解放する前の値を表示する
std::cout << "Value before release: " << *dynamicInteger << std::endl;
// delete を使って動的に割り当てたメモリを解放する
delete dynamicInteger;
// 削除後にポインタを nullptr に設定する
dynamicInteger = nullptr;
// 安全なポインタの使用を示す
if (dynamicInteger == nullptr) {
std::cout << "Memory has been successfully released" << std::endl;
}
return 0;
}
delete 演算子に関する重要なポイント:
delete dynamicInteger;:- 以前
newで割り当てたメモリを解放します。 - メモリリークを防ぎます。
- メモリをシステムに解放します。
- 以前
dynamicInteger = nullptr;:- 削除後にポインタを
nullptrに設定します。 - 解放されたメモリの誤った使用を防ぎます。
- 追加のセーフティチェックを提供します。
- 削除後にポインタを
コードをコンパイルして実行します。
g++ memory_release.cpp -o memory_release
./memory_release
実行結果の例:
Value before release: 42
Memory has been successfully released
重要なメモリ管理ルール:
newで割り当てたメモリに対しては常にdeleteを使用します。- 削除後にポインタを
nullptrに設定します。 - 削除された後のポインタを使用しないでください。
- 各
newには対応するdeleteが必要です。
new[] を使って動的配列を作成する
このステップでは、C++ において new[] 演算子を使って動的配列を作成する方法を学びます。動的配列を使うことで、実行時に複数の要素用のメモリを割り当てることができ、静的配列よりも柔軟性が高くなります。
WebIDE を開き、~/project ディレクトリに dynamic_array.cpp という新しいファイルを作成します。
touch ~/project/dynamic_array.cpp
dynamic_array.cpp に以下のコードを追加します。
#include <iostream>
int main() {
// 動的配列のサイズを宣言する
int arraySize = 5;
// new[] を使って動的な整数型配列を作成する
int* dynamicArray = new int[arraySize];
// 配列要素を初期化する
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// 配列要素を表示する
std::cout << "Dynamic Array Contents:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
return 0;
}
動的配列に関する重要なポイント:
int* dynamicArray = new int[arraySize];:- 整数型の配列用のメモリを割り当てます。
arraySizeが要素数を決定します。- メモリはヒープ上に割り当てられます。
- サイズは実行時に決定できます。
- 配列の初期化とアクセス:
- 標準的な配列インデックス
dynamicArray[i]を使用します。 - 通常の配列と同じように操作できます。
- 実行時の条件に基づいて動的なサイズ調整が可能です。
- 標準的な配列インデックス
コードをコンパイルして実行します。
g++ dynamic_array.cpp -o dynamic_array
./dynamic_array
実行結果の例:
Dynamic Array Contents:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
重要な留意点:
- 動的配列はメモリ割り当てにおいて柔軟性を提供します。
- サイズは実行時に決定できます。
- 割り当てには
new[]を、解放にはdelete[]を使用することを忘れないでください。 - 常に割り当てと解放の方法を一致させましょう。
delete[] を使って配列を削除する
このステップでは、delete[] 演算子を使って動的配列に割り当てられたメモリを適切に解放する方法を学びます。適切なメモリ管理は、メモリリークを防ぎ、効率的なリソース利用を確保するために重要です。
WebIDE を開き、~/project ディレクトリに delete_array.cpp という新しいファイルを作成します。
touch ~/project/delete_array.cpp
delete_array.cpp に以下のコードを追加します。
#include <iostream>
int main() {
// 動的配列のサイズを宣言する
int arraySize = 5;
// new[] を使って動的な整数型配列を作成する
int* dynamicArray = new int[arraySize];
// 配列要素を初期化する
for (int i = 0; i < arraySize; ++i) {
dynamicArray[i] = i * 10;
}
// 削除前の配列要素を表示する
std::cout << "Array Contents Before Deletion:" << std::endl;
for (int i = 0; i < arraySize; ++i) {
std::cout << "Element " << i << ": " << dynamicArray[i] << std::endl;
}
// delete[] を使って動的に割り当てた配列を解放する
delete[] dynamicArray;
// 削除後にポインタを nullptr に設定する
dynamicArray = nullptr;
// 安全なポインタの使用を示す
if (dynamicArray == nullptr) {
std::cout << "Dynamic array has been successfully deleted" << std::endl;
}
return 0;
}
動的配列の削除に関する重要なポイント:
delete[] dynamicArray;:- 配列全体のメモリを適切に解放します。
new[]で割り当てた配列には必ずdelete[]を使用します。- メモリリークを防ぎます。
- 各配列要素のデストラクタを呼び出します。
dynamicArray = nullptr;:- 削除後にポインタを
nullptrに設定します。 - 解放されたメモリへの誤ったアクセスを防ぎます。
- 追加のセーフティチェックを提供します。
- 削除後にポインタを
コードをコンパイルして実行します。
g++ delete_array.cpp -o delete_array
./delete_array
実行結果の例:
Array Contents Before Deletion:
Element 0: 0
Element 1: 10
Element 2: 20
Element 3: 30
Element 4: 40
Dynamic array has been successfully deleted
重要なメモリ管理ルール:
new[]で割り当てた配列には常にdelete[]を使用します。deleteとdelete[]を混同しないでください。- 削除後にポインタを
nullptrに設定します。 - 各
new[]には対応するdelete[]が必要です。
スマートポインタ shared_ptr を実装する
このステップでは、C++ 標準ライブラリのスマートポインタである shared_ptr について学びます。これは自動的なメモリ管理と参照カウントを提供します。スマートポインタはメモリリークを防ぎ、メモリ管理を簡素化します。
WebIDE を開き、~/project ディレクトリに shared_pointer.cpp という新しいファイルを作成します。
touch ~/project/shared_pointer.cpp
shared_pointer.cpp に以下のコードを追加します。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : data(value) {
std::cout << "Constructor called. Value: " << data << std::endl;
}
~MyClass() {
std::cout << "Destructor called. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
int main() {
// MyClass への shared_ptr を作成する
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(42);
// 同じオブジェクトを指す別の shared_ptr を作成する
std::shared_ptr<MyClass> ptr2 = ptr1;
// 参照カウントを表示する
std::cout << "Reference Count: " << ptr1.use_count() << std::endl;
// shared_ptr を通じてオブジェクトにアクセスする
std::cout << "Data from ptr1: " << ptr1->getData() << std::endl;
std::cout << "Data from ptr2: " << ptr2->getData() << std::endl;
// ポインタがスコープ外になると自動的にメモリを解放する
return 0;
}
shared_ptr に関する重要なポイント:
std::make_shared<MyClass>(42):- 動的メモリ割り当てを伴う shared ポインタを作成する。
- コンストラクタ引数でオブジェクトを初期化する。
newとshared_ptrの個別の作成よりも効率的。
ptr1.use_count():- 同じオブジェクトを指す shared_ptr インスタンスの数を返す。
- オブジェクトの参照カウントを追跡するのに役立つ。
- 自動的なメモリ管理:
- オブジェクトを参照する shared_ptr がなくなると、メモリが自動的に解放される。
- 手動のメモリ管理エラーを防ぐ。
コードをコンパイルして実行します。
g++ -std=c++11 shared_pointer.cpp -o shared_pointer
./shared_pointer
実行結果の例:
Constructor called. Value: 42
Reference Count: 2
Data from ptr1: 42
Data from ptr2: 42
Destructor called. Value: 42
重要な shared_ptr の特性:
- 自動的なメモリ管理
- 参照カウント
- メモリリークを防ぐ
- スレッドセーフな参照カウント
排他的な所有権のために unique_ptr を使用する
このステップでは、動的に割り当てられたリソースの排他的所有権を提供するスマートポインタである unique_ptr について学びます。shared_ptr とは異なり、unique_ptr は一度に 1 つのポインタのみがリソースを所有できることを保証します。
WebIDE を開き、~/project ディレクトリに unique_pointer.cpp という新しいファイルを作成します。
touch ~/project/unique_pointer.cpp
unique_pointer.cpp に以下のコードを追加します。
#include <iostream>
#include <memory>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created. Value: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed. Value: " << data << std::endl;
}
int getData() const { return data; }
private:
int data;
};
void processResource(std::unique_ptr<Resource> resource) {
std::cout << "Processing resource with value: " << resource->getData() << std::endl;
// 関数が終了すると Resource は自動的に削除されます
}
int main() {
// unique_ptr を作成する
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>(42);
// リソースデータを表示する
std::cout << "Resource value: " << ptr1->getData() << std::endl;
// std::move を使って所有権を移す
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// ptr1 は現在 nullptr になっています
if (ptr1 == nullptr) {
std::cout << "ptr1 is now null after moving ownership" << std::endl;
}
// unique_ptr を関数に渡す (所有権が移されます)
processResource(std::move(ptr2));
return 0;
}
unique_ptr に関する重要なポイント:
std::make_unique<Resource>(42):- 動的メモリ割り当てを伴うユニークポインタを作成します。
- リソースの排他的所有権を保証します。
std::move(ptr1):- リソースの所有権を移します。
- 元のポインタは
nullptrになります。 - 同じリソースを複数のポインタが所有することを防ぎます。
- 自動的なメモリ管理:
- unique_ptr がスコープ外になるとリソースが自動的に削除されます。
- 手動のメモリ管理は不要です。
コードをコンパイルして実行します。
g++ -std=c++14 unique_pointer.cpp -o unique_pointer
./unique_pointer
実行結果の例:
Resource created. Value: 42
Resource value: 42
ptr1 is now null after moving ownership
Processing resource with value: 42
Resource destroyed. Value: 42
重要な unique_ptr の特性:
- 排他的所有権
- 自動的なメモリ管理
- コピーできず、移動のみ可能
- リソースリークを防ぐ
メモリリークを確認する
このステップでは、強力なメモリデバッグツールである Valgrind を使って、C++ におけるメモリリークを検出し防止する方法を学びます。メモリリークは、動的に割り当てられたメモリが適切に解放されない場合に発生し、プログラムが増え続けるメモリを消費します。
まず、ターミナルで Valgrind をインストールします。
sudo apt update
sudo apt install -y valgrind
WebIDE で ~/project ディレクトリに memory_leak.cpp を作成して、メモリリークの例を作成します。
touch ~/project/memory_leak.cpp
memory_leak.cpp に以下のコードを追加します。
#include <iostream>
class Resource {
public:
Resource(int value) : data(value) {
std::cout << "Resource created: " << data << std::endl;
}
~Resource() {
std::cout << "Resource destroyed: " << data << std::endl;
}
private:
int data;
};
void createMemoryLeak() {
// これはメモリリークを引き起こします。リソースが削除されていません。
Resource* leak = new Resource(42);
// 欠落:delete leak;
}
int main() {
// 複数のメモリリークをシミュレートします。
for (int i = 0; i < 3; ++i) {
createMemoryLeak();
}
return 0;
}
デバッグシンボル付きでプログラムをコンパイルします。
g++ -g memory_leak.cpp -o memory_leak
Valgrind を実行してメモリリークを検出します。
valgrind --leak-check=full./memory_leak
Valgrind の出力の例:
==1988== Memcheck, a memory error detector
==1988== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1988== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==1988== Command:./memory_leak
==1988==
Resource created: 42
Resource created: 42
Resource created: 42
==1988==
==1988== HEAP SUMMARY:
==1988== in use at exit: 12 bytes in 3 blocks
==1988== total heap usage: 5 allocs, 2 frees, 73,740 bytes allocated
==1988==
==1988== 12 bytes in 3 blocks are definitely lost in loss record 1 of 1
==1988== at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1988== by 0x109241: createMemoryLeak() (memory_leak.cpp:19)
==1988== by 0x109299: main (memory_leak.cpp:26)
==1988==
==1988== LEAK SUMMARY:
==1988== definitely lost: 12 bytes in 3 blocks
==1988== indirectly lost: 0 bytes in 0 blocks
==1988== possibly lost: 0 bytes in 0 blocks
==1988== still reachable: 0 bytes in 0 blocks
==1988== suppressed: 0 bytes in 0 blocks
==1988==
==1988== For lists of detected and suppressed errors, rerun with: -s
==1988== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
ご覧の通り、Valgrind は 3 つのブロックにおける 12 バイトのメモリリークを検出しました。createMemoryLeak() で作成された Resource オブジェクトが削除されておらず、メモリリークを引き起こしています。
メモリリークを修正するには、コードを変更してリソースを適切に削除します。
void createMemoryLeak() {
// 動的に割り当てられたリソースを適切に削除する
Resource* leak = new Resource(42);
delete leak; // この行を追加してメモリリークを防止する
}
メモリリークが修正されたことを確認するために、再度コンパイルして Valgrind でプログラムを実行します。
g++ -g memory_leak.cpp -o memory_leak
valgrind --leak-check=full./memory_leak
==2347== Memcheck, a memory error detector
==2347== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2347== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2347== Command:./memory_leak
==2347==
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
Resource created: 42
Resource destroyed: 42
==2347==
==2347== HEAP SUMMARY:
==2347== in use at exit: 0 bytes in 0 blocks
==2347== total heap usage: 5 allocs, 5 frees, 73,740 bytes allocated
==2347==
==2347== All heap blocks were freed -- no leaks are possible
==2347==
==2347== For lists of detected and suppressed errors, rerun with: -s
==2347== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
メモリリークに関する重要なポイント:
- 常に動的に割り当てられたメモリを
deleteする unique_ptrやshared_ptrのようなスマートポインタを使用する- Valgrind を使ってメモリリークを検出する
- より良いデバッグ情報を得るために
-gフラグ付きでコンパイルする
メモリ割り当て失敗を処理する
このステップでは、C++ においてメモリ割り当て失敗をどのように対処するかを学びます。動的メモリ割り当てが失敗すると、new 演算子は std::bad_alloc 例外を投げます。この例外をキャッチして適切に処理することができます。
WebIDE を開き、~/project ディレクトリに memory_allocation.cpp という新しいファイルを作成します。
touch ~/project/memory_allocation.cpp
memory_allocation.cpp に以下のコードを追加します。
#include <iostream>
#include <new>
#include <limits>
void demonstrateMemoryAllocation() {
try {
// 大量のメモリを割り当てようとする
const size_t largeSize = 1000000000000; // 1 兆個の整数
int* largeArray = new int[largeSize];
// 割り当てに失敗した場合、この行は実行されません
std::cout << "Memory allocation successful" << std::endl;
// 割り当てたメモリを解放する
delete[] largeArray;
}
catch (const std::bad_alloc& e) {
// メモリ割り当て失敗を処理する
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}
}
void safeMemoryAllocation() {
// 例外を投げないように std::nothrow を使用する
int* safeArray = new(std::nothrow) int[1000000];
if (safeArray == nullptr) {
std::cerr << "Memory allocation failed silently" << std::endl;
return;
}
// 割り当てたメモリを使用する
std::cout << "Safe memory allocation successful" << std::endl;
// 割り当てたメモリを解放する
delete[] safeArray;
}
int main() {
std::cout << "Demonstrating memory allocation failure handling:" << std::endl;
// 方法 1: 例外処理を使用する
demonstrateMemoryAllocation();
// 方法 2: std::nothrow を使用する
safeMemoryAllocation();
return 0;
}
メモリ割り当て失敗の対処に関する重要なポイント:
std::bad_allocによる例外処理:try-catchブロックがメモリ割り当て例外をキャッチする- 詳細なエラー情報を提供する
- プログラムクラッシュを防ぐ
std::nothrowによる割り当て:- 例外を投げない
- 割り当て失敗時に
nullptrを返す - サイレントな失敗処理を可能にする
コードをコンパイルして実行します。
g++ memory_allocation.cpp -o memory_allocation
./memory_allocation
実行結果の例:
Demonstrating memory allocation failure handling:
Memory allocation failed: std::bad_alloc
Safe memory allocation successful
重要なメモリ割り当て戦略:
- 常に割り当て失敗をチェックする
- 例外処理または
std::nothrowを使用する - フォールバックメカニズムを実装する
- 極めて大きなメモリブロックの割り当てを避ける
まとめ
この実験では、C++ における動的メモリ割り当ての扱い方を学びます。まず、実行時に動的にメモリを割り当てるために new 演算子を使う方法を学びます。これにより、静的割り当てと比較して、より柔軟なメモリ管理が可能になります。次に、メモリリークを防ぐために、delete 演算子を使ってこの動的に割り当てられたメモリを適切に解放する方法を学びます。また、new[] を使って動的配列を作成し、delete[] で削除する方法についても学びます。この実験では、メモリ管理を簡素化し、一般的な落とし穴を避けるための shared_ptr や unique_ptr などのスマートポインタの使用方法も扱います。最後に、メモリ割り当て失敗をチェックして対処する技術を学びます。



