Rubyの変数と参照値渡し
AOJでRubyで競技プロの問題解いてて、Rubyってメモリ消費が多いんだなーてことを実感。Rubyのオブジェクトがどれくらいメモリを消費しているのか確認できる方法調べてみました。
require 'objspace' rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] num = 1 p ObjectSpace.memsize_of(num) # => 0 p ObjectSpace.memsize_of(num) + rvalue_size # => 40 str = 'a' p ObjectSpace.memsize_of(str) # => 40 p ObjectSpace.memsize_of(str) + rvalue_size # => 80 array = [] p ObjectSpace.memsize_of(array) # => 40 p ObjectSpace.memsize_of(array) + rvalue_size # => 80 hash = {foo: '1'} p ObjectSpace.memsize_of(hash) # => 232 p ObjectSpace.memsize_of(hash) + rvalue_size # => 272
しかし、確認しようと
ObjectSpace.memsize_of()
をたたいてIntegerを確認してみると 0 という数字が返ってびっくりです。これはCレベルで最適化され、オブジェクトが生成されないほげほげ...とのことです(次回調べてみます)。
オブジェクトが生成されないということで、以下のようなことが起こるみたいです。(今回の本題)
Rubyの変数は値渡しらしく、これは変数を他の変数に代入するときには、元変数の値が代入先の変数の値になるということ。しかし、Rubyの世界は全てオブジェクトなので、変数の値は「オブジェクトのアドレス」になるのである。これが参照値渡し。
つまり、オブジェクトはアドレスをもっており、そのアドレスの先に値を保持しているのである。
それを前提として以下のコードを確認してみる
def hoge(a, b) a += 10 b += 10 end x = 1 y = 2 p x.object_id #=> 3 (xには3というObjectIDが振られている) p y.object_id #=> 5 (yには5というObjectIDが振られている) hoge(x, y) p x.object_id #=> 3 (xには3というObjectIDが振られている) p y.object_id #=> 5 (yには5というObjectIDが振られている) #hogeメソッド実行結果 p x #=> 1 p y #=> 2 def fuga(ary) ary[0] += 10 ary[1] += 10 end array = [1, 2] p array.object_id #=> 70226961071960 (arrayには70226961071960というObjectIDが振られている) p array[0].object_id #=> #=> 3 (array[0]には3というObjectIDが振られている) p array[1].object_id #=> #=> 5 (array[1]には5というObjectIDが振られている) fuga(array) p array.object_id #=> 70226961071960 (arrayには70226961071960というObjectIDが振られている) p array[0].object_id #=> 23 (array[0]には23というObjectIDが振られている) p array[1].object_id #=> 25 (array[1]には25というObjectIDが振られている) #fugaメソッド実行結果 p array #=> [11, 12]
仮引数を配列オブジェクトにすると、元の数字も変更された。
これは、オブジェクトが直接値を保持しているのではなく、数字を保持しているアドレスを保持しているからである。fugaメソッドは配列array[]が保持しているアドレスを受け取ったことになります。
それじゃ、整数はIntegerのオブジェクトじゃないの?という疑問が生まれます。
それについてはこちらRubyのFixnum - nil