Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

Ruby 內部揭秘:理解物件記憶體佈局

想要快速參觀 Ruby 內部結構嗎?

那你來對地方了。

因為……

我們將一起探索 Ruby 物件在記憶體中的佈局,以及如何操作內部資料結構做一些酷的事。

繫好安全帶,準備踏上 Ruby 解譯器的深度之旅!

陣列的記憶體佈局

當你建立陣列,Ruby 必須用一些系統記憶體和一點點中繼資料來支撐它。

中繼資料包含:

  • 陣列大小(項目計數)
  • 陣列容量
  • 類別
  • 物件狀態(frozen 或否)
  • 指向資料儲存記憶體位置的指標

因為主要 Ruby 解譯器(MRI)是用 C 寫的,所以沒有物件。

但有別的東西:structs。

C 語言的 struct 幫你把相關資料存一起,這在 MRI 原始碼中大量用來表示 Array, String 等物件。

看其中一個 struct 我們可以推斷物件的記憶體佈局。

來看 Array 的 struct,叫 RArray:

 struct RArray { struct RBasic basic; union { struct { long len; union { long capa; VALUE shared; } aux; const VALUE *ptr; } heap; const VALUE ary[RARRAY_EMBED_LEN_MAX]; } as; }; 

如果你不熟 C 這可能看起來有點嚇人,別擔心!我會幫你拆解成容易消化的小塊 🙂

第一個是 RBasic,也是個 struct:

 struct RBasic { VALUE flags; VALUE klass; } 

這是大多數 Ruby 物件都有的,包含這個物件的類別和一些二進位旗標,說這物件是否 frozen(還有像 'tainted' 這類屬性)。

換句話說:

RBasic 包含物件的通用中繼資料。

接著是另一個 struct,包含陣列長度(len)。

union 表達式說 aux 可以是 capa(容量)或 shared。這主要是優化,細節在 Pat Shaughnessy 的這篇優秀文章解釋。記憶體分配方面,編譯器會用 union 裡最大的型別。

然後是 ptr,包含實際 Array 資料儲存的記憶體位址。

這是長什麼樣子的圖(每個白/灰格子在 32-bit 系統是 4 bytes):

Ruby 內部揭秘:理解物件記憶體佈局

你可以用 ObjectSpace 模組看物件的記憶體大小:

 require 'objspace' ObjectSpace.memsize_of([]) # 20 

現在我們準備好來點好玩的了!

Fiddle:一個有趣的實驗

RBasic 在 32-bit 系統正好 8 bytes,64-bit 系統 16 bytes。知道這點我們可以用 Fiddle 模組存取物件的原始記憶體位元組並改變它們做有趣實驗。

例如:

我們可以透過切換單一位元來改變 frozen 狀態。

這本質上就是 freeze 方法做的事,但注意沒有 unfreeze 方法。

我們為了好玩實作它!

先 require Fiddle 模組(Ruby 標準函式庫一部分)並建立 frozen 字串。

 require 'fiddle' str = 'water'.freeze str.frozen? # true 

接著:

我們需要字串的記憶體位址,可以這樣獲得。

 memory_address = str.object_id * 2 

最後:

我們翻轉 Ruby 檢查物件是否 frozen 的确切那個位元。我們也用 frozen? 方法檢查是否生效。

 Fiddle::Pointer.new(memory_address)[1] ^= 8 str.frozen? # false 

注意 index [1] 指的是 flags 值的第 2 個位元組(總共 4 個位元組)。

然後用 ^= 這是「XOR」(獨佔 OR)運算子翻轉那個位元。

我們這樣做是因為 flags 裡不同位元有不同含義,我們不想改無關的東西。

如果讀過我的 ruby tricks 文章可能看過這個,但現在你知道它怎麼運作了 🙂

另一件能試的是改陣列長度並列印陣列。

你會看到陣列變短!

你甚至可以改類別讓 Array 以為它是 String……

結論

你學到了一點 Ruby 背後如何運作。Ruby 物件記憶體如何佈局 以及如何用 Fiddle 模組玩弄它。

你大概不該在真實應用這樣用 Fiddle,但實驗起來很有趣。

別忘了 分享這篇文章 讓更多人看到 🙂