เหตุใดการกำหนดค่าให้กับฟิลด์บิตจึงไม่ให้ค่าเดิมกลับคืนมา


96

ฉันเห็นรหัสด้านล่างในโพสต์ Quora นี้ :

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = 1;
  if(s.enabled == 1)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

ในทั้งสอง C & C ++ ผลลัพธ์ของโค้ดที่เป็นที่ไม่คาดคิด ,

ถูกปิดใช้งาน !!

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

ใครสามารถให้คำอธิบายที่ละเอียดกว่านี้?


หมายเหตุ : ทั้งแท็ก & จำเป็นต้องใช้เนื่องจากมาตรฐานของพวกเขาแตกต่างกันเล็กน้อยสำหรับการอธิบายบิตฟิลด์ ดูคำตอบสำหรับสเปค CและC ++ เปค


46
เนื่องจาก bitfield ถูกประกาศตามที่intฉันคิดว่ามันสามารถเก็บค่า0และ-1.
Osiris

6
ลองคิดดูว่า int ร้านค้า -1 เป็นอย่างไร บิตทั้งหมดถูกตั้งค่าเป็น 1 ดังนั้นหากคุณมีเพียงบิตเดียวมันจะต้องเป็น -1 อย่างชัดเจน ดังนั้น 1 และ -1 ใน int 1 บิตจึงเหมือนกัน เปลี่ยนการตรวจสอบเป็น 'if (s.enabled! = 0)' และใช้งานได้ เพราะ 0 มันไม่ได้
Jürgen

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

8
คุณลองเปลี่ยนเป็นstruct mystruct { unsigned int enabled:1; };หรือยัง?
ChatterOne

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

คำตอบ:


78

บิตฟิลด์ถูกกำหนดโดยมาตรฐานไม่ดีอย่างไม่น่าเชื่อ เมื่อได้รับรหัสนี้struct mystruct {int enabled:1;};เราจะไม่รู้ว่า:

  • พื้นที่นี้ใช้พื้นที่เท่าใด - หากมีบิต / ไบต์ช่องว่างภายในและตำแหน่งที่อยู่ในหน่วยความจำ
  • ตำแหน่งที่บิตอยู่ในหน่วยความจำ ไม่ได้กำหนดและขึ้นอยู่กับความอดทน
  • ไม่ว่าint:nบิตฟิลด์จะถือได้ว่าลงชื่อหรือไม่ได้ลงนาม

ในส่วนสุดท้าย C17 6.7.2.1/10 กล่าวว่า:

บิตฟิลด์ถูกตีความว่ามีประเภทจำนวนเต็มที่ลงชื่อหรือไม่ได้ลงชื่อซึ่งประกอบด้วยจำนวนบิตที่ระบุ125)

หมายเหตุที่ไม่ใช่กฎเกณฑ์อธิบายข้างต้น:

125)ตามที่ระบุไว้ใน 6.7.2 ด้านบนหากตัวระบุประเภทที่ใช้จริงคือintหรือชื่อ typedef ที่กำหนดเป็นintจะมีการกำหนดการใช้งานไม่ว่าจะมีการลงชื่อหรือไม่ได้ลงนามในช่องบิต

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

แนวทางปฏิบัติที่ดี:

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

5
ในที่ทำงานเรามี static_asserts เกี่ยวกับขนาดและที่อยู่ของ bitfields เพื่อให้แน่ใจว่าพวกเขาไม่ได้มีการบุนวม เราใช้ bitfields สำหรับการลงทะเบียนฮาร์ดแวร์ในเฟิร์มแวร์ของเรา
Michael

4
@Lundin: สิ่งที่น่าเกลียดกับ # define-d มาสก์และออฟเซ็ตคือโค้ดของคุณถูกทิ้งเกลื่อนไปด้วยการเปลี่ยนแปลงและตัวดำเนินการ AND / OR ที่ชาญฉลาด ด้วย bitfields คอมไพลเลอร์จะดูแลสิ่งนั้นให้คุณ
Michael

4
@ Michael ด้วย bitfields คอมไพเลอร์จะดูแลสิ่งนั้นให้คุณ ก็ไม่เป็นไรหากมาตรฐานของคุณสำหรับ "ดูแลสิ่งนั้น" "ไม่พกพา" และ "คาดเดาไม่ได้" เหมืองสูงกว่านั้น
Andrew Henle

3
@AndrewHenle Leushenko กำลังบอกว่าจากมุมมองของมาตรฐาน C นั้นขึ้นอยู่กับการใช้งานไม่ว่าจะเลือกที่จะปฏิบัติตาม x86-64 ABI หรือไม่ก็ตาม
mtraceur

3
@AndrewHenle ใช่ฉันเห็นด้วยทั้งสองจุด ประเด็นของฉันคือฉันคิดว่าความไม่เห็นด้วยของคุณกับ Leushenko ทำให้เกิดความจริงที่ว่าคุณกำลังใช้ "การกำหนดการนำไปใช้งาน" เพื่ออ้างถึงเฉพาะสิ่งที่ไม่ได้กำหนดโดยมาตรฐาน C อย่างเคร่งครัดหรือกำหนดโดยแพลตฟอร์ม ABI อย่างเคร่งครัดและเขาใช้เพื่ออ้างถึง กับสิ่งที่ไม่ได้กำหนดไว้อย่างเคร่งครัดโดยมาตรฐาน C
mtraceur

58

ฉันไม่เข้าใจว่าเป็นไปได้อย่างไรที่เราตั้งค่าบางอย่างแล้วมันไม่ปรากฏขึ้นตามที่เป็นอยู่

คุณถามว่าทำไมคอมไพล์เทียบกับให้ข้อผิดพลาด?

ใช่มันควรจะทำให้คุณมีข้อผิดพลาด และถ้าคุณใช้คำเตือนของคอมไพเลอร์ ใน GCC ด้วย-Werror -Wall -pedantic:

main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1' 
changes value from '1' to '-1' [-Werror=overflow]
   s.enabled = 1;
           ^

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

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

(TL; DR - หากคุณมีความซับซ้อนพอที่จะ "ต้องการ" บิตฟิลด์ได้อย่างถูกต้องแสดงว่าไม่ได้กำหนดไว้อย่างชัดเจนเพียงพอที่จะให้บริการคุณ)


15
ผู้เขียนมาตรฐานอยู่ในช่วงวันหยุดในวันที่ออกแบบบทบิตฟิลด์ ภารโรงก็เลยต้องทำ ไม่มีเหตุผลใด ๆเกี่ยวกับวิธีการออกแบบบิตฟิลด์
Lundin

9
ไม่มีเหตุผลทางเทคนิคที่สอดคล้องกัน แต่นั่นทำให้ฉันสรุปได้ว่ามีเหตุผลทางการเมืองนั่นคือเพื่อหลีกเลี่ยงการทำโค้ดที่มีอยู่หรือการนำไปใช้งานไม่ถูกต้อง แต่ผลลัพธ์ก็คือมี bitfields น้อยมากที่คุณสามารถพึ่งพาได้
John Bollinger

6
@JohnBollinger มีเรื่องการเมืองเกิดขึ้นอย่างแน่นอนซึ่งสร้างความเสียหายให้กับ C90 เป็นอย่างมาก ครั้งหนึ่งฉันเคยพูดคุยกับสมาชิกคนหนึ่งของคณะกรรมการที่อธิบายที่มาของอึจำนวนมาก - มาตรฐาน ISO ไม่ได้รับอนุญาตให้สนับสนุนเทคโนโลยีที่มีอยู่บางอย่าง นี่คือเหตุผลที่เราติดอยู่กับสิ่งที่ผิดปกติเช่นการสนับสนุนส่วนเติมเต็มของ 1 และขนาดที่ลงนามการลงนามที่กำหนดในการนำไปใช้งานการcharรองรับไบต์ที่ไม่ใช่ 8 บิตเป็นต้นพวกเขาไม่ได้รับอนุญาตให้คอมพิวเตอร์ปัญญาอ่อนเสียเปรียบตลาด
Lundin

1
@Lundin มันน่าสนใจที่จะเห็นคอลเลกชันของการเขียนและการชันสูตรพลิกศพจากผู้ที่เชื่อว่าการแลกเปลี่ยนเกิดขึ้นด้วยความผิดพลาดและทำไม ฉันสงสัยว่าการศึกษาสิ่งเหล่านี้"เราทำครั้งที่แล้วและได้ผล / ไม่ได้ผล" มากเพียงใดได้กลายเป็นความรู้เชิงสถาบันที่จะแจ้งให้ทราบในกรณีต่อไปเทียบกับเรื่องราวในหัวของผู้คน
HostileFork บอกว่าอย่าไว้วางใจใน

1
สิ่งนี้ยังคงแสดงเป็นจุดที่ 1 ในหลักการดั้งเดิมของ C ในกฎบัตร C2x: "โค้ดที่มีอยู่มีความสำคัญการใช้งานที่มีอยู่ไม่ได้" ... "ไม่มีการนำไปใช้เป็นตัวอย่างในการกำหนด C: สันนิษฐานว่าการใช้งานที่มีอยู่ทั้งหมดจะต้องเปลี่ยนแปลงเล็กน้อยเพื่อให้สอดคล้องกับมาตรฐาน"
Leushenko

23

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

struct mystruct { int enabled:1; };

ประกาศenableเป็นฟิลด์ 1 บิต เพราะมันมีการลงนามค่าที่ถูกต้องและ-1 0การตั้งค่าฟิลด์ให้1ล้นบิตที่ย้อนกลับไป-1(นี่คือพฤติกรรมที่ไม่ได้กำหนด)

โดยพื้นฐานแล้วเมื่อจัดการกับบิตฟิลด์ที่ลงนามแล้วค่าสูงสุด2^(bits - 1) - 1คือ0ในกรณีนี้


"ลงนามแล้วค่าที่ถูกต้องคือ -1 และ 0" ใครบอกว่าเซ็น? ไม่ได้กำหนดไว้ แต่เป็นพฤติกรรมที่กำหนดโดยการนำไปใช้งาน หากมีการลงนามแล้วค่าที่ถูกต้องและ- +ส่วนเสริม 2 ไม่สำคัญ
Lundin

5
@Lundin หมายเลขชมเชย 1 บิตสองค่ามีค่าที่เป็นไปได้สองค่าเท่านั้น หากบิตถูกตั้งค่าเนื่องจากเป็นบิตเครื่องหมายจึงเป็น -1 ถ้าไม่ได้ตั้งค่าไว้แสดงว่าเป็น "บวก" 0 ฉันรู้ว่านี่คือการกำหนดการนำไปใช้ฉันแค่อธิบายผลลัพธ์โดยใช้การปลูกถ่ายที่พบบ่อยที่สุด
NathanOliver

1
คีย์ในที่นี้คือส่วนเติมเต็มของ 2 หรือแบบฟอร์มอื่น ๆ ที่ลงนามไม่สามารถทำงานได้ด้วยบิตเดียวที่มีอยู่
Lundin

1
@JohnBollinger ฉันเข้าใจอย่างนั้น นั่นเป็นเหตุผลที่ฉันมีที่ประชุมสภาว่านี่คือการกำหนดการดำเนินการ อย่างน้อยสำหรับ 3 บิ๊กพวกเขาทั้งหมดปฏิบัติintตามที่ลงนามในกรณีนี้ เป็นเรื่องที่น่าเสียดายที่บิตฟิลด์อยู่ภายใต้การระบุ โดยพื้นฐานแล้วนี่คือคุณสมบัตินี้ปรึกษาคอมไพเลอร์ของคุณเกี่ยวกับวิธีใช้งาน
NathanOliver

1
@Lundin ถ้อยคำมาตรฐานสำหรับการแทนจำนวนเต็มที่ลงนามสามารถจัดการกับกรณีที่มีบิตค่าเป็นศูนย์ได้ดีอย่างน้อยก็ในสองในสามทางเลือกที่อนุญาต สิ่งนี้ได้ผลเนื่องจากกำหนดค่าสถานที่ (ลบ) เพื่อเซ็นชื่อบิตแทนที่จะให้การตีความอัลกอริทึม
John Bollinger

10

คุณอาจคิดว่าในระบบเสริมของ 2 บิตซ้ายสุดคือบิตเครื่องหมาย จำนวนเต็มที่ลงนามใด ๆ ที่มีชุดบิตทางซ้ายสุดจึงเป็นค่าลบ

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

ค่าที่จำนวนเต็มลงนาม 1 บิตสามารถเก็บได้คือ-2^(n-1)= -2^(1-1)= -2^0= -1และ2^n-1= 2^1-1=0


8

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

12.2.4

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

enum BOOL { FALSE=0, TRUE=1 };
struct A {
  BOOL b:1;
};
A a;
void f() {
  a.b = TRUE;
  if (a.b == TRUE)    // yields true
    { /* ... */ }
}

- ตัวอย่างตอนท้าย]


เมื่อมองแวบแรกส่วนที่เป็นตัวหนาจะเปิดให้ตีความได้ อย่างไรก็ตามเจตนาที่ถูกต้องจะชัดเจนเมื่อenum BOOLได้มาจากไฟล์int.

enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = TRUE;
  if(s.enabled == TRUE)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

ด้วยรหัสด้านบนจะให้คำเตือนโดยไม่มี-Wall -pedantic:

คำเตือน: 'mystruct :: enable' มีขนาดเล็กเกินไปที่จะเก็บค่าทั้งหมดของ 'enum BOOL' struct mystruct { BOOL enabled:1; };

ผลลัพธ์คือ:

ถูกปิดใช้งาน !! (เมื่อใช้enum BOOL : int)

หากenum BOOL : intทำให้เรียบง่ายenum BOOLผลลัพธ์จะเป็นไปตามมาตรฐานด้านบนระบุ:

ถูกเปิดใช้งาน (เมื่อใช้งานenum BOOL)


ดังนั้นจึงสามารถสรุปได้เช่นเดียวกับคำตอบอื่น ๆ เพียงไม่กี่คำintประเภทนั้นไม่ใหญ่พอที่จะเก็บค่า "1" ไว้ในบิตบิตเดียว


0

ไม่มีอะไรผิดกับความเข้าใจของคุณเกี่ยวกับ bitfields ที่ฉันเห็น สิ่งที่ฉันเห็นคือคุณกำหนด mystruct ใหม่ก่อนเป็นstruct mystruct {int enable: 1; }แล้วเป็นโครงสร้าง mystruct s; . สิ่งที่คุณควรเขียนคือ:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
    mystruct s; <-- Get rid of "struct" type declaration
    s.enabled = 1;
    if(s.enabled == 1)
        printf("Is enabled\n"); // --> we think this to be printed
    else
        printf("Is disabled !!\n");
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.