เค้าโครงหน่วยความจำโครงสร้างใน C


86

ฉันมีพื้นหลัง C # ฉันเป็นมือใหม่มากสำหรับภาษาระดับต่ำเช่น C

ใน C # structหน่วยความจำของคอมไพเลอร์จะจัดวางโดยค่าเริ่มต้น คอมไพเลอร์สามารถเรียงลำดับฟิลด์ข้อมูลใหม่หรือวางบิตเพิ่มเติมระหว่างฟิลด์โดยปริยาย ดังนั้นฉันต้องระบุแอตทริบิวต์พิเศษบางอย่างเพื่อแทนที่พฤติกรรมนี้สำหรับรูปแบบที่แน่นอน

AFAIK, C จะไม่เรียงลำดับใหม่หรือจัดรูปแบบหน่วยความจำของ a structตามค่าเริ่มต้น อย่างไรก็ตามฉันได้ยินมาว่ามีข้อยกเว้นเล็กน้อยที่หาได้ยากมาก

ลักษณะการจัดวางหน่วยความจำของ C คืออะไร? สิ่งที่ควรเรียงใหม่ / จัดตำแหน่งและไม่?

คำตอบ:


111

ใน C คอมไพลเลอร์ได้รับอนุญาตให้กำหนดการจัดแนวบางส่วนสำหรับทุกประเภทดั้งเดิม โดยปกติการจัดตำแหน่งจะเป็นขนาดของประเภท แต่เป็นการใช้งานโดยเฉพาะทั้งหมด

มีการเพิ่มไบต์ช่องว่างเพื่อให้ทุกออบเจ็กต์อยู่ในแนวเดียวกันอย่างเหมาะสม ไม่อนุญาตให้จัดลำดับใหม่

อาจเป็นไปได้ว่าทุกคอมไพเลอร์สมัยใหม่จากระยะไกลจะดำเนินการ #pragma packซึ่งช่วยให้สามารถควบคุมช่องว่างภายในและปล่อยให้โปรแกรมเมอร์ปฏิบัติตาม ABI (แม้ว่าจะไม่เป็นมาตรฐานอย่างเคร่งครัดก็ตาม)

จาก C99 §6.7.2.1:

12 สมาชิกแต่ละตัวที่ไม่ใช่บิตฟิลด์ของโครงสร้างหรืออ็อบเจ็กต์ยูเนี่ยนจะถูกจัดแนวในลักษณะที่กำหนดไว้สำหรับการนำไปใช้งานที่เหมาะสมกับประเภทของมัน

13 ภายในออบเจ็กต์โครงสร้างสมาชิกที่ไม่ใช่บิตฟิลด์และหน่วยที่บิตฟิลด์อยู่มีแอดเดรสที่เพิ่มขึ้นตามลำดับที่ประกาศ ตัวชี้ไปยังออบเจ็กต์โครงสร้างที่แปลงอย่างเหมาะสมชี้ไปที่สมาชิกเริ่มต้น (หรือถ้าสมาชิกนั้นเป็นบิตฟิลด์จากนั้นไปยังหน่วยที่มันอยู่) และในทางกลับกัน อาจมีช่องว่างภายในที่ไม่มีชื่อภายในออบเจ็กต์โครงสร้าง แต่ไม่ได้อยู่ที่จุดเริ่มต้น


1
คอมไพเลอร์บางตัว (เช่น GCC) ใช้เอฟเฟกต์เช่นเดียว#pragma packกับ แต่มีการควบคุมความหมายอย่างละเอียดมากขึ้น
Chris Lutz

21
ฉันประหลาดใจที่เห็นการโหวตลดลง ใครช่วยชี้ข้อผิดพลาด
Potatoswatter

2
C11 ยังมี_Alignas.
idmean

117

เป็นการใช้งานโดยเฉพาะ แต่ในทางปฏิบัติกฎ (ในกรณีที่ไม่มี#pragma packหรือคล้ายกัน) คือ:

  • สมาชิกโครงสร้างจะถูกจัดเก็บตามลำดับที่มีการประกาศ (ซึ่งเป็นข้อกำหนดตามมาตรฐาน C99 ดังที่กล่าวไว้ก่อนหน้านี้)
  • หากจำเป็นให้เพิ่มช่องว่างก่อนสมาชิกโครงสร้างแต่ละตัวเพื่อให้แน่ใจว่ามีการจัดตำแหน่งที่ถูกต้อง
  • ประเภทดั้งเดิม T แต่ละประเภทต้องการการจัดตำแหน่งsizeof(T)ไบต์

ดังนั้นให้โครงสร้างต่อไปนี้:

  • ch1 อยู่ที่ชดเชย 0
  • ใส่ไบต์ช่องว่างเพื่อจัดแนว ...
  • s ที่ชดเชย 2
  • ch2 อยู่ที่ออฟเซ็ต 4 ทันทีหลัง s
  • แทรก 3 ไบต์ช่องว่างเพื่อจัดแนว ...
  • ll ที่ชดเชย 8
  • i อยู่ที่ออฟเซ็ต 16 หลังจากล
  • เพิ่มช่องว่างภายใน 4 ไบต์ในตอนท้ายเพื่อให้โครงสร้างโดยรวมเป็นผลคูณของ 8 ไบต์ ฉันตรวจสอบสิ่งนี้ในระบบ 64 บิต: ระบบ 32 บิตอาจอนุญาตให้โครงสร้างมีการจัดตำแหน่ง 4 ไบต์

ดังนั้นsizeof(ST)24

สามารถลดลงเหลือ 16 ไบต์โดยการจัดเรียงสมาชิกใหม่เพื่อหลีกเลี่ยงช่องว่างภายใน:


3
หากจำเป็นให้เพิ่มช่องว่างก่อน ... ควรเพิ่มcharสมาชิกคนสุดท้ายในตัวอย่างของคุณ
Deduplicator

9
ประเภทดั้งเดิมไม่จำเป็นต้องมีการจัดตำแหน่งsizeof(T)ไบต์ ตัวอย่างเช่นdoubleบนสถาปัตยกรรม 32 บิตทั่วไปคือ 8 ไบต์ แต่มักต้องการการจัดแนวแบบ 4 ไบต์เท่านั้น นอกจากนี้ช่องว่างที่ส่วนท้ายของโครงสร้างเพียงแผ่นอิเล็กโทรดเพื่อจัดตำแหน่งของสมาชิกโครงสร้างที่กว้างที่สุด ตัวอย่างเช่นโครงสร้างของตัวแปรถ่าน 3 ตัวอาจไม่มีช่องว่างภายใน
แมตต์

1
@ dan04 จะเป็นแนวทางปฏิบัติที่ดีในการจัดวางโครงสร้างตามลำดับจากมากไปหาน้อยของ sizeof (T) จะมีข้อเสียในการทำเช่นนี้หรือไม่?
RohitMat

11

คุณสามารถเริ่มต้นด้วยการอ่านบทความวิกิพีเดียการจัดตำแหน่งโครงสร้างข้อมูลเพื่อทำความเข้าใจเกี่ยวกับการจัดตำแหน่งข้อมูลให้ดีขึ้น

จากบทความวิกิพีเดีย :

การจัดแนวข้อมูลหมายถึงการวางข้อมูลที่ออฟเซ็ตหน่วยความจำเท่ากับจำนวนหลาย ๆ คำซึ่งจะเพิ่มประสิทธิภาพของระบบเนื่องจากวิธีที่ CPU จัดการกับหน่วยความจำ ในการจัดแนวข้อมูลอาจจำเป็นต้องแทรกไบต์ที่ไม่มีความหมายระหว่างจุดสิ้นสุดของโครงสร้างข้อมูลสุดท้ายและจุดเริ่มต้นของโครงสร้างข้อมูลถัดไปซึ่งเป็นช่องว่างภายในโครงสร้างข้อมูล

จาก6.54.8 Structure-Packing Pragmasของเอกสาร GCC:

เพื่อความเข้ากันได้กับคอมไพเลอร์ Microsoft Windows GCC สนับสนุนชุดคำสั่ง #pragma ซึ่งเปลี่ยนการจัดตำแหน่งสูงสุดของสมาชิกของโครงสร้าง (นอกเหนือจากบิตฟิลด์ความกว้างศูนย์) สหภาพแรงงานและคลาสที่กำหนดในภายหลัง ค่า n ด้านล่างนี้จะต้องเป็นเลขยกกำลังสองเสมอและระบุการจัดตำแหน่งใหม่เป็นไบต์

  1. #pragma pack(n) เพียงแค่ตั้งค่าการจัดตำแหน่งใหม่
  2. #pragma pack() ตั้งค่าการจัดแนวเป็นแบบที่มีผลเมื่อเริ่มการคอมไพล์ (ดูเพิ่มเติมที่ตัวเลือกบรรทัดคำสั่ง -fpack-struct [=] ดูตัวเลือก Code Gen)
  3. #pragma pack(push[,n]) ดันการตั้งค่าการจัดแนวปัจจุบันบนสแต็กภายในจากนั้นตั้งค่าการจัดแนวใหม่เป็นทางเลือก
  4. #pragma pack(pop)คืนค่าการตั้งค่าการจัดแนวเป็นค่าที่บันทึกไว้ที่ด้านบนสุดของสแต็กภายใน (และลบรายการสแต็กนั้นออก) โปรดทราบว่า #pragma pack([n])ไม่มีผลต่อสแต็กภายในนี้ จึงเป็นไปได้ที่จะได้#pragma pack(push) ตามมาด้วยหลายกรณีและมีการสรุปเป็นหนึ่งเดียว#pragma pack(n) #pragma pack(pop)

เป้าหมายบางอย่างเช่น i386 และ powerpc สนับสนุน ms_struct #pragmaซึ่งวางโครงสร้างตามที่บันทึก __attribute__ ((ms_struct))ไว้

  1. #pragma ms_struct on เปิดเค้าโครงสำหรับโครงสร้างที่ประกาศ
  2. #pragma ms_struct off ปิดเค้าโครงสำหรับโครงสร้างที่ประกาศ
  3. #pragma ms_struct reset กลับไปที่เค้าโครงเริ่มต้น

ขอบคุณสำหรับการดูแล ฉันแก้ไขคำถามตามที่คุณชี้แนะ
eonil
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.