C# メモリと値型と参照型と


さて今回はメモリと値型と参照型ということで。
メモリに関してはなんとなく知ってるよ〜でもなんだかむつかしい…なんて方、少なくないと思います。まぁ僕なんですけど。
というわけでいきましょう!(進行下手くそか)

PCの主要パーツ

PCの最も重要なパーツのひとつで、他にはCPUやハードディスクドライブ(HDD、現在はSSD:ソリッドステートドライブが主流?)といった、PC好きな方なら一度は聞いたことのあるものです。
最近はGPUというパーツがどんどん出てきていて、PCの性能がさらに大幅UP!て感じです。
PCオタクとは程遠い僕からすれば、処理性能がだいぶ違うのはわかる!でもベンチマークとかよくわからん!とりあえずすげーのはよーくわかったぞ!という感じ
こういう話題には惹かれるんですけどね、知人との会話の中で凄さはわかるぞと。プログラムなどに興味関心を持っているためか、ハードウェア技術の進歩に置いてかれています。勉強します。

若干の違いはあるかもしれませんが、CPUは頭脳、メモリは机、ハードディスクは引き出しなんて例えられます。

ハードディスク

ハードディスクは、テレビの録画/保存や据え置き型ゲーム機に接続して使用されるなどで、この中では割と有名な方かと思います。
僕はなぜかHDDを2台、SSDを2台持っています(すべて外付け)。そんなに持つほど使ってないです。宝の持ち腐れ。
家電量販店やネットショップなどで、256GBとか512GBとか1TBとか書かれているやつは、大体HDDやSSDだと思ってください。
SSDの価格としてはHDDよりも高く、512GBで2万円とかします。
財布を溶かします。

CPU

CPUはIntel Coreなんちゃら〜だったりAMD Ryzenなんちゃら〜と言われるものですね。
CPUが違うだけでPCの性能も変わります(当然メモリも大事)。
最近ではApple社がM1チップなどでApple社製品の性能を格段に上げていますが、アプリやソフトが未対応だったりすることがあるので、現段階ではとりあえずめちゃめちゃすげーやつってだけです。(僕の見解ですよ)
というのも、僕自身M1チップ搭載のMacBookを使用しているのですが、まぁまぁ大変なんですよ。
開発したいから環境構築する!うまくいかねぇ…Windowsでやろ…というのが割とあります。
仮に開発してコンパイルまでできても、CPUの違い(M1搭載Macとintel搭載Mac)で動かない!なんてことがあります。(知人との体験談)

GPU

グラフィックボードやビデオカードなんて呼ばれ方があります。
プログラミングでは機械学習や深層学習などで重宝するイメージ。あとはゲームや動画編集ですかね?
GPUを使用した場合と使用しない場合で、処理速度が全然違います。
例えば、Excel操作はCPUのみで十分ですが、動画編集などの場合にはCPUだけだと下手したら編集ソフトが落ちたりします。
そんな時に処理速度を補ってくれるのがGPUの役割という感じでしょうか。
また、最近のノートPCには、内蔵されているもの(CPUに組み込まれている?)も多くあります。
ハードディスクと同様、外付けのビデオカードもあるようですが、個人的には「外付けGPU買うくらいならPCを新調(もしくはデスクトップPCを購入)した方が…」とも思います。
詳しくは知らないのでGPUについては僕の個人的な感想程度に捉えてください。

メモリ

さてさてお待たせしました、今回のメインであるメモリのお話です。
先ほどは机と例えたメモリですが、一体どんな役割でどういった処理を行なっているのか。
イメージとして、まずは机を思い浮かべてみてください。
そしてその机をもっと広くした机を想像してみてください。
最初にイメージした机と後にイメージした机、どちらの方が作業しやすいでしょうか。
当然後にイメージした机の方が作業しやすいかと思います。
これがメモリです。
狭い机だと道具を使い終わったらしまって、使う時に持ってきて〜というのが繰り返されます。
これが広い机だと、使わなくても置いておけば、「持ってくる」という工程を飛ばしてまた使えます。
楽ですよね〜。これがメモリの簡単なイメージです。

本題!メモリの役割やら詳細やら

ここからはネット記事や基本情報技術者試験の参考書などから情報を得てお伝えしていきたいと思います。
基本情報って、基本とは言いつつ案外難しいものです。目に見えないお話なのでイメージが湧きにくいからでしょうか。
アウトプットとしてこの内容を取り上げた記事も書かないと…

ROM

Read Only Memoryの各単語の頭文字を取った略称です。
読み出し(読み込み)専用のメモリ。電源を切っても記憶された内容が消えない「不揮発性」と言われる特性があります。
最近では書き込めるタイプのものもあるとか… Read Onlyどこ行った…

PROM

Programmable ROM
書き込めるタイプのROMのこと。
種類が複数あり、紫外線で記憶を消去するタイプ(どういう仕組み…?)や電圧をかけて記憶を消去するタイプがあるようです。
その中でも聞いたことがありそうなのはフラッシュメモリですかね?

RAM

こちらもRandom Access Memoryという各単語の頭文字を取った略称。
※余談ですが、僕はRead And Memoryだと思っていた時期がありました(意味は間違っていない…?)。
読み書きできるもので、電源を切ると記憶していた内容が消えてしまう「揮発性」という特性を持っています。
RAMには種類があります。↓

DRAM

Dynamic RAM
コンデンサに電荷を備えた状態かどうかで1ビットを表現します。
構造が簡単で高集積化に適していることから、次に紹介するSRAMに比べて大容量で安価。
コンデンサは放置していると自然放電してしまう特性を持っているため、一定時間ごとに記憶内容を維持する動作(リフレッシュ:再読み込み)が必要です。
主記憶に用いられます。

SRAM

Static RAM
特殊な回路(フリップフロップ回路)で構成されています。高速ですが構造が複雑で集積度を高めにくいため、DRAMと比べて容量は少なく高価。
しかし、電源がついている限りは記憶した内容を保ち続けられるため、先述のリフレッシュ動作が不要となります。
キャッシュメモリに用いられています。

ひとまずROMとRAMはこの辺にしておきますね。
基本情報を取り扱う時に詳しくご説明します。

メモリとプログラムの関係

最近の言語はメモリを意識せずに書けてしまうとのこと。実際に僕自身はあまり気にしてきませんでした。
複雑な処理をする場合にはやはりメモリを意識しないと動作や処理に支障を及ぼすようなので、規模の大きい開発では気にする必要がありそうです。
自分の考えたロジックやプログラムでどのようにメモリで処理が行われているのか、いずれ意識して書かないといけない場面が出てくることを想定して頭の奥の片隅にしまい込んでください。「あの初心者がなんかの記事であんなこと言ってたな」くらいでいいです。

言語によって差はありますが、プログラムを実行した際に自動で型を判定してメモリに確保されています。
型や長さを表すビット、値を示すビットが2進数で記憶されます。
言語やこちらの記事(C#ですが)などで紹介した型によって入力できるメモリ領域が確保される感じです。

これが文字列になると、データ(文字列)の長さに合わせてメモリを確保することになります。
string型は参照型データとなるため、データを保存した領域を参照=そのメモリ領域に格納されている値を示すということになりますね。
変数に格納された文字数が多ければ多いほどメモリ領域が圧迫され、int型などの値型変数になると、決まったメモリ領域が圧迫されるといった感じです。

格納方法

int型などの値型データは、変数を表すメモリ領域に対して直接格納されるためプリミティブ型と呼ばれます。
対する参照型データは、格納した領域のアドレスを格納する型のため、オブジェクト型と呼ばれます。
ややこしいし、オブジェクト型とobject型があるのね…

クラスを使用した場合

以前ご紹介したクラスの説明ではクラスの中にメソッドや変数(フィールド)を書き、インスタンス化しないと使えないとお伝えしました。
クラスはインスタンス化した時に初めて使えるようになります。
今回のテーマに沿って言い換えるなら、「インスタンス化した時にメモリが確保される」ということです。
変数を扱うにはメモリ領域を確保(変数を宣言しておけば自動的に行われるのであまり意識しないけど)する必要がありますが、クラスを使用する際も同じようにインスタンス化をすることでメモリ領域の確保が行われるということですね。
変数の宣言 = クラスの定義 + クラスのインスタンス化ということだと思ってください。

メモリの解放?

これらのデータはメモリ領域が解放されない限り、メモリ領域を占有(圧迫)している状態です。
他のソフトやプログラムが動作している時にPCが重たい… そんな時はこれが原因かもしれません。
先ほどのRAMで紹介した揮発性という特徴を利用して、PCを再起動すれば解決!かもしれませんが、手間がかかります。
なので、プログラム内で「メモリを解放」してやるとそんな手間は省けるわけです。

メモリの解放は思っているよりも簡単

C#には、値型のみですが使わなくなったメモリ領域を自動的に解放してくれる機能があります。
※構造体(現時点ではあまり紹介してないです)は値型ですが、構造体の中に参照型のデータが含まれている場合はメモリの解放処理が必要になります。
じゃあ参照型は?
これだ… 1,2,3!()

C#の参照型データのメモリ解放
Class.Dispose();

これだけです。
※Classはインスタンス化されていることを前提とします。
クラス単位ではなく、クラス内の変数(フィールド)単位でもメモリの解放を行うことができますが、長くなるので別記事で詳しく取り上げましょうかね…
C#では、Disposeメソッドを使わなくてもメモリを解放する方法は他にもあります。これも別記事にて。

値型と参照型のメモリ管理の違い

そもそもの領域について

メモリ領域については「下位アドレス」やら「上位アドレス」というものがあり、上位アドレスから下位アドレスに向かって「スタック領域」「ヒープ領域」「スタティック領域」という領域に分けられています。

先ほどお伝えしたように、値型は使わなくなったメモリ領域を自動的に解放してくれる機能があります。
値型データはメモリの中でも「スタック領域」を使用します。
スタック領域に格納された値データは、メソッドの終了時に破棄(解放)してくれるので、解放処理が必要ありません。
また参照型はスタック領域とスタティック領域の間にある「ヒープ領域」に格納されます。
ヒープ領域は自動で解放処理してくれる訳ではないため、参照型のデータを扱う場合はプログラム内で解放処理が必要だったりします。

値型と参照型を使い分ける理由

そもそも論、格納できるデータの内容に違いがあるから、というのは大前提であるのですが、このメモリの内容を絡めた理由もしっかりあります。

単純なデータ構造であれば値型

参照型データを扱うにはさまざまなコスト(メモリの解放処理や処理速度)が発生します。
値型を格納するスタック領域は処理速度が早いため、C#(他言語同様)の変数やクラスにおけるメモリ領域の特性を理解できたなら、値型を使う方がメリットが多いのかなと僕は思います。

複雑なデータ構造なら参照型

参照型は膨大なデータ量(メモリ領域)になりやすく、仮に値型にデータを格納してスタック領域でのメモリを確保できたとしても、データ量が大きければ話は変わってきます。スタック領域は処理速度が早いとは言えど、データのサイズが大きいと重くなることもあるんですね。
そこで、ヒープ領域を確保する参照型のデータにするわけです。
string型は参照型の文字列データ、char型は値型の文字データです。
例えば、"hoge"というstring型データと、"h","o","g","e"という4つのchar型データで考えてみると、参照型であるstring型データの方が取り出しやすそうなイメージが湧くかと思います。僕はそういう解釈です。(自信ない)
またややこしい話になるので簡潔にお伝えしますが、値型データひとつの確保サイズと、参照型データひとつの確保サイズを考えると、参照型データで定義した方が効率が良い場面もあります。

char型はスタック領域に2バイト、参照型データの確保領域はスタック領域に8バイトのアドレスデータとヒープ上に値のデータを置く形になります。
※C言語ではchar型は1バイトとされていますが、C#では日本語を扱えるように2バイトとされています。
以前の記事ではアドレスデータを参照して値を受け取るみたいな説明をしたのですが、その部分です。
今回は例が悪いので違いはさほどない、むしろchar型の方が効率良さそうです。(早口言葉とかだったらわかりやすいかも)

結論、特性を理解していれば値型も参照型も一長一短であることはわかります。

最後に

情報量が多いですね。別分野も混ざってきました。
ただ、メモリは重要になる部分かなと個人的に思っています。
正しい記事を目指してはいますが、初心者なので大目にみてやってください。
今回もしんどかった〜笑

コメントくださっている方!
大変嬉しいです。ありがとうございます。