วัตถุประสงค์ของสหภาพใน C และ C ++


254

ก่อนหน้านี้ฉันใช้สหภาพอย่างสะดวกสบาย วันนี้ฉันตื่นตระหนกเมื่อฉันอ่านโพสต์นี้และรู้ว่ารหัสนี้

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

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

ปรับปรุง:

ฉันต้องการชี้แจงบางสิ่งในความเข้าใจย้อนหลัง

  • คำตอบสำหรับคำถามนั้นไม่เหมือนกันสำหรับ C และ C ++; น้องตัวน้อยที่ไม่รู้ของฉันติดแท็กเป็นทั้ง C และ C ++
  • หลังจากกำจัดสิ่งสกปรกด้วยมาตรฐานของ C ++ 11 ฉันไม่สามารถสรุปได้อย่างชัดเจนว่ามันเรียกร้องการเข้าถึง / ตรวจสอบสมาชิกสหภาพที่ไม่ได้ทำงานอยู่นั้นไม่ได้กำหนดไว้ ทั้งหมดที่ฉันสามารถหาได้คือ§9.5 / 1:

    หากการรวมแบบเลย์เอาต์มาตรฐานมีโครงสร้างเลย์เอาต์มาตรฐานหลายตัวที่ใช้ร่วมกันเริ่มต้นร่วมกันและหากวัตถุประเภทยูเนี่ยนแบบเลย์เอาต์มาตรฐานนี้มีหนึ่งในโครงสร้างแบบเลย์เอาท์แบบมาตรฐานจะอนุญาตให้ตรวจสอบลำดับเริ่มต้นทั่วไปของ ของสมาชิกโครงสร้าง layout แบบมาตรฐาน §9.2 / 19: โครงสร้างเลย์เอาต์มาตรฐานสองรายการใช้ลำดับเริ่มต้นร่วมกันหากสมาชิกที่เกี่ยวข้องมีประเภทที่เข้ากันได้กับโครงร่างและสมาชิกทั้งสองไม่เป็นบิตฟิลด์หรือทั้งสองเป็นบิตฟิลด์ที่มีความกว้างเท่ากันสำหรับลำดับแรกหรือมากกว่า สมาชิก.

  • ในขณะที่อยู่ใน C ( C99 TC3 - DR 283เป็นต้นไป) มันถูกกฎหมายที่จะทำเช่นนั้น ( ขอบคุณ Pascal Cuoq ที่นำสิ่งนี้ขึ้นมา) อย่างไรก็ตามการพยายามทำมันยังสามารถนำไปสู่พฤติกรรมที่ไม่ได้กำหนดหากค่าการอ่านเกิดขึ้นไม่ถูกต้อง (เรียกว่า "การแทนแทร็บ") สำหรับประเภทที่อ่านได้ มิฉะนั้นการอ่านค่าคือการใช้งานที่กำหนดไว้
  • C89 / 90 เรียกสิ่งนี้ภายใต้พฤติกรรมที่ไม่ระบุ (ภาคผนวก J) และหนังสือของ K & R กล่าวว่ามันถูกกำหนดไว้แล้ว อ้างอิงจาก K&R:

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

  • สารสกัดจาก TC ++ PL ของ Stroustrup (เหมืองที่เน้น)

    การใช้สหภาพอาจเป็นสิ่งจำเป็นสำหรับความเข้ากันได้ของข้อมูลบางครั้งใช้ในทางที่ผิดสำหรับ "การแปลงแบบ "

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


11
คอมไพเลอร์ได้รับอนุญาตให้แทรกการเติมระหว่างองค์ประกอบในโครงสร้าง ดังนั้นb, g, r,และอาจจะไม่ต่อเนื่องกันจึงไม่ตรงกับรูปแบบของการa uint32_tนี่คือนอกเหนือจากปัญหา Endianess ที่คนอื่นได้ชี้ให้เห็น
Thomas Matthews

8
นี่คือเหตุผลที่คุณไม่ควรติดแท็กคำถาม C และ C ++ คำตอบนั้นแตกต่างกัน แต่เนื่องจากผู้ตอบไม่แม้แต่จะบอกว่าพวกเขากำลังตอบแท็กอะไร (พวกเขารู้ด้วยหรือเปล่า) คุณก็จะกลายเป็นขยะ
Pascal Cuoq

5
@downvoter ขอบคุณที่ไม่ได้อธิบายฉันเข้าใจว่าคุณต้องการให้ฉันเข้าใจ
gripe

1
เกี่ยวกับความตั้งใจดั้งเดิมของการมีสหภาพจำไว้ว่ามาตรฐาน C โพสต์วันที่สหภาพ C หลายปี ดูอย่างรวดเร็วที่ Unix V7 จะแสดงการแปลงประเภทผ่านทางสหภาพ
ninjalj

3
scouring C++11's standard I couldn't conclusively say that it calls out accessing/inspecting a non-active union member is undefined [...] All I could find was §9.5/1...จริงๆ? คุณอ้างถึงบันทึกย่อข้อยกเว้นไม่ใช่จุดหลักที่จุดเริ่มต้นของย่อหน้า : "ในสหภาพสมาชิกส่วนใหญ่ของข้อมูลที่ไม่คงที่สามารถใช้งานได้ตลอดเวลานั่นคือมูลค่าสูงสุดของ สมาชิกข้อมูลที่ไม่คงที่สามารถจัดเก็บในสหภาพได้ตลอดเวลา " - และลงไปที่ p4: "โดยทั่วไปเราต้องใช้การโทร destructor อย่างชัดเจนและกำหนดตำแหน่งโอเปอเรเตอร์ใหม่เพื่อเปลี่ยนสมาชิกที่ใช้งานอยู่ของสหภาพ "
underscore_d

คำตอบ:


407

วัตถุประสงค์ของสหภาพแรงงานค่อนข้างชัดเจน แต่ด้วยเหตุผลบางอย่างที่คนคิดถึงมันบ่อยครั้ง

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

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

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

ด้วยเหตุผลบางอย่างจุดประสงค์ดั้งเดิมของสหภาพได้ "แทนที่" ด้วยสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง: เขียนสมาชิกหนึ่งคนของสหภาพแล้วตรวจสอบสมาชิกอีกคน การตีความหน่วยความจำชนิดนี้ (aka "type punning") ไม่ใช่การใช้งานที่ถูกต้องของสหภาพ โดยทั่วไปนำไปสู่พฤติกรรมที่ไม่ได้กำหนดอธิบายว่าเป็นการสร้างพฤติกรรมที่กำหนดไว้ในการนำไปใช้ใน C89 / 90

แก้ไข:การใช้สหภาพแรงงานเพื่อจุดประสงค์ในการตีพิมพ์ (เช่นการเขียนสมาชิกหนึ่งคนจากนั้นอ่านอีกคน) ได้รับคำจำกัดความที่ละเอียดยิ่งขึ้นในเอกสารอ้างอิงทางเทคนิคของมาตรฐาน C99 (ดูDR # 257และDR # 283 ) อย่างไรก็ตามโปรดทราบว่าสิ่งนี้อย่างเป็นทางการไม่ได้ป้องกันคุณจากการทำงานที่ไม่ได้กำหนดโดยพยายามอ่านการเป็นตัวแทนของแทร็บ


37
+1 สำหรับการทำอย่างละเอียดให้ตัวอย่างที่ใช้งานง่ายและพูดเกี่ยวกับมรดกของสหภาพ!
ตำนาน 2k

6
ปัญหาที่ฉันมีกับคำตอบนี้คือ OS ส่วนใหญ่ที่ฉันเห็นมีไฟล์ส่วนหัวที่ทำสิ่งนี้ ตัวอย่างเช่นฉันเห็นมันเป็นรุ่นเก่า (รุ่น 64 บิต) <time.h>ทั้งใน Windows และ Unix การยกเลิกเป็น "ไม่ถูกต้อง" และ "ไม่ได้กำหนด" ไม่เพียงพอหากฉันถูกเรียกให้เข้าใจโค้ดที่ใช้งานได้ในลักษณะนี้
TED

31
@AndreyT“ มันไม่เคยถูกกฎหมายที่จะใช้สหภาพแรงงานสำหรับประเภท punning จนกระทั่งเมื่อเร็ว ๆ นี้”: 2004 ไม่ใช่“ ล่าสุดมาก” โดยเฉพาะอย่างยิ่งเมื่อพิจารณาว่าเป็นเพียง C99 ที่ถูกพูดอย่างงุ่มง่ามในตอนแรก ในความเป็นจริงประเภทการจับคู่แม้ว่าสหภาพแรงงานจะถูกกฎหมายใน C89, ถูกกฎหมายใน C11 และถูกกฎหมายใน C99 ตลอดแม้ว่าจะใช้เวลาจนถึงปี 2004 สำหรับคณะกรรมการในการแก้ไขถ้อยคำที่ไม่ถูกต้องและการเปิดตัว TC3 ในภายหลัง open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
Pascal Cuoq

6
@ legends2k ภาษาโปรแกรมถูกกำหนดโดยมาตรฐาน Corrigendum ทางเทคนิค 3 ของมาตรฐาน C99 อนุญาตให้พิมพ์การสะกดคำในเชิงอรรถ 82 อย่างชัดเจนซึ่งฉันขอเชิญคุณอ่านด้วยตัวเอง นี่ไม่ใช่ทีวีที่สัมภาษณ์ดาราร็อคและแสดงความคิดเห็นต่อการเปลี่ยนแปลงสภาพภูมิอากาศ ความคิดเห็นของ Stroustrup นั้นไม่มีอิทธิพลกับสิ่งที่มาตรฐาน C กล่าว
Pascal Cuoq

6
@ legends2k " ฉันรู้ว่าความคิดเห็นของแต่ละคนไม่สำคัญและมีเพียงมาตรฐานเท่านั้น " ความคิดเห็นของนักเขียนคอมไพเลอร์มีความสำคัญมากกว่า "ข้อกำหนด" ภาษาที่แย่มาก
curiousguy

38

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

struct VAROBJECT
{
    enum o_t { Int, Double, String } objectType;

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;

ฉันเห็นด้วยโดยสิ้นเชิงโดยไม่เข้าสู่ความโกลาหลพฤติกรรมที่ไม่ได้กำหนดบางทีนี่อาจเป็นพฤติกรรมที่ดีที่สุดของสหภาพแรงงานที่ฉันสามารถนึกได้ แต่จะไม่เป็นการสิ้นเปลืองเมื่อใช้เพียงพูดintหรือchar*วัตถุ 10 รายการ []; ในกรณีใดฉันสามารถประกาศโครงสร้างที่แยกต่างหากสำหรับแต่ละชนิดข้อมูลแทน VAROBJECT มันจะไม่ลดความยุ่งเหยิงและใช้พื้นที่น้อยลงหรือไม่?
legends2k

3
ตำนาน: ในบางกรณีคุณทำอย่างนั้นไม่ได้ คุณใช้บางอย่างเช่น VAROBJECT ใน C ในกรณีเดียวกันเมื่อคุณใช้ Object ใน Java
Erich Kitzmueller

โครงสร้างข้อมูลของสหภาพที่ติดแท็กดูเหมือนจะเป็นการใช้สหภาพที่ถูกกฎหมายตามที่คุณอธิบาย
ตำนาน 2k

นอกจากนี้ยังให้ตัวอย่างของวิธีการใช้ค่า
Ciro Santilli 郝海东冠状病六四事件法轮功

1
@CiroSantilli 新疆改造中心六四事件法轮功ตัวอย่างของC ++ Primerอาจช่วยได้ wandbox.org/permlink/cFSrXyG02vOSdBk2
Rick

34

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

หากคุณใช้ C ++ (คุณใช้สองแท็ก) และคุณสนใจเรื่องการพกพาจริงๆคุณสามารถใช้ struct และเตรียม setter ที่ใช้uint32_tและตั้งค่าฟิลด์อย่างเหมาะสมผ่านการดำเนินการ bitmask เดียวกันสามารถทำได้ใน C กับฟังก์ชั่น

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


+1 สำหรับคำตอบที่ชัดเจนและเรียบง่าย! ฉันเห็นด้วยสำหรับความสะดวกในการพกพาวิธีการที่คุณให้ไว้ในพารา 2 นั้นดี แต่ฉันสามารถใช้วิธีที่ฉันตั้งคำถามถ้ารหัสของฉันถูกผูกลงกับสถาปัตยกรรมเดียว (จ่ายราคา protability) เนื่องจากมันช่วยประหยัด 4 ไบต์สำหรับแต่ละพิกเซลค่าและบางครั้งบันทึกในการใช้ฟังก์ชั่นนั้น ?
ตำนาน 2k

ปัญหา endian ไม่ได้บังคับให้มาตรฐานประกาศว่าเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ reinterpret_cast มีปัญหา endian เดียวกันทั้งหมด แต่มีพฤติกรรมการใช้งานที่กำหนดไว้
JoeG

1
@ legends2k ปัญหาคือเครื่องมือเพิ่มประสิทธิภาพอาจสันนิษฐานว่า uint32_t ไม่ได้ถูกแก้ไขโดยการเขียนไปยัง uint8_t และเพื่อให้คุณได้รับค่าที่ไม่ถูกต้องเมื่อการใช้ที่ปรับให้เหมาะสมซึ่งสันนิษฐานว่า ... @Joe พฤติกรรมที่ไม่ได้กำหนดจะปรากฏขึ้นทันทีที่คุณเข้าถึง ตัวชี้ (ฉันรู้ว่ามีข้อยกเว้นบางอย่าง)
AProgrammer

1
@ legends2k / AProgrammer: ผลลัพธ์ของ reinterpret_cast นั้นถูกกำหนดไว้ การใช้ตัวชี้ที่ส่งคืนจะไม่ส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนดเฉพาะในลักษณะการใช้งานที่กำหนดไว้เท่านั้น กล่าวอีกนัยหนึ่งพฤติกรรมจะต้องสอดคล้องและกำหนด แต่มันไม่สามารถพกพาได้
JoeG

1
@ legends2k: เครื่องมือเพิ่มประสิทธิภาพที่เหมาะสมจะรับรู้การดำเนินการระดับบิตที่เลือกทั้งไบต์และสร้างรหัสเพื่ออ่าน / เขียนไบต์เช่นเดียวกับสหภาพ แต่กำหนดไว้อย่างดี (และพกพา) เช่น uint8_t getRed () const {สีกลับ & 0x000000FF; } เป็นโมฆะ setRed (uint8_t r) {color = (color & ~ 0x000000FF) | R; }
Ben Voigt

22

ส่วนใหญ่ที่พบบ่อยการใช้งานของunionฉันเป็นประจำเจอเป็นaliasing

พิจารณาสิ่งต่อไปนี้:

union Vector3f
{
  struct{ float x,y,z ; } ;
  float elts[3];
}

สิ่งนี้ทำอะไร ช่วยให้Vector3f vec;สมาชิกของ 's สะอาดและเรียบร้อยด้วยชื่อใดชื่อหนึ่ง :

vec.x=vec.y=vec.z=1.f ;

หรือโดยการเข้าถึงจำนวนเต็มเข้าไปในอาร์เรย์

for( int i = 0 ; i < 3 ; i++ )
  vec.elts[i]=1.f;

ในบางกรณีการเข้าถึงโดยใช้ชื่อเป็นสิ่งที่ชัดเจนที่สุดที่คุณสามารถทำได้ ในกรณีอื่น ๆ โดยเฉพาะอย่างยิ่งเมื่อเลือกแกนโดยทางโปรแกรมสิ่งที่ง่ายกว่าคือการเข้าถึงแกนด้วยดัชนีตัวเลข - 0 สำหรับ x, 1 สำหรับ y และ 2 สำหรับ z


3
สิ่งนี้เรียกอีกอย่างหนึ่งtype-punningซึ่งถูกกล่าวถึงในคำถามด้วย นอกจากนี้ตัวอย่างในคำถามจะแสดงตัวอย่างที่คล้ายกัน
ตำนาน 2k

4
มันไม่ได้พิมพ์เวทย์มนต์ ในตัวอย่างประเภทของฉันตรงกันดังนั้นจึงไม่มี "pun" มันเป็นเพียงนามแฝง
bobobobo

3
ใช่ แต่ถึงกระนั้นจากมุมมองที่แน่นอนของมาตรฐานภาษาสมาชิกที่เขียนและอ่านจากที่แตกต่างกันซึ่งไม่ได้กำหนดตามที่กล่าวไว้ในคำถาม
ตำนาน 2k

3
ฉันหวังว่ามาตรฐานในอนาคตจะแก้ไขกรณีนี้ให้ได้รับอนุญาตภายใต้กฎ อย่างไรก็ตามอาร์เรย์จะไม่เข้าร่วมในกฎนั้นภายใต้ถ้อยคำปัจจุบัน
Ben Voigt

3
@curtguy: มีความต้องการอย่างชัดเจนว่าสมาชิกโครงสร้างจะถูกวางไว้โดยไม่ต้องรองช่องว่าง หากการทดสอบรหัสสำหรับตำแหน่งสมาชิกโครงสร้างหรือขนาดโครงสร้างรหัสควรทำงานถ้าเข้าถึงได้โดยตรงผ่านสหภาพ แต่การอ่านที่เข้มงวดของมาตรฐานจะบ่งชี้ว่าการที่อยู่ของสหภาพหรือสมาชิก struct ให้ผลตัวชี้ที่ไม่สามารถใช้ เป็นตัวชี้ประเภทของตัวเอง แต่ก่อนอื่นจะต้องแปลงกลับเป็นตัวชี้ไปเป็นประเภทล้อมรอบหรือประเภทตัวอักษร คอมไพเลอร์ใด ๆ จากระยะไกลที่สามารถทำงานได้จะขยายภาษาโดยการทำให้การทำงานสิ่งที่มากกว่า ...
SuperCat

10

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

union A {
   int i;
   double d;
};

A a[10];    // records in "a" can be either ints or doubles 
a[0].i = 42;
a[1].d = 1.23;

แน่นอนว่าคุณต้องเลือกใช้ discriminator เพื่อพูดว่าตัวแปรมีอะไรบ้าง และโปรดทราบว่าใน C ++ ยูเนี่ยนจะไม่ได้ใช้งานมากนักเพราะมันสามารถมีประเภท POD เท่านั้น - มีประสิทธิภาพโดยที่ไม่มีตัวสร้างและ destructors


คุณเคยใช้มัน (เช่นในคำถาม)? :)
ตำนาน 2

มันค่อนข้างเชื่องช้า แต่ฉันไม่ยอมรับ "บันทึกการเปลี่ยนแปลง" นั่นคือฉันแน่ใจว่าพวกเขาอยู่ในใจ แต่ถ้าพวกเขามีความสำคัญทำไมไม่ให้พวกเขา? "จัดเตรียมแบบเอกสารประกอบการก่อสร้างเนื่องจากอาจเป็นประโยชน์ในการสร้างสิ่งอื่น ๆ เช่นกัน" ดูเหมือนว่าจะมีความเป็นไปได้สูงขึ้น โดยเฉพาะอย่างยิ่งการประยุกต์ใช้อย่างน้อยหนึ่งที่อาจจะเป็นในใจ - หน่วยความจำที่แมปลงทะเบียน I / O ที่ input และ output ลงทะเบียน (ในขณะที่คาบเกี่ยวกัน) เป็นหน่วยงานที่แตกต่างกันที่มีชื่อของตัวเองชนิด ฯลฯ
Steve314

@ Stev314 ถ้านั่นคือการใช้งานที่พวกเขามีอยู่ในใจพวกเขาอาจทำให้มันไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนด

@Neil: +1 เป็นคนแรกที่พูดเกี่ยวกับการใช้งานจริงโดยไม่ต้องกดปุ่มพฤติกรรมที่ไม่ได้กำหนด ฉันเดาว่าพวกเขาสามารถทำให้การใช้งานถูกกำหนดเช่นเดียวกับการดำเนินการ punning ชนิดอื่น ๆ (reinterpret_cast เป็นต้น) แต่อย่างที่ฉันถามคุณเคยใช้มันเพื่อพิมพ์อักษรตัวจิ๋วหรือไม่?
legends2k

@Neil - ตัวอย่างการลงทะเบียนหน่วยความจำที่แมปไม่ได้ไม่ได้กำหนดไว้ endian / etc ปกติและให้ธง "ระเหย" การเขียนที่อยู่ในรุ่นนี้ไม่ได้อ้างอิงการลงทะเบียนเดียวกันกับการอ่านที่อยู่เดียวกัน ดังนั้นจึงไม่มีปัญหา "คุณกำลังอ่านอะไร" ในขณะที่คุณไม่ได้อ่าน - อะไรก็ตามที่คุณเขียนไปยังที่อยู่นั้นเมื่อคุณอ่านคุณกำลังอ่านอินพุตอิสระ ปัญหาเดียวคือทำให้แน่ใจว่าคุณอ่านด้านอินพุตของสหภาพและเขียนด้านเอาต์พุต เป็นเรื่องปกติในสิ่งที่ฝังตัว - อาจจะยังคงเป็น
Steve314

8

ใน C มันเป็นวิธีที่ดีในการใช้งานสิ่งที่แตกต่าง

enum possibleTypes{
  eInt,
  eDouble,
  eChar
}


struct Value{

    union Value {
      int iVal_;
      double dval;
      char cVal;
    } value_;
    possibleTypes discriminator_;
} 

switch(val.discriminator_)
{
  case eInt: val.value_.iVal_; break;

ในช่วงเวลาของหน่วยความจำ litlle โครงสร้างนี้ใช้หน่วยความจำน้อยกว่าโครงสร้างที่มีสมาชิกทั้งหมด

โดยวิธี C ให้

    typedef struct {
      unsigned int mantissa_low:32;      //mantissa
      unsigned int mantissa_high:20;
      unsigned int exponent:11;         //exponent
      unsigned int sign:1;
    } realVal;

เพื่อเข้าถึงค่าบิต


แม้ว่าตัวอย่างทั้งสองของคุณจะถูกกำหนดอย่างสมบูรณ์ในมาตรฐาน แต่เดี๋ยวก่อนการใช้เขตข้อมูลบิตแน่ใจว่ายิงรหัสที่ไม่สามารถแปลได้ใช่ไหม
legends2k

ไม่มันไม่ใช่ เท่าที่ฉันรู้มันสนับสนุนอย่างกว้างขวาง
Totonga

1
การสนับสนุนคอมไพเลอร์ไม่ได้แปลเป็นอุปกรณ์พกพา C Book : C (ดังนั้น C ++) ไม่รับประกันการเรียงลำดับของฟิลด์ภายในคำศัพท์ของเครื่องดังนั้นหากคุณใช้พวกมันด้วยเหตุผลหลังโปรแกรมของคุณจะไม่เพียงพกพาได้ แต่จะขึ้นอยู่กับคอมไพเลอร์ด้วย
legends2k

5

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


2
มีปัญหา endian หรือไม่? การแก้ไขที่ค่อนข้างง่ายเมื่อเทียบกับ "ไม่ได้กำหนด" แต่ควรคำนึงถึงสำหรับบางโครงการถ้าใช่
Steve314

5

ใน C ++, Boost Variantใช้ยูเนี่ยนเวอร์ชันปลอดภัยซึ่งออกแบบมาเพื่อป้องกันพฤติกรรมที่ไม่ได้กำหนดไว้ให้มากที่สุด

การแสดงของมันเหมือนกับenum + unionโครงสร้าง (สแต็กจัดสรรเกินไปเป็นต้น) แต่ใช้รายการเทมเพลตประเภทแทนenum:)


5

พฤติกรรมอาจไม่ได้กำหนด แต่นั่นก็หมายความว่าไม่มี "มาตรฐาน" คอมไพเลอร์ที่ดีทั้งหมดเสนอ#pragmasเพื่อควบคุมการบรรจุและการจัดตำแหน่ง แต่อาจมีค่าเริ่มต้นแตกต่างกัน ค่าเริ่มต้นจะเปลี่ยนตามการตั้งค่าการเพิ่มประสิทธิภาพที่ใช้ด้วย

นอกจากนี้สหภาพแรงงานไม่เพียง แต่ช่วยประหยัดพื้นที่ พวกเขาสามารถช่วยคอมไพเลอร์สมัยใหม่ด้วยประเภทการสะกดคำ หากคุณreinterpret_cast<>ทุกอย่างคอมไพเลอร์ไม่สามารถตั้งสมมติฐานเกี่ยวกับสิ่งที่คุณกำลังทำอยู่ อาจต้องทิ้งสิ่งที่รู้เกี่ยวกับประเภทของคุณและเริ่มต้นใหม่อีกครั้ง (บังคับให้เขียนกลับไปที่หน่วยความจำซึ่งไม่มีประสิทธิภาพมากในปัจจุบันนี้เมื่อเทียบกับความเร็วสัญญาณนาฬิกาของ CPU)


4

ในทางเทคนิคแล้วมันยังไม่ได้กำหนด แต่ในความเป็นจริงคอมไพเลอร์ส่วนใหญ่ (ทั้งหมด?) ปฏิบัติต่อมันเหมือนกับการใช้reinterpret_castจากประเภทหนึ่งไปยังอีกประเภทหนึ่ง ฉันจะไม่นอนทับรหัสปัจจุบันของคุณ


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

4

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


4

คนอื่นพูดถึงความแตกต่างของสถาปัตยกรรม (เล็ก - ใหญ่น้อย)

ฉันอ่านปัญหาที่ว่าเนื่องจากหน่วยความจำสำหรับตัวแปรถูกแชร์แล้วจากการเขียนไปยังสิ่งอื่นการเปลี่ยนแปลงอื่น ๆ และขึ้นอยู่กับประเภทของพวกเขาค่าอาจไม่มีความหมาย

เช่น. สหภาพ {ลอย f; int i; } x;

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

ฉันคิดว่ายังมีปัญหาของการจัดตำแหน่ง: หากตัวแปรบางอย่างต้องจัดเรียงคำแล้วคุณอาจไม่ได้รับผลลัพธ์ที่คาดหวัง

เช่น. สหภาพ {ถ่าน c [4]; int i; } x;

ถ้าสมมุติว่าในเครื่องบางเครื่องจะต้องจัดตำแหน่งคำแล้ว c [0] และ c [1] จะแบ่งปันที่เก็บข้อมูลกับฉัน แต่ไม่ใช่ c [2] และ c [3]


ไบต์ที่ต้องจัดเรียงคำ? มันไม่สมเหตุสมผลเลย ไบต์มีความต้องการการจัดตำแหน่งไม่มีโดยความหมาย
curiousguy

ใช่ฉันน่าจะใช้ตัวอย่างที่ดีกว่า ขอบคุณ
philcolbourn

@currguy: มีหลายกรณีที่หนึ่งอาจต้องการที่จะมีอาร์เรย์ของไบต์จะจัดตำแหน่งคำ หากมีหลายอาเรย์เช่น 1024 ไบต์และมักจะต้องการคัดลอกไปที่อื่นการจัดเรียงคำอาจใช้กับระบบหลาย ๆ ระบบที่เพิ่มความเร็วเป็นสองเท่าmemcpy()จากที่หนึ่งไปอีกที่หนึ่ง บางระบบอาจจัดเรียงการchar[]จัดสรรที่เกิดขึ้นนอกโครงสร้าง / สหภาพเพื่อเหตุผลนั้นและเหตุผลอื่น ๆ ในตัวอย่างยังหลงเหลืออยู่สมมติฐานที่ว่าiจะซ้อนทับกันทุกองค์ประกอบของการc[]ไม่เป็นแบบพกพา sizeof(int)==4แต่นั่นเป็นเพราะมีการรับประกันว่าไม่มี
supercat

4

ในภาษา C ตามที่บันทึกไว้ในปี 1974 สมาชิกโครงสร้างทั้งหมดได้แชร์เนมสเปซทั่วไปและความหมายของ "ptr-> member" ถูกกำหนดให้เป็นการเพิ่มการกระจัดของสมาชิกไปที่ "ptr" และเข้าถึงที่อยู่ผลลัพธ์โดยใช้ประเภทสมาชิก การออกแบบนี้ทำให้สามารถใช้ PTR เดียวกันกับชื่อสมาชิกที่นำมาจากนิยามโครงสร้างที่แตกต่างกัน แต่มีออฟเซ็ตเดียวกัน โปรแกรมเมอร์ใช้ความสามารถนั้นเพื่อวัตถุประสงค์ที่หลากหลาย

เมื่อสมาชิกโครงสร้างได้รับมอบหมายเนมสเปซของตัวเองมันเป็นไปไม่ได้ที่จะประกาศสมาชิกโครงสร้างสองคนที่มีการกระจัดเหมือนกัน การเพิ่มสหภาพลงในภาษาทำให้สามารถบรรลุความหมายเดียวกันกับที่มีอยู่ในรุ่นก่อนหน้าของภาษา (แม้ว่าการไม่สามารถมีชื่อที่ส่งออกไปยังบริบทที่ล้อมรอบอาจยังจำเป็นต้องใช้การค้นหา / แทนที่เพื่อแทนที่สมาชิก foo-> สมาชิก เป็น foo-> type1.member) สิ่งที่สำคัญไม่มากนักที่ผู้คนเพิ่มสหภาพมีเป้าหมายการใช้งานเฉพาะในใจ แต่พวกเขาให้วิธีการที่โปรแกรมเมอร์ที่พึ่งอาศัยความหมายก่อนหน้าไม่ว่าจะด้วยจุดประสงค์ใดก็ควรจะสามารถบรรลุเป้าหมาย ความหมายเดียวกันแม้ว่าพวกเขาจะต้องใช้ไวยากรณ์ที่แตกต่างเพื่อทำมัน


ชื่นชมบทเรียนประวัติศาสตร์อย่างไรก็ตามด้วยการกำหนดมาตรฐานเช่นและไม่ได้กำหนดซึ่งไม่ใช่กรณีในยุค C ที่หนังสือ K&R เป็น "มาตรฐาน" เพียงอย่างเดียวเราต้องแน่ใจว่าจะไม่ใช้มันเพื่อจุดประสงค์ใดและ เข้าสู่ดินแดนยูบี
ตำนาน 2k

2
@ legends2k: เมื่อมาตรฐานถูกเขียนการใช้งาน C ส่วนใหญ่ถือว่าสหภาพแรงงานในลักษณะเดียวกันและการรักษาดังกล่าวมีประโยชน์ อย่างไรก็ตามมีบางส่วนที่ไม่ได้ทำและผู้เขียนมาตรฐานไม่พอใจกับการใช้งานที่มีอยู่ว่าเป็น "การไม่สอดคล้อง" แต่พวกเขาพบว่าหากผู้ดำเนินการไม่ต้องการมาตรฐานเพื่อบอกให้พวกเขาทำอะไรบางอย่าง (ตามหลักฐานที่แสดงว่าพวกเขาทำไปแล้ว ) การปล่อยให้มันไม่ระบุหรือไม่ได้กำหนดจะรักษาสถานะเดิมไว้ ความคิดที่ว่ามันควรจะทำในสิ่งที่กำหนดไว้น้อยกว่าที่พวกเขาก่อนที่จะมีมาตรฐานถูกเขียน ...
SuperCat

2
... ดูเหมือนจะเป็นนวัตกรรมล่าสุด สิ่งที่น่าเศร้าอย่างยิ่งเกี่ยวกับสิ่งนี้คือถ้าผู้เขียนคอมไพเลอร์ที่กำหนดเป้าหมายแอปพลิเคชั่นระดับสูงต้องคิดหาวิธีเพิ่มแนวทางการเพิ่มประสิทธิภาพที่มีประโยชน์ให้กับภาษาที่คอมไพเลอร์ส่วนใหญ่นำมาใช้ในช่วงทศวรรษ 1990 "90% ของการติดตั้งใช้งานผลลัพธ์จะเป็นภาษาที่สามารถทำงานได้ดีขึ้นและน่าเชื่อถือกว่าไฮเปอร์ - ซีซียุคใหม่
supercat

2

คุณสามารถใช้ aa union ด้วยเหตุผลหลักสองข้อ:

  1. วิธีที่สะดวกในการเข้าถึงข้อมูลเดียวกันในรูปแบบต่างๆเช่นในตัวอย่างของคุณ
  2. วิธีการประหยัดพื้นที่เมื่อมีสมาชิกข้อมูลที่แตกต่างกันซึ่งมีเพียงคนเดียวเท่านั้นที่สามารถ 'ใช้งานได้'

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

ตัวอย่างที่ดีของ 2 สามารถพบได้ในประเภทตัวแปรที่ใช้อย่างกว้างขวางใน COM


2

ดังที่คนอื่น ๆ กล่าวถึงสหภาพที่รวมกับการแจกแจงและการรวมเข้ากับ structs สามารถใช้ในการติดตั้งสหภาพที่ติดแท็กได้ การใช้งานในทางปฏิบัติอย่างหนึ่งคือการติดตั้ง Rust Result<T, E>ซึ่ง แต่เดิมนั้นถูกใช้งานโดยใช้ pure enum(Rust สามารถเก็บข้อมูลเพิ่มเติมในตัวแปรการแจงนับ) นี่คือตัวอย่าง C ++:

template <typename T, typename E> struct Result {
    public:
    enum class Success : uint8_t { Ok, Err };
    Result(T val) {
        m_success = Success::Ok;
        m_value.ok = val;
    }
    Result(E val) {
        m_success = Success::Err;
        m_value.err = val;
    }
    inline bool operator==(const Result& other) {
        return other.m_success == this->m_success;
    }
    inline bool operator!=(const Result& other) {
        return other.m_success != this->m_success;
    }
    inline T expect(const char* errorMsg) {
        if (m_success == Success::Err) throw errorMsg;
        else return m_value.ok;
    }
    inline bool is_ok() {
        return m_success == Success::Ok;
    }
    inline bool is_err() {
        return m_success == Success::Err;
    }
    inline const T* ok() {
        if (is_ok()) return m_value.ok;
        else return nullptr;
    }
    inline const T* err() {
        if (is_err()) return m_value.err;
        else return nullptr;
    }

    // Other methods from https://doc.rust-lang.org/std/result/enum.Result.html

    private:
    Success m_success;
    union _val_t { T ok; E err; } m_value;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.