คุณต้องการทัวร์ชม Ruby internals อย่างรวดเร็วหรือไม่
ถ้าอย่างนั้นคุณก็พร้อมรับของสมนาคุณ
เพราะ …
เราจะมาร่วมกันสำรวจว่าวัตถุ Ruby ถูกจัดวางในหน่วยความจำอย่างไร และคุณจะจัดการโครงสร้างข้อมูลภายในเพื่อทำสิ่งเจ๋งๆ ได้อย่างไร
คาดเข็มขัดนิรภัยและเตรียมพร้อมสำหรับการเดินทางสู่ส่วนลึกของล่าม Ruby!
เค้าโครงหน่วยความจำของอาร์เรย์
เมื่อคุณสร้างอาร์เรย์ Ruby จะต้องสำรองข้อมูลนั้นด้วยหน่วยความจำระบบบางส่วนและข้อมูลเมตาเล็กน้อย
ข้อมูลเมตารวมถึง :
- ขนาดอาร์เรย์ (จำนวนรายการ)
- ความจุอาร์เรย์
- คลาส
- สถานะวัตถุ (ค้างหรือไม่)
- ตัวชี้ตำแหน่งที่ข้อมูลถูกเก็บไว้ในหน่วยความจำ
เนื่องจากตัวแปล Ruby หลัก (MRI) เขียนด้วยภาษา C จึงไม่มีวัตถุ
แต่มีอย่างอื่น:โครงสร้าง .
struct ใน C ช่วยให้คุณเก็บข้อมูลที่เกี่ยวข้องไว้ด้วยกัน และสิ่งนี้ถูกใช้อย่างมากในซอร์สโค้ดของ MRI เพื่อแสดงสิ่งต่างๆ เช่น Array
, String
ของ &วัตถุชนิดอื่นๆ
เมื่อดูโครงสร้างเหล่านี้ เราสามารถสรุปเค้าโครงหน่วยความจำของวัตถุได้
มาดูโครงสร้างสำหรับ Array
เรียกว่า 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 RBasic { VALUE flags; VALUE klass; }
นี่คือสิ่งที่อ็อบเจกต์ Ruby ส่วนใหญ่มี &มันมีบางสิ่งเช่นคลาสสำหรับอ็อบเจกต์นี้ &แฟล็กไบนารีบางตัวที่บอกว่าอ็อบเจกต์นี้ถูกตรึงหรือไม่ (และสิ่งอื่น ๆ เช่นแอตทริบิวต์ 'tainted')
กล่าวอีกนัยหนึ่ง :
RBasic
มีข้อมูลเมตาทั่วไปสำหรับวัตถุ
หลังจากนั้นเรามีโครงสร้างอื่นซึ่งมีความยาวของอาร์เรย์ (len
)
นิพจน์สหภาพบอกว่า aux
สามารถเป็นได้ทั้ง capa
(สำหรับความจุ) หรือ shared
. ส่วนใหญ่เป็นการเพิ่มประสิทธิภาพ ซึ่งมีการอธิบายรายละเอียดเพิ่มเติมในโพสต์ที่ยอดเยี่ยมนี้โดย Pat Shaughnessy ในแง่ของการจัดสรรหน่วยความจำ คอมไพเลอร์จะใช้ประเภทที่ใหญ่ที่สุดภายในสหภาพ
จากนั้นเราก็มี ptr
ซึ่งมีที่อยู่หน่วยความจำที่ Array
. จริง ข้อมูลจะถูกเก็บไว้
นี่คือรูปภาพของสิ่งที่ดูเหมือน (กล่องสีขาว/เทาทุกกล่องมีขนาด 4 ไบต์ในระบบ 32 บิต ):
คุณสามารถดูขนาดหน่วยความจำของวัตถุโดยใช้โมดูล ObjectSpace:
require 'objspace' ObjectSpace.memsize_of([]) # 20
ตอนนี้เราพร้อมที่จะสนุกแล้ว!
Fiddle:การทดลองแสนสนุก
RBasic มีขนาด 8 ไบต์ในระบบ 32 บิตและ 16 ไบต์ในระบบ 64 บิต เมื่อทราบสิ่งนี้แล้ว เราสามารถใช้โมดูล Fiddle เพื่อเข้าถึงไบต์หน่วยความจำดิบสำหรับวัตถุและเปลี่ยนเพื่อการทดลองที่สนุกสนาน
ตัวอย่างเช่น :
เราเปลี่ยนสถานะแช่แข็งได้ด้วยการสลับบิต
นี่คือสาระสำคัญของวิธีการตรึง แต่สังเกตว่าไม่มีวิธีการยกเลิกการตรึง
นำไปใช้เพื่อความสนุกกันเถอะ!
ขั้นแรก ให้ต้องใช้ Fiddle
โมดูล (ส่วนหนึ่งของ Ruby Standard Library) และสร้างสตริงที่ตรึงไว้
require 'fiddle' str = 'water'.freeze str.frozen? # true
ถัดไป:
เราต้องการที่อยู่หน่วยความจำสำหรับสตริงของเรา ซึ่งสามารถหาได้แบบนี้
memory_address = str.object_id * 2
สุดท้าย:
เราพลิกบิตที่แน่นอนที่ Ruby ตรวจสอบเพื่อดูว่าวัตถุถูกแช่แข็งหรือไม่ เรายังตรวจสอบด้วยว่าวิธีนี้ใช้การได้โดยการเรียก frozen?
วิธีการ
Fiddle::Pointer.new(memory_address)[1] ^= 8 str.frozen? # false
สังเกตว่าดัชนี [1]
หมายถึงไบต์ที่ 2 ของ แฟล็ก ค่า (ซึ่งประกอบด้วยทั้งหมด 4 ไบต์)
จากนั้นเราก็ใช้ ^=
ซึ่งเป็นตัวดำเนินการ “XOR” (Exclusive OR) เพื่อพลิกบิตนั้น
เราทำสิ่งนี้เพราะส่วนต่าง ๆ ภายใน แฟล็ก มีความหมายต่างกันและเราไม่ต้องการเปลี่ยนแปลงสิ่งที่ไม่เกี่ยวข้อง
หากคุณได้อ่านโพสต์ ruby tricks ของฉัน คุณอาจเคยเห็นสิ่งนี้มาก่อน แต่ตอนนี้คุณรู้แล้วว่ามันทำงานอย่างไร 🙂
อีกสิ่งหนึ่งที่คุณสามารถลองได้คือเปลี่ยนความยาวของอาร์เรย์ &พิมพ์อาร์เรย์
คุณจะเห็นว่าอาร์เรย์สั้นลง!
คุณยังสามารถเปลี่ยนคลาสเพื่อสร้าง Array
คิดว่าเป็น String
…
บทสรุป
คุณได้เรียนรู้เล็กน้อยเกี่ยวกับวิธีการทำงานของ Ruby ภายใต้ประทุน หน่วยความจำสำหรับวัตถุ Ruby ถูกจัดวางอย่างไร &คุณสามารถใช้ Fiddle
. ได้อย่างไร โมดูลที่จะเล่นกับสิ่งนั้น
คุณไม่ควรใช้ Fiddle
แบบนี้ในแอปจริง แต่ทดลองเล่นก็สนุก
อย่าลืม แชร์โพสต์นี้ ให้คนเห็นมากขึ้น 🙂