CとRubyの代入文を比較してみた
「コーディングを支える技術」を読んだ.
コーディングを支える技術 ~成り立ちから学ぶプログラミング作法 (WEB+DB PRESS plus)
- 作者: 西尾泰和
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (17件) を見る
この本がいかに素晴らしいかはいつかブログで書くとして、同書に以下のようなRubyのコードがある*1.
#Ruby x = "Ruby" p x #-> "Ruby" z = x x[0] = "P" p x #-> "Puby" p z #-> "Puby"
x[0] = "P"
とすることでxもzも"Puby"になる.
なんと言ってもいきなりz = x
である.普通の代入文が「値のコピー」じゃないってどういうこと?*3などとよくわからない迷路に迷い込んだ.
しかし、冷静に考えると扱っているのは「文字列」だった.Cで言ったらこんな感じ?
//C #include <stdio.h> int main(){ char x[2] = "C"; char *z = NULL; z = x; printf("x=%s z=%s\n", x, z); //-> x=C z=C x[0] = 'D'; printf("x=%s z=%s\n", x, z); //-> x=D z=D return 0; }
ここでのz = x;
ははっきりzにxの参照(=ポインタ=配列の先頭アドレス)を代入しているというのが「zの宣言」から見て取れる.
自分はどうも無意識に宣言と関連付けて代入分を見ているようで上記のコードだと混乱してしまった.
RubyもCで言うところのポインタを渡していると思えばわかる.結局Rubyもxの参照の値をzにコピーしているので,Rubyの代入文はCと同様に「値のコピー」を行なっていた.
しかし,文法的には宣言が一切ないただの代入文だ.データが数値だったらどうするんだって感じだ.
調べてみると,Rubyは数値も文字列も含めて全て"オブジェクト"らしい.
んでもって変数はそのオブジェクトの参照を持つらしい.つまり数値だったとしても変数が持つのはオブジェクトの参照...
だったらやっぱりx=1; y=x; x+=10;
でyが11となってしまうではないか!なーんて考えてしまった.つまり,やっぱりRubyだと,
//C #include <stdio.h> int main(){ int x = 1; int *y; printf("x=%d p=%p\n", x, &x); //-> x=1 p=0xbf95e6c8 y = &x; //xの参照をyに渡す x += 10; printf("x=%d p=%p\n", x, &x); //-> x=11 p=0xbf95e6c8 printf("y=%d p=%p\n", *y, y); //-> y=11 p=0xbf95e6c8 return 0; }
となってしまうのでは?なーんて思ってしまった.
でもそうはならない.
Rubyではx=1; y=x; x+=10;
でxが持つ参照を渡されたyがなぜxに10を足してもなお値が1であり続けるのか調べてみる.
手始めに以下のように文字列自体を再定義した.するとオブジェクトIDが変化した*5が変化した.
#Ruby x = "Ruby" p x #-> "Ruby" p x.object_id #-> -610474798 #(余談:自分の環境ではマイナスの大きな数値になったけど何を基準に決めているんだろう??) x[0] = "P" p x #-> "Puby" #一部だけ変更してもオブジェクトIDは変わらない p x.object_id #-> -610474798 x = "C" #xを再定義 p x #-> "C" #オブジェクトIDが変化した p x.object_id #-> -610474858
次にこれを数値でやってみた.例えば足し算してみるとなんと文字列と同様にxのオブジェクトIDが変化する!!
#Ruby x = 1 p x #-> 1 p x.object_id #-> 3 #余談:数値の場合,オブジェクトIDが3や23などの小さな値になった!!一体何が基準なんだ?? x += 10 p x #-> 11 #オブジェクトIDが3から23に変化した!なんじゃこりゃー! p x.object_id #-> 23
Cではこうはならない.
//C #include <stdio.h> int main(){ int x = 1; int y; y = x; printf("x=%d p=%p\n", x, &x); //-> x=1 p=0xbfc62758 printf("y=%d p=%p\n", y, &y);//-> y=1 p=0xbfc6275c x += 10; //(xに格納されている値が変化してもxのアドレスは変化しない) printf("x=%d p=%p\n", x, &x); //-> x=11 p=0xbfc62758 printf("y=%d p=%p\n", y, &y);//-> y=1 p=0xbfc6275c return 0; }
int型の変数xの値のアドレスは変化していない.ここでのy = x;
はやはり値のコピーだけどその値というのはポインタではなくてここでは整数値1だ.んでもって変数xの持つ値(整数値の1)に10を足しているのだから物理的に別の領域に存在する変数yには当然何の影響もない.
さっきRubyの変数はオブジェクトへの参照を持つと言ったけど,数値の値を変更すると参照が変わる.もはや言うまでもないかもしれないけど,この代入時に新たなオブジェクトを割り当てるという行為によって上記のCと同じ挙動を実現している.
#Ruby x=1 p x #-> 1 p x.object_id #-> 3 y = x x += 10 p x #-> 11 p x.object_id #-> 23 p y #-> 1 p y.object_id #-> 3
yはxの参照をそのまま受け継いでいるのが面白い.誰にも受け継がれずに宙ぶらりんになったオブジェクトはRubyのガベージコレクションが動的に解放する(らしい).
ちなみに、x = 1
の時、x[0] = "2"
としてみるとundefined method
[]=' for 1:Fixnum (NoMethodError)`とちゃんと怒られた.数値の間はちゃんと数値扱いしてくれる.これが動的型付けの威力なの...か?こんなんでも遊べるんだから無知って悲しい.