変数とメモリ空間

Loading

前回までで、データを変数に格納して使うことを覚えたと思います。
しかし、「データを変数に格納する」とは具体的には何をしていることなのでしょう?
ここでは、変数とメモリ空間の関係について説明し、値の取得・代入などで何が行われているかを一歩踏み込んで理解しましょう。

目次

メモリ空間

PCや、スマホやタブレットなどのモバイル端末には「メモリ1」という装置が備わっています。
メモリは主記憶装置といって、データを一時的に保存しておく短期記憶機能を持っています。
詳しくは下記記事を参照してください。

メモリは物理的には電気的な0、1の信号でなんらかのデータを保持する仕組みです。
しかし、プログラムの実行動作を機械の動作から把握するのはプログラマーには困難です。

そのため、メモリの仕組みを考えるときは「それぞれに番号が振られたたくさんのデータを入れられる箱」を例え話に使うことが一般的です。

データを格納する箱をメモリセルと言います。プログラム上で記述したデータはメモリセルに(0、1の電気信号に変換されて)保持されます。
メモリセルに振られた番号をメモリアドレスと言います。メモリセルを互いに区別するための位置を表すためにアドレスと言われています。

メモリ空間の概念図
メモリ空間は、データを格納するメモリセルと、メモリセルに割り振られたメモリアドレスで構成されている

変数への代入

このメモリ空間の概念図を利用して、変数とデータがどのようにメモリに格納されるかを確認しましょう。

let x = 100;

JavaScriptがこのコードを実行すると、PCの内部では次のようなことが起きます。

  1. 変数xをメモリセルの一つに割り当てる(例えば1番地)
  2. 値100をメモリセルの一つに割り当てる(例えば4番地)
  3. 変数xのメモリセルに、値100のメモリアドレス(4番地)が格納される

これを図式化したものが下記の図になります。

変数と値の格納
変数には値が格納されてるメモリアドレスを保持している

JavaScriptが変数xを取得する際は

  1. 変数xが保持しているデータへのメモリアドレス(4番地)を参照する
  2. 参照先の4番地に入っている「100」を取得する

という手順を踏んで値を取得します。

console.log( x ); // xを呼び出すと、参照先(4番地)に格納されたデータを取得する
> 100

このようにメモリ空間の観点から眺めると、変数は値への参照を保持しているということになります。

変数のコピー:データがプリミティブ型の場合

では、下記の例ではメモリ空間の値はどうなっているでしょうか?

let x = 100;
let y = x;

このとき、メモリ空間では次のようなことが行われています。

  1. 変数xと値100がメモリセルに格納される(例えば1番地に変数、4番地に値)
  2. 変数yをメモリセルの一つに割り当てる(例えば2番地)
  3. 変数xを変数yに代入すると、変数xの持っていたメモリアドレス(4番地)が変数yのメモリセルにコピーされる

これを図示すると下記のようになります。

変数のコピー
yにxを代入すると、参照メモリアドレスがコピーされる

このようにして、変数yは変数xと同じ値「100」を参照するようになります。
この仕組みにより、プログラマーから見ると、変数xの値が変数yにコピーされたように見えます。

console.log( y );
> 100

とこで、変数のコピーがこのような仕組みだとすると、次の場合、xの値はどうなるでしょう?

y++;
console.log( x );
console.log( y );

ここで「y++」というのは、「yの値を1増やしてyに再代入」という演算を行う演算子です(詳しくは別の記事で解説します)。
これまでの話だと、yはxと値を「共有」しています。
そうすると、yの値を変更すると、xの値も一緒に変わってしまうのでしょうか?

いいえ、そうはなりません。上記の(1)の結果は以下のようになります。

100 (xの値)
101 (yの値)

ちゃんと別々の値になっていますね。

これは次のような仕組みでメモリ空間が変更されるからです。

  • yの参照先のデータ100に+1されて新たなデータ「101」が別のメモリセル(5番地)に保持される
  • yの参照先が5番地に変更される
変数yの値変更はxの値に影響しない
「101」が新たに別のメモリセルに作成され、yの参照先が切り替わる

こうなることで、xとyで別々の値が保持されるわけです。

変数のコピー:データがオブジェクトの場合

以上、データがプリミティブ型の場合についてメモリ空間の様子を見てきました。

しかし、データがオブジェクトの場合についてはちょっと注意が必要です。
以下のようなオブジェクトと変数のコピーについて考えてみましょう。

let person1 = {
  name: '太郎',
  age: 25
};

let person2 = person1; ・・・(1)

person2.age = 26; ・・・(2)

// それぞれの値はどうなっているか?
console.log( person1 ); 
console.log( person2 );

まず、(1)の段階では、メモリ空間は以下のようになっています。

(1) オブジェクトのメモリ空間への格納

ここで注目すべきはオブジェクトのプロパティnameとageについてです。
図を見ると、プロパティも変数と同様にそれぞれメモリセルが割り当てられ、値への参照(メモリアドレス)が保持されています。

ここで、(2)のようにperson2のageの値を変更してみましょう。
その時のメモリ空間の様子は以下の図です。

(2) ageの値を変更

person2が参照しているデータは、3番地に格納されたオブジェクトです。
そのオブジェクトのプロパティageは、新しいデータを代入されると同時に参照先が切り替わります。
これで、person2.ageの値は「26」へと変更されます。

ここで注意してほしいのは、person1の参照先は引き続き3番地のオブジェクトを指し続けていることです。
このとき、person1、person2の値はどうなっているでしょうか?

console.log( person1 );
> { name: '太郎', age: 26 }

console.log( person2 );
> { name: '太郎', age: 26 }

なんと、person1ともperson2とも同じ内容になってしまいました。

これはperson1とperson2の参照先が引き続き同じであり、オブジェクトの内容だけが変更されたことが原因です。
このように、プリミティブ型のデータとオブジェクトのデータでは、変数のコピーの挙動に差が出てくることに注意しましょう。

これを避けるためには、各変数毎に別のオブジェクトを作成する必要があります。

// person1 と person2 に別々のオブジェクトを作成する
let person1 = {
  name: '太郎',
  age: 25
};

let person2 = {
  name: '太郎',
  age: 25
};

// person2のageだけ変更
person2.age = 26;

// person1とperson2で別々の値になる
console.log( person1 );
> { name: '太郎', age: 25 }

console.log( person2 );
> { name: '太郎', age: 26 }

このように別々のオブジェクトを作成すれば、person1とperson2で別々のオブジェクトを参照することになり、各変数ごとに変更の影響を押さえられます。
後の記事で説明するnew演算子によるオブジェクトの生成を行えば、同じオブジェクトを多数生成することができます。
くわしくはクラスの解説までお待ち下さい。

まとめ

今回は変数とメモリ空間の関係について解説いたしました。
メモリ空間に格納されたデータとアドレスの参照はJavaScriptを深く理解するためにとても重要な事項です。
理解できるまでしっかり学習しましょう。

次回は演算子について解説していきます。

脚注

  1. RAM(Random Access Memory)とも呼ばれる。