#pragma pack effect


233

ฉันสงสัยว่ามีใครบางคนสามารถอธิบายให้ฉันฟังได้ว่า#pragma packคำสั่ง preprocessor ทำอะไรและที่สำคัญกว่านั้นคือเหตุใดจึงต้องการใช้มัน

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


1
มันบังคับเฉพาะการจัดตำแหน่ง / การบรรจุของ struct แต่เช่นเดียวกับ#pragmaคำสั่งทั้งหมดที่พวกเขากำลังดำเนินการที่กำหนดไว้
dreamlax

A mod s = 0โดยที่ A คือที่อยู่และ s คือขนาดของประเภทข้อมูล ตรวจสอบว่าข้อมูลไม่ได้อยู่ในแนวที่ไม่ตรงหรือไม่
legends2k

คำตอบ:


422

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

struct Test
{
   char AA;
   int BB;
   char CC;
};

คอมไพเลอร์สามารถเลือกวางโครงสร้างในหน่วยความจำเช่นนี้:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

และsizeof(Test)จะเป็น 4 × 3 = 12 แม้ว่าจะมีข้อมูลเพียง 6 ไบต์เท่านั้น กรณีการใช้งานที่พบบ่อยที่สุดสำหรับ#pragma(ความรู้ของฉัน) คือเมื่อทำงานกับอุปกรณ์ฮาร์ดแวร์ที่คุณต้องให้แน่ใจว่าคอมไพเลอร์ไม่ได้แทรกการเติมลงในข้อมูลและสมาชิกแต่ละคนจะติดตามก่อนหน้านี้ ด้วย#pragma pack(1)โครงสร้างข้างต้นจะถูกวางแบบนี้:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

และsizeof(Test)จะเป็น 1 × 6 = 6

ด้วย#pragma pack(2)โครงสร้างข้างต้นจะถูกวางแบบนี้:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

และsizeof(Test)จะเป็น 2 × 4 = 8

ลำดับของตัวแปรใน struct ก็มีความสำคัญเช่นกัน ด้วยตัวแปรที่เรียงลำดับดังนี้:

struct Test
{
   char AA;
   char CC;
   int BB;
};

และด้วย#pragma pack(2)โครงสร้างจะถูกวางไว้เช่นนี้:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

และsizeOf(Test)จะเป็น 3 × 2 = 6


76
มันอาจจะคุ้มค่าที่จะเพิ่มข้อเสียของการบรรจุ (การเข้าถึงวัตถุ unaligned จะช้าในที่ดีที่สุดกรณี แต่จะก่อให้เกิดข้อผิดพลาดในบางแพลตฟอร์ม.)
jalf

11
ดูเหมือนว่าการจัดแนว "ลงโทษประสิทธิภาพ" กล่าวถึงจริงอาจจะได้รับประโยชน์ในบางระบบdanluu.com/3c-conflict

6
@Pierier ไม่ได้จริงๆ โพสต์ดังกล่าวพูดถึงการจัดตำแหน่งที่ค่อนข้างรุนแรง (จัดเรียงตามขอบเขต 4KB) CPU คาดว่าการจัดตำแหน่งขั้นต่ำบางอย่างสำหรับชนิดข้อมูลต่าง ๆ แต่ในกรณีที่เลวร้ายที่สุดการจัดตำแหน่งแบบ 8 ไบต์ (ไม่นับประเภทเวกเตอร์ซึ่งอาจต้องมีการจัดตำแหน่งแบบ 16 หรือ 32 ไบต์) การไม่จัดแนวบนขอบเขตเหล่านั้นโดยทั่วไปจะช่วยให้คุณมีประสิทธิภาพการทำงานที่เห็นได้ชัดเจน (เนื่องจากการโหลดอาจต้องดำเนินการสองอย่างแทนการดำเนินการหนึ่ง) แต่ประเภทนั้นอยู่ในแนวที่ดีหรือไม่ใช่ การจัดตำแหน่งที่เข้มงวดกว่าที่ซื้อคุณไม่มีอะไร (และซากปรักหักพังแคชใช้ประโยชน์
jalf

6
กล่าวอีกนัยหนึ่งคู่คาดว่าจะอยู่ในขอบเขต 8 ไบต์ การวางไว้ในขอบเขต 7 ไบต์จะทำให้ประสิทธิภาพลดลง แต่วางไว้บนขอบเขต 16, 32, 64 หรือ 4096 ไบต์ซื้ออะไรที่คุณเหนือสิ่งที่ขอบเขต 8 ไบต์ให้คุณแล้ว คุณจะได้รับประสิทธิภาพการทำงานที่เหมือนกันจาก CPU ในขณะที่การใช้งานแคชแย่ลงมากสำหรับเหตุผลที่ระบุไว้ในโพสต์นั้น
jalf

4
ดังนั้นบทเรียนไม่ได้เป็น "บรรจุเป็นประโยชน์" (บรรจุละเมิดจัดตำแหน่งธรรมชาติประเภทเพื่อให้เจ็บประสิทธิภาพการทำงาน) แต่เพียงแค่ 'ไม่เกินชิดเกินกว่าสิ่งที่เป็นสิ่งจำเป็น'
jalf

27

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

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


17
หากต้องการเลิกทำในภายหลังให้ทำดังนี้: #pragma pack (push, 1) และ #pragma pack (ป๊อป)
malhal

16

มันบอกคอมไพเลอร์ขอบเขตเพื่อจัดแนววัตถุในโครงสร้างให้ ตัวอย่างเช่นถ้าฉันมีสิ่งที่ชอบ:

struct foo { 
    char a;
    int b;
};

ด้วยเครื่อง 32- บิตโดยทั่วไปคุณจะ "ต้องการ" มีช่องว่างระหว่าง 3 ไบต์aและbเพื่อที่bจะลงจอดที่ขอบเขต 4 ไบต์เพื่อเพิ่มความเร็วในการเข้าถึงสูงสุด

อย่างไรก็ตามหากคุณต้องจับคู่โครงสร้างที่กำหนดจากภายนอกที่คุณต้องการให้แน่ใจว่าคอมไพเลอร์วางโครงสร้างของคุณอย่างแม่นยำตามคำจำกัดความภายนอกนั้น ในกรณีนี้คุณสามารถให้คอมไพเลอร์ a #pragma pack(1)เพื่อบอกไม่ให้แทรกการเติมใด ๆ ระหว่างสมาชิก - ถ้าคำจำกัดความของโครงสร้างรวมถึงการขยายระหว่างสมาชิกคุณใส่มันอย่างชัดเจน (เช่นโดยทั่วไปกับสมาชิกที่มีชื่อunusedNหรือignoreNหรืออะไรบางอย่างในนั้น ใบสั่ง).


"คุณปกติ" ต้องการ "มี 3 padding ระหว่าง a และ b เพื่อให้ b จะลงที่ขอบเขต 4 ไบต์เพื่อเพิ่มความเร็วในการเข้าถึง" - วิธีที่ 3 padding จะเพิ่มความเร็วในการเข้าถึงสูงสุดอย่างไร
Ashwin

8
@Ashwin: การวางbที่ขอบเขต 4 ไบต์หมายความว่าโปรเซสเซอร์สามารถโหลดได้โดยการออกโหลด 4 ไบต์เดียว แม้ว่ามันจะขึ้นอยู่กับโปรเซสเซอร์ แต่ถ้ามันอยู่ในขอบเขตแปลกมีโอกาสที่ดีที่การโหลดมันจะต้องใช้โปรเซสเซอร์ในการออกคำสั่งโหลดสองคำสั่งแยกกันแล้วใช้ shifter เพื่อแยกชิ้นส่วนเหล่านั้นเข้าด้วยกัน บทลงโทษทั่วไปอยู่ที่อันดับ 3 ของการโหลดช้าลงของรายการนั้น
Jerry Coffin

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

2
@SF: มันสามารถ - แต่ถึงแม้ว่ามันจะไม่ใช่ไม่ถูกเข้าใจผิด - บนซีพียู x86 (สำหรับตัวอย่างที่ชัดเจน) การดำเนินการจะดำเนินการในฮาร์ดแวร์ แต่คุณยังคงได้รับชุดของการดำเนินการเดียวกัน และการชะลอตัว
Jerry Coffin

8

องค์ประกอบข้อมูล (เช่นสมาชิกของคลาสและ structs) โดยทั่วไปจะจัดตำแหน่งบนขอบเขต WORD หรือ DWORD สำหรับโปรเซสเซอร์รุ่นปัจจุบันเพื่อปรับปรุงเวลาในการเข้าถึง การดึง DWORD ตามที่อยู่ซึ่งไม่สามารถหารด้วย 4 ต้องมีรอบการทำงานของ CPU เพิ่มเติมอย่างน้อยหนึ่งรอบในตัวประมวลผล 32 บิต ดังนั้นถ้าคุณมีสมาชิกchar a, b, c;ชาแนลสามคนพวกเขามักจะใช้พื้นที่เก็บข้อมูล 6 หรือ 12 ไบต์

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


ที่จริงแล้วchar a,b,c;โดยปกติจะใช้พื้นที่จัดเก็บข้อมูล 3 หรือ 4 ไบต์ (อย่างน้อย x86) เนื่องจากความต้องการการจัดตำแหน่งของพวกเขาคือ 1 ไบต์ หากไม่เป็นเช่นนั้นคุณจะจัดการchar str[] = "foo";อย่างไร การเข้าถึง a charเป็น fetch-shift-mask แบบง่าย ๆ เสมอในขณะที่การเข้าถึง a intสามารถเป็น fetch-fetch-merge หรือเพียงดึงข้อมูลขึ้นอยู่กับว่ามันถูกจัดตำแหน่งหรือไม่ intมี (บน x86) การจัดตำแหน่งแบบ 32 บิต (4 ไบต์) เพราะมิฉะนั้นคุณจะได้รับ (พูด) ครึ่งintหนึ่งDWORDและอีกครึ่งและนั่นจะเป็นการค้นหาสองครั้ง
ทิม Julas

3

คอมไพเลอร์สามารถจัดตำแหน่งสมาชิกในโครงสร้างเพื่อให้ได้ประสิทธิภาพสูงสุดบนแพลตฟอร์มที่แน่นอน #pragma packคำสั่งอนุญาตให้คุณควบคุมการจัดตำแหน่งนั้น โดยปกติแล้วคุณควรปล่อยไว้ตามค่าเริ่มต้นเพื่อประสิทธิภาพสูงสุด หากคุณต้องการส่งโครงสร้างไปยังเครื่องระยะไกลโดยทั่วไปคุณจะใช้#pragma pack 1เพื่อแยกการจัดตำแหน่งที่ไม่ต้องการ


2

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

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

นอกจากนี้ยังอาจซ้อนทับโครงสร้างการลงทะเบียนภายในของอุปกรณ์ I / O บางอย่างเช่นตัวควบคุม UART หรือ USB เช่นกันเพื่อให้การเข้าถึงการลงทะเบียนผ่านโครงสร้างแทนที่จะเป็นที่อยู่โดยตรง


1

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

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


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

1

ฉันเคยใช้มันในรหัสก่อนหน้านี้แม้เพียงเพื่อติดต่อกับรหัสดั้งเดิม นี่เป็นแอปพลิเคชั่น Mac OS X Cocoa ที่จำเป็นในการโหลดไฟล์การตั้งค่าจากรุ่นคาร์บอนรุ่นก่อนหน้า (ซึ่งเป็นแบบย้อนกลับเข้ากันได้กับ M68k System 6.5 รุ่นดั้งเดิม ... คุณเข้าใจแล้ว) ไฟล์การกำหนดค่าตามความชอบในเวอร์ชันดั้งเดิมเป็นไบนารีดัมพ์ของโครงสร้างการกำหนดค่าที่ใช้#pragma pack(1)เพื่อหลีกเลี่ยงการเพิ่มพื้นที่ว่างและการบันทึกขยะ (เช่นไบต์การขยายที่จะเป็นในโครงสร้าง)

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


1

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


0

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

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

เอาต์พุตมีดังนี้: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48

แจ้งให้ทราบว่าขนาดของ struct ที่เป็นสิ่งที่นับไบต์ แต่ struct B มีการขยายเพิ่ม (ดูนี้สำหรับรายละเอียดเกี่ยวกับช่องว่างภายใน) เมื่อทำสิ่งนี้ตรงข้ามกับ #pragma pack คุณสามารถควบคุมการแปลง "รูปแบบการวางสาย" เป็นประเภทที่เหมาะสมได้ ตัวอย่างเช่น "char two [2]" ไปเป็น "short int" และ cetera


ไม่ผิดหรอก ถ้าคุณดูตำแหน่งในหน่วยความจำของ b.two มันไม่ใช่หนึ่งไบต์หลังจาก b.one (คอมไพเลอร์สามารถ (และมักจะ) จัดตำแหน่ง b.two ดังนั้นจึงจัดตำแหน่งให้เข้าถึงคำได้ สำหรับ a.two มันคือหนึ่งไบต์หลังจาก a.one หากคุณจำเป็นต้องเข้าถึง a.two เป็น int แบบสั้นคุณควรมีทางเลือก 2 ทางคือใช้สหภาพ (แต่มักจะล้มเหลวหากคุณมีปัญหา endianness) หรือแกะ / แปลงโดยใช้รหัส (ใช้ฟังก์ชัน ntohX ที่เหมาะสม)
xryl669

1
sizeofส่งกลับsize_tซึ่งจะต้องพิมพ์ออกมาใช้ %zuการใช้ตัวระบุรูปแบบที่ไม่ถูกต้องจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
phuclv

0

ทำไมต้องใช้มัน

เพื่อลดหน่วยความจำของโครงสร้าง

ทำไมไม่ควรใช้งาน

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