ทำไมโครงสร้างขนาด 3 แทนที่จะเป็น 2


91

ฉันได้กำหนดโครงสร้างนี้:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

sizeof(col)ให้ฉันเอาท์พุทของ 3 แต่ไม่ควรจะเป็น 2 หรือไม่? ถ้าฉันแสดงความคิดเห็นเพียงองค์ประกอบเดียวsizeofคือ 2 ฉันไม่เข้าใจว่าทำไม: ห้าองค์ประกอบของ 3 บิตเท่ากับ 15 บิตและน้อยกว่า 2 ไบต์

มี "ขนาดภายใน" ในการกำหนดโครงสร้างเช่นนี้หรือไม่? ฉันแค่ต้องการคำชี้แจงเพราะจากความคิดของฉันเกี่ยวกับภาษาจนถึงตอนนี้ฉันคาดว่าจะมีขนาด 2 ไบต์ไม่ใช่ 3


4
อาจเป็นการเพิ่มประสิทธิภาพของการจัดตำแหน่ง เริ่มต้นไบต์ใหม่หากขนาดบิตถัดไปไม่พอดีกับพื้นที่ที่ถูกครอบครองจริง
πάνταῥεῖ

4
เว้นแต่คุณจะมีข้อ จำกัด ภายนอกบางอย่างที่ต้องการการบรรจุบิตและแพลตฟอร์มของคุณให้การรับประกันเพิ่มเติมเกี่ยวกับข้อเสนอมาตรฐานมีจุดเล็กน้อยในการใช้บิตฟิลด์
David Rodríguez - น้ำลายไหล

3
ขอให้สังเกตว่าสำหรับ C โดยใช้ถ่านเป็นแบบพกพาน้อยกว่าการใช้ int, stackoverflow.com/a/23987436/23118
hlovdal

2
โปรดทราบว่ามีการกำหนดการใช้งานเกือบทุกอย่างเกี่ยวกับฟิลด์บิต คุณอาจได้รับคำตอบที่แตกต่างจากคอมไพเลอร์ที่แตกต่างกันและจะไม่มีการไล่เบี้ย โปรดทราบว่าเนื่องจากคุณไม่ได้ระบุsigned charหรือunsigned charคุณไม่สามารถบอกได้โดยไม่ต้องดูเอกสารว่าคอมไพเลอร์จะถือว่า 'ธรรมดา' charในช่องบิตเป็นลายเซ็นหรือไม่ได้ลงนามและการตัดสินใจ (ในทางทฤษฎี) อาจแตกต่างจากการตัดสินใจว่า 'ธรรมดา' charถูกลงนามหรือไม่ได้ลงนามเมื่อใช้นอกบิตฟิลด์
Jonathan Leffler

3
โดยเฉพาะใน C99, §6.7.2.1โครงสร้างและสหภาพ specifiers, ¶4 บิตสนามจะมีประเภทที่เป็นรุ่นที่มีคุณสมบัติเหมาะสมหรือไม่เหมาะสมของ_Bool, signed int, unsigned int, หรือบางประเภทการดำเนินงานอื่น ๆ ที่กำหนดไว้ การใช้charจึงจัดอยู่ในหมวดหมู่ 'ประเภทอื่น ๆ ที่กำหนดให้ใช้งานได้'
Jonathan Leffler

คำตอบ:


95

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

ผลรวมของบิตที่คุณใช้ในโครงสร้างคือ 15 ดังนั้นขนาดที่เหมาะสมที่สุดที่จะพอดีกับข้อมูลจำนวนมากนั้นจะเป็น a short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

รหัสด้านบน (สำหรับแพลตฟอร์ม 64 บิตเช่นของฉัน) จะให้ผล2สำหรับโครงสร้างที่สอง สำหรับสิ่งที่มีขนาดใหญ่กว่า a shortโครงสร้างจะเติมไม่เกินหนึ่งองค์ประกอบของประเภทที่ใช้ดังนั้น - สำหรับแพลตฟอร์มเดียวกันนั้นโครงสร้างจะลงท้ายด้วยขนาดที่สี่สำหรับintแปดสำหรับlongฯลฯ


1
คำจำกัดความของโครงสร้างที่เสนอยังคงผิด คำจำกัดความของโครงสร้างที่ถูกต้องจะใช้ "สั้นที่ไม่ได้ลงนาม"
user3629249

21
@ user3629249 เหตุใดจึงไม่ได้ลงนามสั้น 'ถูกต้อง' หากผู้ใช้ต้องการจัดเก็บจาก -4 ถึง 3 สั้นก็ถูกต้อง หากผู้ใช้ต้องการจัดเก็บจาก 0 ถึง 7 แล้วย่อที่ไม่ได้ลงชื่อนั้นถูกต้อง คำถามเดิมใช้ประเภทที่มีลายเซ็น แต่ฉันไม่สามารถบอกได้ว่าเจตนาหรือไม่ตั้งใจ
Bruce Dawson

2
เหตุใดจึงมีความแตกต่างระหว่างบีทวินcharและshort?
GingerPlusPlus

5
@BruceDawson: มาตรฐานอนุญาตให้นำไปใช้งานโดยcharไม่ได้ลงนาม ...
Thomas Eding

@ThomasEding True มาตรฐานอนุญาตให้ใช้ถ่านไม่ได้ลงนาม แต่ประเด็นหลักของฉันยังคงอยู่คือไม่มีการให้เหตุผลในการอ้างว่า short ที่ไม่ได้ลงนามนั้นถูกต้อง (แม้ว่าโดยปกติจะเป็น)
Bruce Dawson

78

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

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(คำสั่งของเขตข้อมูล / ช่องว่างภายในไบต์เดียวกันไม่ได้มีเจตนา แต่เพียงเพื่อให้แนวคิดแก่คุณเนื่องจากคอมไพเลอร์สามารถกำหนดได้ว่าชอบอย่างไร)


16

charสองคนแรกบิตเขตพอดีเดียว อันที่สามไม่สามารถใส่ได้charและต้องการใหม่ 3 + 3 + 3 = 9 ซึ่งไม่พอดีกับอักขระ 8 บิต

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


15

คอมไพเลอร์ส่วนใหญ่อนุญาตให้คุณสามารถควบคุมการขยายการเช่นการใช้#pragma s นี่คือตัวอย่างของ GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

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


9

แม้ว่ามาตรฐาน ANSI C จะระบุน้อยเกินไปเกี่ยวกับวิธีการบรรจุบิตฟิลด์เพื่อให้ได้เปรียบที่สำคัญเหนือ "คอมไพเลอร์ได้รับอนุญาตให้บรรจุบิตฟิลด์ได้ตามที่เห็นสมควร" แต่ในหลาย ๆ กรณีก็ห้ามไม่ให้คอมไพเลอร์บรรจุสิ่งต่างๆอย่างมีประสิทธิภาพสูงสุด

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

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

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

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

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

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

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


2
คอมไพเลอร์ที่แตกต่างกันจะจัดวางฟิลด์บิตต่างกัน ฉันเขียนเอกสารบางอย่างเกี่ยวกับวิธีการทำงานของ Visual C ++ ที่อาจเกี่ยวข้อง ชี้ให้เห็นข้อผิดพลาดที่น่ารำคาญบางประการ: randomascii.wordpress.com/2010/06/06/…
บรู

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