(ruby)(tips)(オブジェクトのポインタのアドレスを確認したい)

rubyのtips一覧は→ http://d.hatena.ne.jp/morakana/20141005/1412679155


 rubyはポインタの指している先をきちんと理解してないと
思いがけない動作をさせてしまう事があります
例えば
a = 'aaa'
b = a
a << 'xxx'
のようにすると
aの値は "aaaxxx" で想定どおりですが
bの値も "aaaxxx" になってしまいます
つまり今回のように「b = a」とすると
新しくaの文字列のコピーが生成されbにそのアドレスがセットされる、のではなく
bのポインタに、単にaと同じアドレスがセットされるだけということになります
言い換えれば、「aもbも同じデータを指している」って事ですね
なので「a << 'xxx'」だけでなく「b << 'xxx'」でも同じ事が起こります
しかし、このように
a = 'aaa'
b = a
a = 'xxx'
と、単に代入するだけだと・・・
aの値は "xxx"
bの値は "aaa"
という結果になります、この感覚で上の例のように書いてしまうと
思いがけない動作をしてしまうって事になるわけですね
しかも、「何が悪いのかに気付きにくい」ので行き詰ってしまう原因になります
オブジェクト指向なのにポインタ出てくるのかよ〜」と思われるかもしれませんが
今回の上の例のようなことが想定外で起こった時に、変数のポインタが何処を指しているのかを
確認する手段を知っていれば実態を早く把握できますし、問題解決も早くなります

 ポインタを取得するだけなら簡単ですので、これだけ覚えておいても損はないです
require 'dl'
a = 'aaa'
a = b
puts(DL::CPtr[a].to_i)
puts(DL::CPtr[b].to_i)

 また、こんな感じにすれば空の文字列リテラルにだってちゃんとアドレスがあるのが分かりますね
require 'dl'
puts(DL::CPtr[''].to_i)
 では
「a << 'xxx'」

「a = 'xxx'」
では、なぜこういうポインタの移り変わりの差が出るのか?
それは、「ポインタを元に対象を破壊的に変更しているかどうか」で変わってきます
「<<」は (Stringオブジェクトの場合だと)左辺のデータの末尾に
右辺の文字列を追加するもので、結果的に左辺のデータを破壊的に変更しています
同じように、元のアドレスにそのまま新しい文字列を上書きする
「upcase!メソッド」のような場合も同じことが起こります
require 'dl'
a = 'aaa'
b = a
puts(DL::CPtr[a].to_i)
puts(DL::CPtr[b].to_i)
a.upcase!
puts(DL::CPtr[a].to_i)
puts(DL::CPtr[b].to_i)
upcase! を実行しても aとbが指しているアドレスが同じという事がわかりますね

 それに対して「=」だと上でも書いたとおり
左辺のポインタを右辺のポインタと同じにセットするわけで
つまり、右辺で新しく生成された文字列リテラル('xxx')のアドレスが
左辺(a)のポインタにセットされます
よって直前で 「b = a」のような事をしておいても bの値には影響が出ない
ということになります
require 'dl'
a = 'aaa'
b = a
puts(DL::CPtr[a].to_i)
puts(DL::CPtr[b].to_i)
a = 'xxx'
puts(DL::CPtr[a].to_i)
puts(DL::CPtr[b].to_i)
「a = 'xxx'」を実行するまでは同じアドレスを指していますが
実行後は bはそのまま同じアドレスを指していますけど
a は新しいアドレスがセットされているのがわかります

 最近のバージョンのrubyを使っていると「DL」をrequireしたときに
DL is deprecated, please use Fiddle (DLは推奨されません、Fiddleを使ってください)
とメッセージが出ますが、とりあえずは気にしなくていいです
どうしても気になる場合はメッセージに従い「Fiddle」を使いましょう
require 'fiddle'
Fiddle::Pointer['abcd'].to_i()