C / C ++: บังคับลำดับฟิลด์บิตและการจัดตำแหน่ง


87

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

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

บนโปรเซสเซอร์ Intel ที่มีคอมไพเลอร์ GCC ฟิลด์ต่างๆจะถูกจัดวางไว้ในหน่วยความจำตามที่แสดง Message.versionเป็น 3 บิตแรกในบัฟเฟอร์และMessage.typeตามด้วย หากฉันพบตัวเลือกการบรรจุโครงสร้างที่เทียบเท่ากันสำหรับคอมไพเลอร์ต่างๆสิ่งนี้จะเป็นข้ามแพลตฟอร์มหรือไม่


17
เนื่องจากบัฟเฟอร์เป็นชุดของไบต์ไม่ใช่บิต "3 บิตแรกในบัฟเฟอร์" จึงไม่ใช่แนวคิดที่แน่นอน คุณจะพิจารณา 3 บิตลำดับต่ำสุดของไบต์แรกเป็น 3 บิตแรกหรือ 3 บิตลำดับสูงสุด?
คาเฟ่

2
เมื่อทำการโอนย้ายบนเครือข่าย "3 บิตแรกในบัฟเฟอร์" จะถูกกำหนดไว้เป็นอย่างดี
Joshua

2
@Joshua IIRC อีเธอร์เน็ตจะส่งบิตที่มีนัยสำคัญน้อยที่สุดของแต่ละไบต์ก่อน (ซึ่งเป็นสาเหตุที่บิตออกอากาศอยู่ที่ตำแหน่งนั้น)
tc.

เมื่อคุณพูดว่า "พกพา" และ "ข้ามแพลตฟอร์ม" คุณหมายถึงอะไร ไฟล์ปฏิบัติการจะเข้าถึงคำสั่งได้อย่างถูกต้องโดยไม่คำนึงถึง OS เป้าหมายหรือ - โค้ดจะคอมไพล์โดยไม่คำนึงถึง toolchain?
Garet Claborn

คำตอบ:


103

ไม่มันจะไม่สามารถพกพาได้อย่างเต็มที่ ตัวเลือกการบรรจุสำหรับโครงสร้างเป็นส่วนขยายและไม่สามารถพกพาได้อย่างเต็มที่ นอกจากนั้น C99 §6.7.2.1ย่อหน้าที่ 10 ยังกล่าวว่า: "ลำดับของการจัดสรรบิตฟิลด์ภายในหน่วย

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


ใช่ GCC ตั้งข้อสังเกตเฉพาะว่า bitfields ถูกจัดเรียงตาม ABI ไม่ใช่การนำไปใช้งาน ดังนั้นการอยู่ในคอมไพเลอร์เดียวจึงไม่เพียงพอที่จะรับประกันการสั่งซื้อ สถาปัตยกรรมจะต้องได้รับการตรวจสอบด้วย ฝันร้ายเล็กน้อยสำหรับการพกพาจริงๆ
underscore_d

10
เหตุใดมาตรฐาน C จึงไม่รับประกันคำสั่งซื้อสำหรับช่องบิต
Aaron Campbell

8
เป็นเรื่องยากที่จะกำหนด "ลำดับ" ของบิตภายในไบต์อย่างสม่ำเสมอและแบบพอร์ทได้โดยน้อยกว่ามากลำดับของบิตที่อาจข้ามขอบเขตไบต์ คำจำกัดความใด ๆ ที่คุณตั้งไว้จะไม่ตรงกับแนวทางปฏิบัติที่มีอยู่จำนวนมาก
Stephen Canon

2
กำหนดการใช้งานช่วยให้สามารถปรับให้เหมาะสมเฉพาะแพลตฟอร์มได้ ในบางแพลตฟอร์มการเติมช่องว่างระหว่างช่องบิตสามารถปรับปรุงการเข้าถึงลองนึกภาพฟิลด์เจ็ดบิตสี่ช่องใน int 32 บิต: การจัดตำแหน่งให้ตรงกันทุก ๆ บิตที่ 8 เป็นการปรับปรุงที่สำคัญสำหรับแพลตฟอร์มที่มีการอ่านไบต์
peterchen

ไม่packedบังคับใช้การสั่งซื้อ: stackoverflow.com/questions/1756811/...วิธีการบังคับใช้การสั่งซื้อบิต: stackoverflow.com/questions/6728218/gcc-compiler-bit-order
Ciro Santilli郝海东冠状病六四事件法轮功

45

ช่องบิตแตกต่างกันอย่างมากในแต่ละคอมไพเลอร์ถึงคอมไพเลอร์ขออภัย

ด้วย GCC เครื่องจักร endian ขนาดใหญ่จะวางบิตที่ใหญ่ที่สุดเครื่องแรกและ endian เล็ก ๆ น้อย ๆ จะวางส่วนท้ายเล็กน้อยก่อน

K&R กล่าวว่า "สมาชิกฟิลด์ที่อยู่ติดกัน [บิต -] ของโครงสร้างจะถูกบรรจุลงในหน่วยเก็บข้อมูลที่ขึ้นอยู่กับการนำไปใช้งานในทิศทางที่ขึ้นอยู่กับการนำไปใช้งานเมื่อฟิลด์ที่ตามหลังฟิลด์อื่นจะไม่พอดี ... อาจถูกแบ่งระหว่างหน่วย ช่องว่างที่ไม่มีชื่อความกว้าง 0 บังคับให้ช่องว่างนี้ ... "

ดังนั้นหากคุณต้องการเลย์เอาต์ไบนารีอิสระของเครื่องจักรคุณต้องทำด้วยตัวเอง

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


K&R ถือเป็นข้อมูลอ้างอิงที่มีประโยชน์จริง ๆ หรือไม่เนื่องจากเป็นการกำหนดมาตรฐานล่วงหน้าและ (ฉันคิดว่า?) อาจถูกแทนที่ในหลาย ๆ ด้านหรือไม่?
underscore_d

1
K&R ของฉันคือ post-ANSI
Joshua

1
ตอนนี้น่าอาย: ฉันไม่รู้เลยว่าพวกเขาออกการแก้ไขหลัง ANSI ความผิดฉันเอง!
underscore_d

35

ควรหลีกเลี่ยง Bitfields - ไม่สามารถพกพาได้ระหว่างคอมไพเลอร์แม้จะใช้กับแพลตฟอร์มเดียวกันก็ตาม จากมาตรฐาน C99 6.7.2.1/10 - "โครงสร้างและตัวระบุสหภาพ" (มีถ้อยคำที่คล้ายกันในมาตรฐาน C90):

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

คุณไม่สามารถรับประกันได้ว่าฟิลด์บิตจะ 'ขยาย' ขอบเขต int หรือไม่และคุณไม่สามารถระบุได้ว่าบิตฟิลด์เริ่มต้นที่ระดับต่ำสุดของ int หรือระดับไฮเอนด์ของ int (ไม่ขึ้นอยู่กับว่าโปรเซสเซอร์เป็น big-endian หรือ little-endian)

ชอบ bitmasks ใช้อินไลน์ (หรือแม้แต่มาโคร) เพื่อตั้งค่าล้างและทดสอบบิต


2
ลำดับของบิตฟิลด์สามารถกำหนดได้ในเวลาคอมไพล์
Greg A. Woods

9
นอกจากนี้ bitfields เป็นที่ต้องการอย่างมากเมื่อจัดการกับแฟล็กบิตที่ไม่มีการแสดงภายนอกภายนอกโปรแกรม (เช่นบนดิสก์หรือในรีจิสเตอร์หรือในหน่วยความจำที่โปรแกรมอื่นเข้าถึงเป็นต้น)
Greg A. Woods

1
@ GregA.Woods: หากเป็นเช่นนั้นจริงโปรดให้คำตอบโดยอธิบายถึงวิธีการ ฉันไม่พบสิ่งใดนอกจากความคิดเห็นของคุณเมื่อ googling มัน ...
mozzbozz

1
@ GregA.Woods: ขออภัยควรเขียนถึงความคิดเห็นที่ฉันอ้างถึง ฉันหมายถึง: คุณบอกว่า "ลำดับของบิตฟิลด์สามารถกำหนดได้ในเวลาคอมไพล์" ฉันทำอะไรไม่ได้เกี่ยวกับเรื่องนี้และจะทำอย่างไร
mozzbozz

2
@mozzbozz ดูที่planix.com/~woods/projects/wsg2000.cและค้นหาคำจำกัดความและการใช้งาน_BIT_FIELDS_LTOHและ_BIT_FIELDS_HTOL
Greg A. Woods

11

endianness กำลังพูดถึงคำสั่งไบต์ไม่ใช่คำสั่งบิต ทุกวันนี้มั่นใจได้ 99% ว่าคำสั่งบิตได้รับการแก้ไขแล้ว อย่างไรก็ตามเมื่อใช้ bitfields ควรนับ endianness ดูตัวอย่างด้านล่าง

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

6
ผลลัพธ์ของ a และ b บ่งชี้ว่า endianness ยังคงพูดถึงคำสั่งบิตและคำสั่งไบต์
โปรแกรมเมอร์ Windows

ตัวอย่างที่ยอดเยี่ยมกับการจัดลำดับบิตและปัญหาการสั่งซื้อไบต์
โจนาธาน

1
คุณรวบรวมและเรียกใช้โค้ดจริงหรือไม่? ค่าของ "a" และ "b" ดูไม่สมเหตุสมผลสำหรับฉัน: โดยพื้นฐานแล้วคุณกำลังบอกว่าคอมไพเลอร์จะสลับ nibbles ภายในไบต์เนื่องจาก endianness ในกรณีของ "d" endiannes ไม่ควรส่งผลต่อลำดับไบต์ภายในอาร์เรย์ถ่าน (สมมติว่า char มีความยาว 1 ไบต์) หากคอมไพเลอร์ทำเช่นนั้นเราจะไม่สามารถวนซ้ำผ่านอาร์เรย์โดยใช้พอยน์เตอร์ได้ ในทางกลับกันถ้าคุณใช้อาร์เรย์ของจำนวนเต็ม 16 บิตสองตัวเช่น uint16 data [] = {0x1234,0x5678}; แล้ว d จะเป็น 0x7856 ในระบบ endian เล็กน้อย
Krauss

6

ส่วนใหญ่แล้วอาจเป็นได้ แต่อย่าเดิมพันฟาร์มเพราะถ้าคุณผิดคุณจะสูญเสียก้อนโต

หากคุณจำเป็นต้องมีข้อมูลไบนารีที่เหมือนกันจริงๆคุณจะต้องสร้าง bitfields ด้วย bitmasks เช่นคุณใช้ข้อความสั้นที่ไม่ได้ลงชื่อ (16 บิต) สำหรับข้อความจากนั้นสร้างสิ่งต่างๆเช่น versionMask = 0xE000 เพื่อแทนบิตสูงสุดสาม

มีปัญหาคล้ายกันกับการจัดตำแหน่งภายในโครงสร้าง ตัวอย่างเช่น Sparc, PowerPC และ 680x0 CPUs เป็น big-endian ทั้งหมดและค่าเริ่มต้นทั่วไปสำหรับคอมไพเลอร์ Sparc และ PowerPC คือการจัดเรียงสมาชิกของโครงสร้างบนขอบเขต 4 ไบต์ อย่างไรก็ตามคอมไพเลอร์หนึ่งตัวที่ฉันใช้สำหรับ 680x0 จะจัดชิดขอบ 2 ไบต์เท่านั้น - และไม่มีตัวเลือกในการเปลี่ยนการจัดแนว!

ดังนั้นสำหรับโครงสร้างบางอย่างขนาดของ Sparc และ PowerPC จะเท่ากัน แต่มีขนาดเล็กกว่า 680x0 และสมาชิกบางส่วนอยู่ในการชดเชยหน่วยความจำที่แตกต่างกันภายในโครงสร้าง

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


1

ขอบคุณ @BenVoigt สำหรับความคิดเห็นที่มีประโยชน์มากของคุณเริ่มต้น

ไม่ได้สร้างขึ้นเพื่อบันทึกความทรงจำ

แหล่งที่มาของลินุกซ์ไม่ใช้ช่องบิตเพื่อให้ตรงไปโครงสร้างภายนอก: /usr/include/linux/ip.hมีรหัสนี้สำหรับไบต์แรกของดาต้า IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

อย่างไรก็ตามในแง่ของความคิดเห็นของคุณฉันให้พยายามที่จะได้รับการทำงานสำหรับหลายไบต์ฟิลด์บิตfrag_off


-9

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


5
ฉันคิดว่ามันผิดที่จะระบุว่ามันโง่ที่จะใช้บิตฟิลด์เนื่องจากเป็นวิธีที่สะอาดมากในการแสดงการลงทะเบียนฮาร์ดแวร์ซึ่งสร้างขึ้นเพื่อจำลองใน C.
trondd

13
@trondd: ไม่ได้สร้างขึ้นเพื่อบันทึกหน่วยความจำ Bitfields ไม่ได้มีไว้เพื่อแมปกับโครงสร้างข้อมูลภายนอกเช่นการลงทะเบียนฮาร์ดแวร์ที่แมปหน่วยความจำโปรโตคอลเครือข่ายหรือรูปแบบไฟล์ หากมีวัตถุประสงค์เพื่อแมปกับโครงสร้างข้อมูลภายนอกใบสั่งบรรจุหีบห่อจะได้รับการกำหนดมาตรฐาน
Ben Voigt

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

@BenVoigt อาจเป็นจริง แต่ถ้าโปรแกรมเมอร์เต็มใจที่จะยืนยันว่าการสั่งซื้อคอมไพเลอร์ / ABI ตรงกับสิ่งที่พวกเขาต้องการและเสียสละการพกพาอย่างรวดเร็วตามนั้นพวกเขาก็สามารถทำหน้าที่นั้นได้อย่างแน่นอน สำหรับ 9 * มวลที่เชื่อถือได้ของ "นักเขียนโค้ดในโลกแห่งความจริง" ซึ่งถือว่าการใช้บิตฟิลด์ทั้งหมดเป็น "ไม่เป็นมืออาชีพ / ขี้เกียจ / โง่" และพวกเขาระบุสิ่งนี้ไว้ที่ไหน
underscore_d

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