Rustをはじめよう その4(所有権・前提知識編)

三菱総研DCS デジタル企画推進部の加藤です。 この記事では、プログラミング言語Rust(ラスト)の所有権を理解するための前提知識として、他のプログラミング言語のメモリ管理の仕組みをまとめます。

前書き

記事概要

  • この記事では、所有権を理解するための前提知識として、他のプログラミング言語のメモリ管理の仕組みをご紹介します。
  • CやC++など手動でメモリを解放する言語と、JavaやPythonなどガベージコレクションによるメモリ管理を行う言語の両方の知識・実装経験がある方は、次回の記事からお読みいただければと思います。

他のプログラミング言語のメモリ管理の仕組み

このセクションでは、他のプログラミング言語のメモリ管理の仕組み(手動で行う方法、ガベージコレクションによる方法)を紹介し、これらの問題点を説明します。

手動で行う方法

メモリの確保や確保したメモリの解放の命令を、開発者自身がプログラム上に記述する方法です。この方法はCやC++などのプログラミング言語で採用されています。
具体的な方法としては、各プログラミング言語で決められた文法に則って、メモリ確保・メモリ解放のコードを開発者が都度記述します。例えば、C++では以下のサンプルコードのようにnew/deleteといった命令を使用して、メモリの確保/解放を行います。

[C++のコード]

  int *p;    // int型のポインタ(メモリ上の場所を表す)変数を宣言

  p = new int[100];    // int型の配列(サイズは10)のメモリを確保

  // 配列を使用した処理を実行

  delete[] p;    // メモリの解放


この方法は、開発者がメモリ確保/解放のタイミングを実装時に決めることができるため、適切に実装すれば効率よくメモリを使用できるとともに、後述のガベージコレクションによるメモリ管理よりも高速に動作するというメリットがあります。
その一方で、開発者がメモリ確保/解放の処理を記述するため、誤った実装の仕方をしてしまう可能性があり、これによって以下の問題が発生します。

  • メモリリーク: 確保したメモリを解放し忘れることで、プログラムの動作に不要なメモリ領域が占有され続ける実装ミスです。プログラムの性能劣化や、メモリ不足によるプログラムの機能停止を引き起こすことがあります。
  • メモリの二重解放: 一度解放したメモリを、再度解放してしまう実装ミスです。C++の場合であれば、一度deleteしたポインタ変数をもう一度deleteする場合に発生します。
  • ダングリングポインタの使用: 解放済みのメモリ領域を参照するポインタ(=ダングリングポインタ)を使用してしまう実装ミスです。解放済みのメモリ領域にはどのようなデータが存在するか不定のため、プログラムがどのような動作を取るのか予想することができません。

上記の問題は、基本的には開発者が注意深く実装することでしか回避できません。そのため、開発が煩雑になると同時に、プログラムが上記の問題を含んでいないことを担保できないケースが多いです。さらに、いずれの問題も、発生してしまった場合にはプログラムの予期せぬ動作を引き起こす可能性があります。

ガベージコレクション(GC)による方法

上述の通り、メモリ管理を手動で行う方法は開発者の実装に依存するところが大きく、また開発の手間がかかるものでした。そのため、この方法に代わって、確保したメモリ領域を自動的に解放する仕組みが考案されました。これがガベージコレクション(GC)です。ガベージコレクションはJavaやPythonなどのプログラミング言語で採用されています。
ガベージコレクションではガベージコレクタという動作主体がプログラムの実行を監視しており、確保済みのメモリ領域の要/不要を判断し、不要となったメモリ領域を自動で解放します。

ガベージコレクションは自動的に実行されるため、開発者がメモリを解放するためのコードを書く必要性は原則としてありません。そのため、メモリ管理を手動で行う場合に発生する問題(メモリリーク、メモリの二重解放、ダングリングポインタの使用)を未然に防止できます(実装の仕方によってはこれらの問題が発生する場合があります)。
その一方で、ガベージコレクタによるメモリ領域の要/不要の判断には、相応の処理リソースが必要です。また、あるメモリ領域が真に不要となってから、ガベージコレクタにより解放されるまでにはタイムラグが発生する場合があり、その間は不要なメモリが確保され続けるメモリリークの状態となってしまいます。さらに、ガベージコレクタの実行タイミングはプログラミング言語側の仕様に依存するため、不要となったメモリ領域が解放されない理由を特定することは一般に困難です。

以上のことから、ガベージコレクションはメモリの解放を自動で行うため、メモリ管理を手動で行う場合に発生しがちな問題を回避できるメリットがあると言えます。その一方で、ガベージコレクタの動作のために余分なリソースを用意することが必要であり、不要となったメモリ領域が即時解放されるとは限らないため、プログラムの実行効率が悪かったり、予測できない挙動を取ったりするというデメリットがあります。
さらに、ガベージコレクションを備えたプログラミング言語であっても、プログラムが開いたファイルやネットワークコネクションなどは、その都度解放するためのコードを記述する必要があります。これを忘れてしまうと、リソースリークによる問題(ファイルが開かれ続けることで、他のプログラムからファイルが編集できなくなるなど)が発生します。

メモリ管理の仕組みについてのまとめ

ここまで、Rust以外のプログラミング言語におけるメモリ管理の仕組みをご説明しました。手動でメモリ管理を行う方法、ガベージコレクションのいずれもメリット・デメリットがあること、また、いずれの方法であっても不正な動作やパフォーマンス劣化を引き起こしうることがご理解いただけたと思います。
念のため補足しますが、これらのメモリ管理の仕組みであっても、プログラムが適切に設計・実装・運用されていれば、基本的に問題は発生しません。しかし、プログラムのコード量や開発者の数が増えれば増えるほど、何らかの問題が発生する可能性が高まることも事実です。

Rustでは、ここまでに紹介した2つの方法とは異なる方法でメモリを管理します。この方法の核となる仕組みが、所有権システム(または単に所有権)です。
次回の記事では、Rustの所有権の仕組みについて紹介し、その考え方や実装方法、メリットや注意点についてご説明します。

まとめ

本記事では、Rustの所有権を理解するための前提知識として、他のプログラミング言語のメモリ管理の仕組みについて紹介しました。
今回はRustに直接関係のない内容が多く恐縮ですが、いずれも所有権の仕組みとその重要性を理解するためにも重要な内容です。ぜひとも理解していただきたく思います。

参考文献

  • 「The Rust Programming Language: 2nd Edition」, https://doc.rust-jp.rs/book-ja-pdf/book.pdf 2020 年 2 月 26 日アクセス
  • Jim Blandy, Jason Orendorff 著, 中田 秀基 訳(2018)『プログラミングRust』 オライリー・ジャパン
  • κeen,河野 達也,小松礼人(2019)『実践 Rust 入門[言語仕様から開発手法まで]』 技術評論社
その3 |  その4 | その5