ทำไมขนาดของ struct ไม่เท่ากับผลรวมของขนาดของสมาชิกแต่ละคน?


698

เหตุใดsizeofผู้ประกอบการจึงส่งคืนขนาดที่ใหญ่กว่าสำหรับโครงสร้างมากกว่าขนาดทั้งหมดของสมาชิกโครงสร้าง


14
ดูคำถามที่พบบ่อย C นี้เกี่ยวกับการยืนยันหน่วยความจำ c-faq.com/struct/align.esr.html
Richard Chambers

48
เรื่องเล็ก ๆ น้อย: มีไวรัสคอมพิวเตอร์ที่เกิดขึ้นจริงที่วางรหัสไว้ในโครงสร้างของแผ่นดิสก์ในโปรแกรมโฮสต์
Elazar

4
@Elazar นั่นน่าประทับใจมาก! ฉันไม่เคยคิดว่าจะเป็นไปได้ที่จะใช้พื้นที่เล็ก ๆ เหล่านี้เพื่ออะไร คุณสามารถให้รายละเอียดเพิ่มเติมได้อีกหรือไม่?
OmarL

1
@ Wilson - ฉันแน่ใจว่าเกี่ยวข้อง jmp มากมาย
hoodaticus

4
ดูโครงสร้างการบรรจุ : The Lost Art of C โครงสร้างการบรรจุEric S. Raymond
EsmaeelE

คำตอบ:


649

นี่เป็นเพราะมีการเพิ่มการเติมเพื่อตอบสนองข้อ จำกัด การจัดตำแหน่ง การจัดโครงสร้างข้อมูลส่งผลกระทบต่อทั้งประสิทธิภาพและความถูกต้องของโปรแกรม:

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

นี่คือตัวอย่างการใช้การตั้งค่าทั่วไปสำหรับโปรเซสเซอร์ x86 (ทั้งหมดใช้โหมด 32 และ 64 บิต):

struct X
{
    short s; /* 2 bytes */
             /* 2 padding bytes */
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 3 padding bytes */
};

struct Y
{
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
    short s; /* 2 bytes */
};

struct Z
{
    int   i; /* 4 bytes */
    short s; /* 2 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
};

const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */

หนึ่งสามารถลดขนาดของโครงสร้างโดยการเรียงลำดับสมาชิกโดยการจัดตำแหน่ง (เรียงลำดับตามขนาดพอเพียงสำหรับประเภทพื้นฐาน) (เช่นโครงสร้างZในตัวอย่างด้านบน)

หมายเหตุสำคัญ: ทั้งมาตรฐาน C และ C ++ ระบุว่าการจัดตำแหน่งโครงสร้างถูกกำหนดให้นำไปใช้งาน ดังนั้นคอมไพเลอร์แต่ละคนอาจเลือกที่จะจัดตำแหน่งข้อมูลที่แตกต่างกันส่งผลให้รูปแบบข้อมูลที่แตกต่างและเข้ากันไม่ได้ ด้วยเหตุผลนี้เมื่อต้องจัดการกับไลบรารีที่จะใช้คอมไพเลอร์ต่างกันสิ่งสำคัญคือต้องเข้าใจว่าคอมไพเลอร์จัดเรียงข้อมูลอย่างไร คอมไพเลอร์บางตัวมีการตั้งค่าบรรทัดคำสั่งและ / หรือ#pragmaข้อความพิเศษเพื่อเปลี่ยนการตั้งค่าการจัดแนวโครงสร้าง


38
ฉันต้องการจดบันทึกไว้ที่นี่: โปรเซสเซอร์ส่วนใหญ่ลงโทษคุณสำหรับการเข้าถึงหน่วยความจำที่ไม่ได้จัดแนว (ตามที่คุณกล่าวถึง) แต่คุณไม่สามารถลืมได้ว่ามีหลายคนที่ไม่อนุญาต โดยเฉพาะอย่างยิ่งชิป MIPS จะส่งข้อยกเว้นเกี่ยวกับการเข้าถึงที่ไม่ได้ลงทะเบียน
Cody Brocious

35
แท้จริงแล้วชิป x86 นั้นมีความเป็นเอกลักษณ์มากกว่าที่พวกเขาอนุญาตให้เข้าถึงแบบไม่ได้ลงนามแม้ว่าจะถูกลงโทษ AFAIK ชิปส่วนใหญ่จะโยนข้อยกเว้นไม่ใช่เพียงไม่กี่อย่าง PowerPC เป็นอีกตัวอย่างทั่วไป
Dark Shikari

6
การเปิดใช้งาน pragmas สำหรับการเข้าถึงแบบไม่ตรงแนวโดยทั่วไปจะทำให้โค้ดของคุณมีขนาดเท่ากับบอลลูนบนโปรเซสเซอร์ที่ใช้งานผิดพลาดในการจัดแนวที่ไม่เหมาะสม ARM ยังส่งข้อผิดพลาดที่ไม่ตรงแนว
Mike Dimmick

5
@ Dark - เห็นด้วยทั้งหมด แต่ส่วนใหญ่โปรเซสเซอร์เดสก์ทอป x86 / x64 ดังนั้นส่วนใหญ่ชิปจะไม่ออกความผิดพลาดของการจัดตำแหน่งข้อมูล;)
แอรอน

27
การเข้าถึงข้อมูลที่ไม่ได้จัดแนวนั้นโดยทั่วไปแล้วเป็นคุณสมบัติที่พบในสถาปัตยกรรม CISC และสถาปัตยกรรม RISC ส่วนใหญ่ไม่ได้รวมเอาไว้ (ARM, MIPS, PowerPC, Cell) ในความเป็นจริงแล้วชิปส่วนใหญ่ไม่ใช่โปรเซสเซอร์เดสก์ท็อปสำหรับกฎที่ฝังตัวตามจำนวนชิปและส่วนใหญ่เป็นสถาปัตยกรรม RISC
Lara Dougan

192

การบรรจุและการจัดตำแหน่งไบต์ตามที่อธิบายไว้ในคำถามที่พบบ่อย C ที่นี่ :

มันสำหรับการจัดตำแหน่ง โปรเซสเซอร์จำนวนมากไม่สามารถเข้าถึงปริมาณ 2- และ 4 ไบต์ (เช่น ints และ int ยาว) หากพวกเขาหนาตาในทุกทาง

สมมติว่าคุณมีโครงสร้างนี้:

struct {
    char a[3];
    short int b;
    long int c;
    char d[3];
};

ตอนนี้คุณอาจคิดว่ามันควรจะเป็นไปได้ที่จะแพ็คโครงสร้างนี้ลงในหน่วยความจำเช่นนี้:

+-------+-------+-------+-------+
|           a           |   b   |
+-------+-------+-------+-------+
|   b   |           c           |
+-------+-------+-------+-------+
|   c   |           d           |
+-------+-------+-------+-------+

แต่มันก็ง่ายกว่ามากหากโปรเซสเซอร์ใช้คอมไพเลอร์จัดเรียงมัน:

+-------+-------+-------+
|           a           |
+-------+-------+-------+
|       b       |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           |
+-------+-------+-------+

ในรุ่นที่บรรจุให้สังเกตว่าอย่างน้อยมันก็ยากสำหรับคุณและฉันที่จะดูว่าฟิลด์ b และ c ล้อมรอบอย่างไร? โดยสรุปแล้วมันก็ยากสำหรับโปรเซสเซอร์เช่นกัน ดังนั้นคอมไพเลอร์ส่วนใหญ่จะวางโครงสร้าง (ราวกับมีฟิลด์พิเศษที่มองไม่เห็น) ดังนี้:

+-------+-------+-------+-------+
|           a           | pad1  |
+-------+-------+-------+-------+
|       b       |     pad2      |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           | pad3  |
+-------+-------+-------+-------+

1
ตอนนี้การใช้สล็อตหน่วยความจำ pad1, pad2 และ pad3 คืออะไร
Lakshmi Sreekanth Chitla

7
@YoYoYonnY นั้นเป็นไปไม่ได้ คอมไพเลอร์ไม่ได้รับอนุญาตให้เรียงลำดับสมาชิก struct อีกครั้งแม้ว่าgcc
phuclv

@EmmEff อาจเป็นสิ่งที่ผิด แต่ฉันไม่เข้าใจ: ทำไมจึงไม่มีสล็อตหน่วยความจำสำหรับตัวชี้ในอาร์เรย์?
BalázsBörcsök

1
@ BalázsBörcsökนี่คืออาร์เรย์ขนาดคงที่และดังนั้นองค์ประกอบของพวกเขาจะถูกจัดเก็บโดยตรงใน struct ที่ออฟเซ็ตคงที่ คอมไพเลอร์รู้ทั้งหมดนี้ในเวลารวบรวมเพื่อให้ตัวชี้มีความหมายโดยนัย ตัวอย่างเช่นหากคุณมีตัวแปร struct ของประเภทนี้ที่เรียกว่าsแล้ว&s.a == &sและ&s.d == &s + 12(ได้รับการจัดตำแหน่งที่แสดงในคำตอบ) ตัวชี้จะถูกจัดเก็บเฉพาะในกรณีที่อาร์เรย์มีขนาดตัวแปร (เช่นaถูกประกาศchar a[]แทนchar a[3]) แต่จากนั้นองค์ประกอบจะต้องเก็บไว้ที่อื่น
kbolino

27

ถ้าคุณต้องการให้โครงสร้างมีขนาดที่แน่นอนด้วย GCC ตัวอย่างเช่นใช้ __attribute__((packed))ตัวอย่างเช่นการใช้งาน

บน Windows คุณสามารถตั้งค่าการจัดตำแหน่งเป็นหนึ่งไบต์เมื่อใช้คอมไพเลอร์ cl.exe ด้วยตัวเลือก / Zpตัวเลือก

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

ดังนั้นจึงเป็นเรื่องของการจัดตำแหน่งโดยทั่วไป

คุณต้องมีเหตุผลที่ดีในการเปลี่ยนแปลง


5
"เหตุผลที่ดี" ตัวอย่าง: การรักษาความเข้ากันได้ของไบนารี (การเติมเต็ม) สอดคล้องกันระหว่างระบบ 32- บิตและ 64- บิตสำหรับโครงสร้างที่ซับซ้อนในรหัสตัวอย่างการพิสูจน์แนวคิดที่กำลังเปิดตัวในวันพรุ่งนี้ บางครั้งความจำเป็นต้องมีความสำคัญมากกว่าความเหมาะสม
Mr.Ree

2
ทุกอย่างก็โอเคยกเว้นเมื่อคุณพูดถึงระบบปฏิบัติการ นี่เป็นปัญหาสำหรับความเร็วของ CPU ระบบปฏิบัติการไม่เกี่ยวข้องเลย
Blaisorblade

3
อีกเหตุผลที่ดีคือถ้าคุณกำลังยัดดาต้าสตรีมลงใน struct เช่นเมื่อวิเคราะห์โปรโตคอลเครือข่าย
ceo

1
@dolmen ฉันเพิ่งชี้ให้เห็นว่า "การใช้งานระบบเข้าถึงข้อมูลได้ง่ายกว่า" ไม่ถูกต้องเนื่องจากระบบปฏิบัติการไม่สามารถเข้าถึงข้อมูลได้
Blaisorblade

1
@dolmen อันที่จริงแล้วเราควรพูดถึง ABI (application binary interface) การจัดแนวเริ่มต้น (ใช้หากคุณไม่เปลี่ยนในแหล่งที่มา) ขึ้นอยู่กับ ABI และระบบปฏิบัติการหลายระบบรองรับ ABIs หลายตัว (เช่น 32- และ 64- บิตหรือไบนารีจากระบบปฏิบัติการที่แตกต่างกันหรือสำหรับการรวบรวม ไบนารีเดียวกันสำหรับระบบปฏิบัติการเดียวกัน) OTOH การจัดตำแหน่งที่สะดวกสำหรับประสิทธิภาพนั้นขึ้นอยู่กับ CPU - หน่วยความจำเข้าถึงได้เหมือนกันไม่ว่าคุณจะใช้โหมด 32 หรือ 64 บิต (ฉันไม่สามารถแสดงความคิดเห็นในโหมดจริง แต่ดูเหมือนว่าแทบจะไม่เกี่ยวข้องกับประสิทธิภาพในปัจจุบัน) IIRC Pentium เริ่มชอบการจัดตำแหน่ง 8 ไบต์
Blaisorblade

15

ซึ่งอาจเกิดจากการจัดเรียงไบต์และการเติมเพื่อให้โครงสร้างออกมาเป็นจำนวนไบต์ (หรือคำ) บนแพลตฟอร์มของคุณ ตัวอย่างเช่นใน C บน Linux โครงสร้าง 3 รายการต่อไปนี้:

#include "stdio.h"


struct oneInt {
  int x;
};

struct twoInts {
  int x;
  int y;
};

struct someBits {
  int x:2;
  int y:6;
};


int main (int argc, char** argv) {
  printf("oneInt=%zu\n",sizeof(struct oneInt));
  printf("twoInts=%zu\n",sizeof(struct twoInts));
  printf("someBits=%zu\n",sizeof(struct someBits));
  return 0;
}

มีสมาชิกที่มีขนาด (เป็นไบต์) คือ 4 ไบต์ (32 บิต), 8 ไบต์ (2x32 บิต) และ 1 ไบต์ (2 + 6 บิต) ตามลำดับ โปรแกรมข้างต้น (บน Linux โดยใช้ gcc) พิมพ์ขนาดเป็น 4, 8 และ 4 - โดยที่โครงสร้างสุดท้ายมีการเสริมเพื่อให้เป็นคำเดียว (4 x 8 บิตไบต์บนแพลตฟอร์ม 32 บิตของฉัน)

oneInt=4
twoInts=8
someBits=4

4
"C บน Linux ที่ใช้ gcc" ไม่เพียงพอที่จะอธิบายแพลตฟอร์มของคุณ การจัดแนวส่วนใหญ่ขึ้นอยู่กับสถาปัตยกรรมของ CPU
dolmen

- @ Kyle Burton ขอโทษด้วยฉันไม่เข้าใจว่าทำไมขนาดของโครงสร้าง "someBits" เท่ากับ 4 ฉันคาดว่า 8 ไบต์เนื่องจากมีจำนวนเต็ม 2 ประกาศ (2 * ขนาดของ (int)) = 8 ไบต์ ขอบคุณ
youpilat13

1
สวัสดี @ youpilat13 :2และ:6เป็นจริงระบุ 2 และ 6 บิตไม่ใช่เต็ม 32 บิตในกรณีนี้ someBits.x ซึ่งมีเพียง 2 บิตเท่านั้นที่สามารถเก็บค่าที่เป็นไปได้ 4 ค่า: 00, 01, 10 และ 11 (1, 2, 3 และ 4) มันสมเหตุสมผลหรือไม่ นี่คือบทความเกี่ยวกับคุณสมบัติ: geeksforgeeks.org/bit-fields-c
Kyle Burton

11

ดูสิ่งนี้ด้วย:

สำหรับ Microsoft Visual C:

http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx

และ GCC เรียกร้องความเข้ากันได้กับคอมไพเลอร์ของ Microsoft:

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

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

ฉันค่อนข้างมั่นใจว่าคำสั่งซื้อของสมาชิกนั้นรับประกันใน Cแต่ฉันจะไม่นับที่มันเมื่อเขียนข้ามแพลตฟอร์มหรือข้ามคอมไพเลอร์โปรแกรม


4
"ฉันค่อนข้างมั่นใจว่าคำสั่งซื้อของสมาชิกจะทำเสียงฮึดฮัดใน C" ใช่ C99 พูดว่า: "ภายในวัตถุโครงสร้างสมาชิกที่ไม่ใช่เขตข้อมูลบิตและหน่วยที่มีเขตข้อมูลบิตอยู่จะมีที่อยู่ที่เพิ่มขึ้นตามลำดับที่มีการประกาศ" ความดีมาตรฐานเพิ่มเติมได้ที่: stackoverflow.com/a/37032302/895245
Ciro Santilli 法轮功冠状病病六四事件法轮功


8

ขนาดของโครงสร้างใหญ่กว่าผลรวมของชิ้นส่วนเพราะสิ่งที่เรียกว่าการบรรจุ ตัวประมวลผลเฉพาะมีขนาดข้อมูลที่ต้องการซึ่งทำงานได้ ขนาดที่ต้องการตัวประมวลผลที่ทันสมัยที่สุดหาก 32- บิต (4 ไบต์) การเข้าถึงหน่วยความจำเมื่อข้อมูลอยู่ในขอบเขตชนิดนี้จะมีประสิทธิภาพมากกว่าสิ่งที่ข้ามเขตแดนขนาดนั้น

ตัวอย่างเช่น. พิจารณาโครงสร้างที่เรียบง่าย:

struct myStruct
{
   int a;
   char b;
   int c;
} data;

หากเครื่องเป็นเครื่อง 32- บิตและข้อมูลถูกจัดเรียงบนขอบเขต 32 บิตเราจะเห็นปัญหาทันที (สมมติว่าไม่มีการจัดแนวโครงสร้าง) ในตัวอย่างนี้ให้เราสมมติว่าข้อมูลโครงสร้างเริ่มต้นที่ที่อยู่ 1024 (0x400 - โปรดทราบว่า 2 บิตที่ต่ำที่สุดเป็นศูนย์ดังนั้นข้อมูลจะถูกจัดแนวกับขอบเขต 32 บิต) การเข้าถึง data.a จะทำงานได้ดีเพราะมันเริ่มที่ขอบเขต - 0x400 การเข้าถึง data.b จะทำงานได้ดีเพราะอยู่ที่ 0x404 - ขอบเขต 32 บิตอื่น แต่โครงสร้างที่ไม่ได้จัดแนวจะทำให้ data.c อยู่ที่ 0x405 data.c ขนาด 4 ไบต์อยู่ที่ 0x405, 0x406, 0x407, 0x408 บนเครื่อง 32- บิตระบบจะอ่าน data.c ในระหว่างรอบหนึ่งหน่วยความจำ แต่จะได้รับ 3 จาก 4 ไบต์เท่านั้น (ไบต์ที่ 4 อยู่ในขอบเขตถัดไป) ดังนั้นระบบจะต้องทำการเข้าถึงหน่วยความจำที่สองเพื่อรับไบต์ที่ 4

ทีนี้ถ้าแทนที่จะใส่ data.c ที่ address 0x405 คอมไพเลอร์จะเสริมโครงสร้างด้วย 3 ไบต์และใส่ data.c ที่แอดเดรส 0x408 จากนั้นระบบจะต้องการเพียง 1 รอบในการอ่านข้อมูลซึ่งจะช่วยลดเวลาในการเข้าถึงองค์ประกอบข้อมูลนั้น 50% Padding สลับประสิทธิภาพหน่วยความจำเพื่อประสิทธิภาพการประมวลผล เนื่องจากคอมพิวเตอร์สามารถมีหน่วยความจำจำนวนมาก (หลายกิกะไบต์) คอมไพเลอร์รู้สึกว่าการสลับ (ความเร็วสูงกว่าขนาด) นั้นเหมาะสม

น่าเสียดายที่ปัญหานี้กลายเป็นฆาตกรเมื่อคุณพยายามส่งโครงสร้างผ่านเครือข่ายหรือแม้แต่เขียนข้อมูลไบนารีไปยังไฟล์ไบนารี การเติมเต็มระหว่างองค์ประกอบของโครงสร้างหรือคลาสสามารถขัดขวางข้อมูลที่ส่งไปยังไฟล์หรือเครือข่าย ในการเขียนโค้ดพกพา (อันที่จะไปกับคอมไพเลอร์ต่าง ๆ ) คุณอาจจะต้องเข้าถึงแต่ละองค์ประกอบของโครงสร้างแยกต่างหากเพื่อให้แน่ใจว่า "บรรจุ" ที่เหมาะสม

ในขณะที่คอมไพเลอร์ที่แตกต่างกันมีความสามารถแตกต่างกันในการจัดการการจัดโครงสร้างข้อมูล ตัวอย่างเช่นใน Visual C / C ++ คอมไพเลอร์สนับสนุนคำสั่ง #pragma pack สิ่งนี้จะช่วยให้คุณปรับการบรรจุและการจัดแนวข้อมูล

ตัวอย่างเช่น:

#pragma pack 1
struct MyStruct
{
    int a;
    char b;
    int c;
    short d;
} myData;

I = sizeof(myData);

ตอนนี้ฉันควรมีความยาว 11 โดยไม่มี pragma ฉันสามารถเป็นอะไรก็ได้ตั้งแต่ 11 ถึง 14 (และสำหรับบางระบบมากถึง 32) ขึ้นอยู่กับการบรรจุเริ่มต้นของคอมไพเลอร์


สิ่งนี้อธิบายถึงผลที่ตามมาของการขยายโครงสร้าง แต่ไม่ตอบคำถาม
Keith Thompson

" ... เนื่องจากสิ่งที่เรียกว่าการบรรจุ ... - ฉันคิดว่าคุณหมายถึง" ช่องว่างภายใน "" ขนาดที่ต้องการของโปรเซสเซอร์ที่ทันสมัยที่สุดถ้า 32- บิต (4 ไบต์) "- นั่นเป็นบิตของการขยายใหญ่เกินไปโดยทั่วไป รองรับขนาด 8, 16, 32 และ 64 บิตบ่อยครั้งที่แต่ละขนาดมีการจัดตำแหน่งของตัวเองและฉันไม่แน่ใจว่าคำตอบของคุณเพิ่มข้อมูลใหม่ที่ไม่ได้อยู่ในคำตอบที่ยอมรับแล้ว
Keith Thompson

1
เมื่อฉันบอกว่าการบรรจุฉันหมายถึงวิธีที่คอมไพเลอร์แพ็คข้อมูลลงในโครงสร้าง (และสามารถทำได้โดยการแพ็ดไอเท็มเล็ก ๆ แต่ไม่จำเป็นต้องแพด แต่มันแพ็คเสมอ) สำหรับขนาด - ฉันกำลังพูดถึงสถาปัตยกรรมระบบไม่ใช่สิ่งที่ระบบจะรองรับการเข้าถึงข้อมูล (ซึ่งแตกต่างจากสถาปัตยกรรมบัสพื้นฐาน) สำหรับความคิดเห็นสุดท้ายของคุณฉันได้ให้คำอธิบายที่ง่ายขึ้นและขยายออกในแง่มุมหนึ่งของการแลกเปลี่ยน (ความเร็วเทียบกับขนาด) - ปัญหาการเขียนโปรแกรมที่สำคัญ ฉันยังอธิบายวิธีการแก้ไขปัญหา - นั่นไม่ใช่คำตอบที่ยอมรับ
sid1138

"บรรจุ" #pragma packในบริบทนี้มักจะหมายถึงสมาชิกจัดสรรให้กระชับยิ่งขึ้นกว่าค่าเริ่มต้นเช่นเดียวกับ หากสมาชิกได้รับการจัดสรรในการจัดตำแหน่งเริ่มต้นโดยทั่วไปฉันจะบอกว่าโครงสร้างไม่ได้บรรจุ
Keith Thompson

การบรรจุเป็นชนิดของคำที่มากเกินไป มันหมายถึงวิธีที่คุณใส่องค์ประกอบโครงสร้างไว้ในหน่วยความจำ คล้ายกับความหมายของการวางวัตถุลงในกล่อง (บรรจุสำหรับการเคลื่อนย้าย) นอกจากนี้ยังหมายถึงการใส่องค์ประกอบลงในหน่วยความจำโดยไม่ต้องใส่แผ่นรอง (มือสั้นสำหรับ จากนั้นมีเวอร์ชันคำสั่งของคำในคำสั่ง #pragma pack
sid1138

5

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

นอกจากนี้ไลบรารีอาจถูกคอมไพล์ภายใต้ x86 ด้วย int 32- บิตและคุณอาจเปรียบเทียบส่วนประกอบในกระบวนการ 64 บิตจะให้ผลลัพธ์ที่แตกต่างถ้าคุณทำด้วยมือ


5

ร่างมาตรฐาน C99 N1256

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 ขนาดของผู้ประกอบการ :

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

6.7.2.1 โครงสร้างและตัวระบุสหภาพ :

13 ... อาจมีช่องว่างภายในที่ไม่มีชื่อในวัตถุโครงสร้าง แต่ไม่ใช่จุดเริ่มต้น

และ:

15 อาจมีช่องว่างภายในที่ไม่มีชื่อในตอนท้ายของโครงสร้างหรือสหภาพ

คุณลักษณะสมาชิกอาร์เรย์แบบยืดหยุ่น C99 ใหม่( struct S {int is[];};) อาจส่งผลกระทบต่อการขยาย:

16 เป็นกรณีพิเศษองค์ประกอบสุดท้ายของโครงสร้างที่มีสมาชิกที่มีชื่อมากกว่าหนึ่งรายอาจมีประเภทอาเรย์ที่ไม่สมบูรณ์ สิ่งนี้เรียกว่าสมาชิกอาเรย์ที่มีความยืดหยุ่น ในสถานการณ์ส่วนใหญ่สมาชิกอาร์เรย์ที่ยืดหยุ่นจะถูกละเว้น โดยเฉพาะอย่างยิ่งขนาดของโครงสร้างจะเหมือนกับว่าสมาชิกอาร์เรย์ที่มีความยืดหยุ่นถูกละเว้นยกเว้นว่ามันอาจมีช่องว่างภายในเพิ่มเติมกว่าการละเว้นจะบ่งบอกถึง

ประเด็นปัญหาการพกพาของ Annex J นั้นย้ำ:

ต่อไปนี้ไม่ได้ระบุ: ...

  • ค่าของการเสริมไบต์เมื่อจัดเก็บค่าในโครงสร้างหรือสหภาพ (6.2.6.1)

ร่างมาตรฐาน C ++ 11 N3337

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 ขนาดของ :

2 เมื่อนำไปใช้กับคลาสผลที่ได้คือจำนวนไบต์ในวัตถุของคลาสนั้นรวมถึงการเติมเต็มที่จำเป็นสำหรับการวางวัตถุประเภทนั้นในอาร์เรย์

9.2 สมาชิกกลุ่ม :

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

ฉันรู้ C ++ เพียงพอที่จะเข้าใจหมายเหตุ :-)


4

นอกเหนือจากคำตอบอื่น ๆ แล้ว struct สามารถ (แต่ไม่ปกติ) มีฟังก์ชั่นเสมือนจริงซึ่งในกรณีนี้ขนาดของ struct จะรวมพื้นที่สำหรับ vtbl ด้วย


8
ไม่มาก ในการใช้งานโดยทั่วไปสิ่งที่จะถูกเพิ่ม struct เป็น vtable ชี้
Don Wakefield

3

ภาษา C ทำให้คอมไพเลอร์มีอิสระบางอย่างเกี่ยวกับตำแหน่งขององค์ประกอบโครงสร้างในหน่วยความจำ:

  • หลุมหน่วยความจำอาจปรากฏขึ้นระหว่างส่วนประกอบสองส่วนใด ๆ และหลังจากส่วนประกอบสุดท้าย มันเป็นเพราะความจริงที่ว่าวัตถุบางประเภทในคอมพิวเตอร์เป้าหมายอาจถูก จำกัด ด้วยขอบเขตของการกำหนดที่อยู่
  • ขนาด "หน่วยความจำรู" รวมอยู่ในผลลัพธ์ของขนาดของผู้ให้บริการ ขนาดไม่รวมขนาดของอาเรย์แบบยืดหยุ่นซึ่งมีอยู่ใน C / C ++
  • การประยุกต์ใช้ภาษาบางอย่างอนุญาตให้คุณควบคุมโครงร่างหน่วยความจำของโครงสร้างผ่านตัวเลือก pragma และคอมไพเลอร์

ภาษา C ให้ความมั่นใจกับโปรแกรมเมอร์ของโครงร่างองค์ประกอบในโครงสร้าง:

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

ปัญหาที่เกี่ยวข้องกับการจัดตำแหน่งองค์ประกอบ:

  • คอมพิวเตอร์แต่ละเครื่องเรียงเส้นขอบของวัตถุในวิธีที่ต่างกัน
  • ข้อ จำกัด ที่แตกต่างกันเกี่ยวกับความกว้างของฟิลด์บิต
  • คอมพิวเตอร์ต่างกันเกี่ยวกับวิธีจัดเก็บไบต์ในคำ (Intel 80x86 และ Motorola 68000)

การจัดตำแหน่งทำงานอย่างไร:

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

ป.ล. ข้อมูลรายละเอียดเพิ่มเติมอยู่ที่นี่: "ซามูเอลพีแฮร์ริสัน, Guy L.Steele อ้างอิง CA, (5.6.2 - 5.6.7)


2

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

struct pixel {
    unsigned char red;   // 0
    unsigned char green; // 1
    unsigned int alpha;  // 4 (gotta skip to an aligned offset)
    unsigned char blue;  // 8 (then skip 9 10 11)
};

// next offset: 12

สถาปัตยกรรม x86 สามารถดึงที่อยู่ที่ไม่ตรงแนวได้เสมอ อย่างไรก็ตามมันจะช้ากว่าและเมื่อการวางแนวไม่ตรงกับสองบรรทัดของแคชที่ต่างกันมันจะแสดงสองบรรทัดของแคชเมื่อการเข้าถึงที่ถูกจัดชิดจะขับไล่หนึ่งบรรทัด

สถาปัตยกรรมบางอันต้องดักอ่านและเขียนที่ไม่ตรงแนวและสถาปัตยกรรม ARM รุ่นแรก (อันที่พัฒนาไปสู่ซีพียูมือถือทุกวันนี้) ... เอาละพวกเขาแค่ส่งคืนข้อมูลที่ไม่ดีสำหรับพวกเขา (พวกเขาเพิกเฉยบิตต่ำ ๆ )

สุดท้ายโปรดทราบว่าบรรทัดแคชอาจมีขนาดใหญ่ตามอำเภอใจและคอมไพเลอร์ไม่ได้พยายามเดาสิ่งเหล่านั้นหรือทำการแลกเปลี่ยน space-vs-speed การตัดสินใจจัดตำแหน่งนั้นเป็นส่วนหนึ่งของ ABI และเป็นตัวแทนของการจัดตำแหน่งขั้นต่ำที่จะเติมแคชให้เท่ากันในที่สุด

TL; DR:การจัดตำแหน่งเป็นสิ่งสำคัญ

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