ฉันรู้สึกว่าการเข้าถึงไฟล์ union
สมาชิกนอกเหนือจากชุดสุดท้ายคือ UB แต่ดูเหมือนว่าฉันจะไม่พบข้อมูลอ้างอิงที่มั่นคง (นอกเหนือจากคำตอบที่อ้างว่าเป็น UB แต่ไม่ได้รับการสนับสนุนจากมาตรฐาน)
ดังนั้นพฤติกรรมที่ไม่ได้กำหนดหรือไม่?
ฉันรู้สึกว่าการเข้าถึงไฟล์ union
สมาชิกนอกเหนือจากชุดสุดท้ายคือ UB แต่ดูเหมือนว่าฉันจะไม่พบข้อมูลอ้างอิงที่มั่นคง (นอกเหนือจากคำตอบที่อ้างว่าเป็น UB แต่ไม่ได้รับการสนับสนุนจากมาตรฐาน)
ดังนั้นพฤติกรรมที่ไม่ได้กำหนดหรือไม่?
คำตอบ:
ความสับสนคือ C อนุญาตให้มีการกดพิมพ์ผ่านยูเนี่ยนอย่างชัดเจนในขณะที่ C ++ (C ++ 11) ไม่ได้รับอนุญาต
6.5.2.3 โครงสร้างและสมาชิกสหภาพแรงงาน
95) หากสมาชิกที่ใช้ในการอ่านเนื้อหาของอ็อบเจ็กต์ยูเนี่ยนไม่เหมือนกับสมาชิกล่าสุดที่ใช้ในการจัดเก็บค่าในอ็อบเจ็กต์ส่วนที่เหมาะสมของการแทนค่าอ็อบเจ็กต์จะถูกตีความใหม่เป็นการแสดงอ็อบเจ็กต์ในใหม่ พิมพ์ตามที่อธิบายไว้ใน 6.2.6 (กระบวนการบางครั้งเรียกว่า '' type punning '') นี่อาจเป็นการแสดงกับดัก
สถานการณ์กับ C ++:
9.5 สหภาพแรงงาน [class.union]
ในสหภาพสมาชิกข้อมูลที่ไม่คงที่ส่วนใหญ่สามารถใช้งานได้ตลอดเวลานั่นคือค่าของสมาชิกข้อมูลที่ไม่คงที่ส่วนใหญ่สามารถเก็บไว้ในสหภาพได้ตลอดเวลา
C ++ ต่อมามีภาษาที่อนุญาตให้ใช้สหภาพที่มีstruct
s ที่มีลำดับเริ่มต้นทั่วไป อย่างไรก็ตามสิ่งนี้ไม่อนุญาตให้มีการเจาะประเภท
เพื่อตรวจสอบว่าการเจาะแบบสหภาพคือได้รับอนุญาตใน C ++ เราจะต้องค้นหาต่อไป จำได้ว่าc99 เป็นการอ้างอิงเชิงบรรทัดฐานสำหรับ C ++ 11 (และ C99 มีภาษาคล้ายกับ C11 ที่อนุญาตให้ใช้การพิมพ์ยูเนี่ยน):
3.9 ประเภท [basic.types]
4 - การแสดงวัตถุของวัตถุประเภท T คือลำดับของวัตถุถ่านที่ไม่ได้ลงนาม N ที่นำขึ้นโดยวัตถุประเภท T โดยที่ N เท่ากับขนาดของ (T) การแทนค่าของอ็อบเจ็กต์คือชุดของบิตที่เก็บค่าของชนิด T สำหรับประเภทที่สามารถคัดลอกได้เล็กน้อยการแทนค่าคือชุดของบิตในการแทนอ็อบเจ็กต์ที่กำหนดค่าซึ่งเป็นองค์ประกอบที่ไม่ต่อเนื่องของการนำไปใช้งาน - de fi ned ชุดของค่า 42
42) จุดประสงค์คือโมเดลหน่วยความจำของ C ++ เข้ากันได้กับ ISO / IEC 9899 ภาษาโปรแกรม C
มันน่าสนใจเป็นพิเศษเมื่อเราอ่าน
3.8 อายุการใช้งานวัตถุ [basic.life]
อายุการใช้งานของอ็อบเจ็กต์ประเภท T เริ่มต้นเมื่อ: - ได้รับการจัดเก็บที่มีการจัดตำแหน่งและขนาดที่เหมาะสมสำหรับประเภท T และ - ถ้าอ็อบเจ็กต์มีการกำหนดค่าเริ่มต้นที่ไม่สำคัญการกำหนดค่าเริ่มต้นจะเสร็จสมบูรณ์
ดังนั้นสำหรับประเภทดั้งเดิม (ซึ่งipso factoมีการเริ่มต้นเล็กน้อย) ที่มีอยู่ในการรวมกันอายุการใช้งานของวัตถุจะครอบคลุมอย่างน้อยอายุการใช้งานของสหภาพเอง สิ่งนี้ทำให้เราสามารถเรียกใช้
3.9.2 ประเภทของสารประกอบ [basic.compound]
หากออบเจ็กต์ประเภท T ตั้งอยู่ที่แอดเดรส A ตัวชี้ประเภท cv T * ที่มีค่าคือแอดเดรส A จะถูกบอกให้ชี้ไปที่อ็อบเจ็กต์นั้นโดยไม่คำนึงว่าจะได้รับค่าอย่างไร
สมมติว่าการดำเนินการที่เราสนใจคือ type-punning คือการรับค่าของสมาชิกสหภาพแรงงานที่ไม่ได้ใช้งานและตามที่ระบุไว้ข้างต้นว่าเรามีการอ้างอิงที่ถูกต้องไปยังวัตถุที่อ้างถึงโดยสมาชิกนั้นการดำเนินการนั้นมีค่าเท่ากับ - การแปลงค่า:
4.1 การแปลง Lvalue-to-rvalue [Conv.lval]
ค่า glvalue ของประเภท non-function และ non-array
T
สามารถแปลงเป็น prvalue ได้ หากT
เป็นประเภทที่ไม่สมบูรณ์โปรแกรมที่จำเป็นต้องมีการแปลงนี้จะมีรูปแบบไม่ถูกต้อง หากอ็อบเจ็กต์ที่ glvalue อ้างถึงไม่ใช่อ็อบเจ็กต์ประเภทT
และไม่ใช่อ็อบเจ็กต์ประเภทที่มาจากT
หรือถ้าอ็อบเจ็กต์ไม่ได้กำหนดค่าเริ่มต้นโปรแกรมที่จำเป็นต้องมีการแปลงนี้จะมีพฤติกรรมที่ไม่เหมาะสม
คำถามก็คือว่าวัตถุที่เป็นสมาชิกสหภาพที่ไม่ได้ใช้งานถูกเตรียมใช้งานโดยหน่วยเก็บข้อมูลไปยังสมาชิกสหภาพที่ใช้งานอยู่หรือไม่ เท่าที่ฉันสามารถบอกได้นี่ไม่ใช่กรณีและแม้ว่า:
char
เก็บอาร์เรย์และด้านหลัง (3.9: 2) หรือการเข้าถึงสหภาพโดยสมาชิกที่ไม่ได้ใช้งานได้รับการกำหนดและถูกกำหนดให้เป็นไปตามวัตถุและการแทนค่าการเข้าถึงโดยไม่มีการแทรกกลางอย่างใดอย่างหนึ่งข้างต้นเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ สิ่งนี้มีผลกระทบต่อการเพิ่มประสิทธิภาพที่อนุญาตให้ดำเนินการกับโปรแกรมดังกล่าวเนื่องจากการใช้งานอาจสันนิษฐานได้ว่าพฤติกรรมที่ไม่ได้กำหนดจะไม่เกิดขึ้น
นั่นคือแม้ว่าเราสามารถสร้างค่า lvalue ให้กับสมาชิกสหภาพแรงงานที่ไม่ได้ใช้งานได้อย่างถูกต้องตามกฎหมาย (ซึ่งเป็นเหตุผลว่าทำไมการมอบหมายให้กับสมาชิกที่ไม่ได้ใช้งานโดยไม่มีการก่อสร้างก็เป็นเรื่องปกติ) แต่ก็ถือว่าไม่ได้เริ่มต้น
memcpy
การใช้งาน (การเข้าถึงวัตถุที่ใช้unsigned char
lvalues) ก็ไม่ได้รับอนุญาตเข้าถึง*p
หลังจากint *p = 0; const int *const *pp = &p;
(แม้ว่าการแปลงนัยจากint**
การconst int*const*
ที่ถูกต้อง) ก็ไม่ได้รับอนุญาตเข้าถึงแม้หลังจากที่c
ปัญหา CWG 616 คำใหม่อนุญาตหรือไม่ นอกจากนี้ยังมี [basic.lval] struct S s; const S &c = s;
&
หมายของตัวดำเนินการยูนารีหมายถึงเมื่อนำไปใช้กับสมาชิกสหภาพ ฉันคิดว่าตัวชี้ผลลัพธ์ควรจะใช้งานได้เพื่อเข้าถึงสมาชิกอย่างน้อยก็จนกว่าจะถึงครั้งต่อไปที่จะใช้ lvalue ของสมาชิกอื่นทั้งทางตรงและทางอ้อมในครั้งถัดไป แต่ใน gcc ตัวชี้ไม่สามารถใช้งานได้แม้จะนานขนาดนั้นซึ่งทำให้เกิดคำถามว่าอะไร ตัว&
ดำเนินการควรจะหมายถึง
มาตรฐาน C ++ 11 บอกอย่างนี้
9.5 สหภาพแรงงาน
ในสหภาพสมาชิกข้อมูลที่ไม่คงที่ส่วนใหญ่สามารถใช้งานได้ตลอดเวลานั่นคือค่าของสมาชิกข้อมูลที่ไม่คงที่ส่วนใหญ่สามารถเก็บไว้ในสหภาพได้ตลอดเวลา
หากเก็บไว้เพียงค่าเดียวคุณจะอ่านค่าอื่นได้อย่างไร มันไม่ได้อยู่ที่นั่น
เอกสาร gcc แสดงรายการสิ่งนี้ภายใต้พฤติกรรมที่กำหนดการนำไปใช้งาน
- สมาชิกของยูเนี่ยนอ็อบเจ็กต์ถูกเข้าถึงโดยใช้สมาชิกประเภทอื่น (C90 6.3.2.3)
ไบต์ที่เกี่ยวข้องของการเป็นตัวแทนของอ็อบเจ็กต์จะถือว่าเป็นอ็อบเจ็กต์ประเภทที่ใช้สำหรับการเข้าถึง โปรดดูที่ Type-punning นี่อาจเป็นการแสดงกับดัก
แสดงว่าสิ่งนี้ไม่จำเป็นสำหรับมาตรฐาน C
2016-01-05: จากความคิดเห็นฉันได้เชื่อมโยงกับC99 Defect Report # 283ซึ่งเพิ่มข้อความที่คล้ายกันเป็นเชิงอรรถในเอกสารมาตรฐาน C:
78a) หากสมาชิกที่ใช้ในการเข้าถึงเนื้อหาของอ็อบเจ็กต์ยูเนี่ยนไม่เหมือนกับสมาชิกล่าสุดที่ใช้ในการจัดเก็บค่าในอ็อบเจ็กต์ส่วนที่เหมาะสมของการแทนค่าอ็อบเจ็กต์จะถูกตีความอีกครั้งเป็นการแสดงอ็อบเจ็กต์ในใหม่ พิมพ์ตามที่อธิบายไว้ใน 6.2.6 (กระบวนการบางครั้งเรียกว่า "type punning") นี่อาจเป็นการแสดงกับดัก
ไม่แน่ใจว่าจะชี้แจงได้มากหรือไม่โดยพิจารณาว่าเชิงอรรถไม่ใช่บรรทัดฐานสำหรับมาตรฐาน
ฉันคิดว่ามาตรฐานที่ใกล้เคียงที่สุดคือการบอกว่าพฤติกรรมที่ไม่ได้กำหนดคือจุดที่กำหนดพฤติกรรมสำหรับสหภาพที่มีลำดับเริ่มต้นทั่วไป (C99, §6.5.2.3 / 5):
มีการรับประกันพิเศษอย่างหนึ่งเพื่อลดความซับซ้อนในการใช้สหภาพแรงงาน: หากสหภาพแรงงานมีโครงสร้างหลายอย่างที่ใช้ลำดับเริ่มต้นร่วมกัน (ดูด้านล่าง) และหากออบเจ็กต์ของสหภาพมีโครงสร้างเหล่านี้อยู่ในปัจจุบันจะได้รับอนุญาตให้ตรวจสอบทั่วไป ส่วนเริ่มต้นของส่วนใดก็ได้ที่สามารถมองเห็นการประกาศประเภทที่สมบูรณ์ของสหภาพได้ โครงสร้างสองโครงสร้างใช้ลำดับเริ่มต้นร่วมกันหากสมาชิกที่เกี่ยวข้องมีชนิดที่เข้ากันได้ (และสำหรับบิตฟิลด์มีความกว้างเท่ากัน) สำหรับลำดับของสมาชิกเริ่มต้นตั้งแต่หนึ่งตัวขึ้นไป
C ++ 11 ให้ข้อกำหนด / การอนุญาตที่คล้ายกันที่§9.2 / 19:
ถ้ายูเนี่ยนโครงร่างมาตรฐานมีโครงสร้างโครงร่างมาตรฐานสองชุดขึ้นไปที่แบ่งลำดับเริ่มต้นร่วมกันและถ้าออบเจ็กต์แบบร่วมโครงร่างมาตรฐานในปัจจุบันมีโครงสร้างโครงร่างมาตรฐานอย่างใดอย่างหนึ่งเหล่านี้จะได้รับอนุญาตให้ตรวจสอบส่วนเริ่มต้นทั่วไปของใด ๆ ของพวกเขา. โครงสร้างโครงร่างมาตรฐานสองรายการใช้ลำดับเริ่มต้นร่วมกันหากสมาชิกที่สอดคล้องกันมีชนิดที่เข้ากันได้กับโครงร่างและสมาชิกทั้งสองไม่มีฟิลด์บิตหรือทั้งสองเป็นฟิลด์บิตที่มีความกว้างเท่ากันสำหรับลำดับของสมาชิกเริ่มต้นตั้งแต่หนึ่งตัวขึ้นไป
แม้ว่าทั้งสองจะไม่ได้ระบุไว้โดยตรง แต่ทั้งสองก็มีนัยยะที่ชัดเจนว่า "การตรวจสอบ" (การอ่าน) สมาชิก "ได้รับอนุญาต" ก็ต่อเมื่อ 1) เป็น (ส่วนหนึ่งของ) สมาชิกที่เขียนล่าสุดหรือ 2) เป็นส่วนหนึ่งของการเริ่มต้นทั่วไป ลำดับ.
นั่นไม่ใช่คำสั่งโดยตรงว่าการทำอย่างอื่นเป็นพฤติกรรมที่ไม่ได้กำหนด แต่เป็นสิ่งที่ใกล้เคียงที่สุดที่ฉันทราบ
union
ที่ไม่ได้กำหนดไว้เนื่องจากฉันได้รับความประทับใจจากบล็อกหนึ่ง ๆ ว่าสิ่งนี้ใช้ได้และได้สร้างโครงสร้างและโครงการขนาดใหญ่หลายโครงการไว้รอบ ๆ ตอนนี้ฉันคิดว่าฉันอาจจะโอเคเพราะฉันunion
มีคลาสที่มีประเภทเดียวกันอยู่ด้านหน้า
union
มีเช่น a uint8_t
และ a class Something { uint8_t myByte; [...] };
- ฉันจะถือว่าเงื่อนไขนี้จะใช้ที่นี่ด้วย แต่มันตั้งใจมากที่จะอนุญาตเฉพาะstruct
s โชคดีที่ฉันใช้สิ่งเหล่านี้แทนการใช้แบบดั้งเดิมอยู่แล้ว: O
คำตอบที่ยังไม่ได้กล่าวถึงคือเชิงอรรถ 37 ในย่อหน้า 21 ของหัวข้อ 6.2.5:
โปรดสังเกตว่าชนิดการรวมไม่รวมประเภทการรวมเนื่องจากวัตถุที่มีประเภทการรวมกันสามารถมีสมาชิกได้ครั้งละหนึ่งคนเท่านั้น
ข้อกำหนดนี้ดูเหมือนจะบอกเป็นนัยอย่างชัดเจนว่าคุณต้องไม่เขียนในสมาชิกและอ่านอีกข้อหนึ่ง ในกรณีนี้อาจเป็นพฤติกรรมที่ไม่ได้กำหนดโดยขาดข้อมูลจำเพาะ
ฉันอธิบายเรื่องนี้ด้วยตัวอย่าง
สมมติว่าเรามีสหภาพดังต่อไปนี้:
union A{
int x;
short y[2];
};
ผมคิดว่ามันsizeof(int)
ให้ 4 และนั่นsizeof(short)
ให้ 2.
เมื่อคุณเขียนunion A a = {10}
มันให้สร้างประเภท A ใหม่ใส่ค่า 10
ความทรงจำของคุณควรมีลักษณะเช่นนั้น: (โปรดจำไว้ว่าสมาชิกสหภาพแรงงานทั้งหมดได้รับตำแหน่งเดียวกัน)
| x | | y [0] | y [1] | ----------------------------------------- a-> | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1010 | -----------------------------------------
อย่างที่คุณเห็นค่าของ ax คือ 10 ค่าของ ay 1คือ 10 และค่าของ ay [0] คือ 0
ตอนนี้จะเกิดอะไรขึ้นถ้าฉันทำสิ่งนี้?
a.y[0] = 37;
หน่วยความจำของเราจะมีลักษณะดังนี้:
| x | | y [0] | y [1] | ----------------------------------------- a-> | 0000 0000 | 0010 0101 | 0000 0000 | 0000 1010 | -----------------------------------------
สิ่งนี้จะเปลี่ยนค่าของ ax เป็น 2424842 (เป็นทศนิยม)
ตอนนี้ถ้าสหภาพของคุณมีการลอยตัวหรือสองเท่าแผนที่หน่วยความจำของคุณจะยุ่งเหยิงมากขึ้นเนื่องจากวิธีการจัดเก็บตัวเลขที่แน่นอน ข้อมูลเพิ่มเติมที่คุณสามารถได้รับในที่นี่