ใครจะใช้สหภาพเมื่อใด เป็นส่วนที่เหลือจากวัน C-only หรือไม่?


136

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


31
C / C ++ ไม่ใช่ภาษา สหภาพแรงงานมีประโยชน์ในระดับปานกลางใน C และส่วนใหญ่ไม่มีประโยชน์ใน C ++ มันจะถูกต้องถ้าจะบอกว่าใน C ++ พวกมันเป็น "ส่วนที่เหลือจาก C ++ ที่ขึ้นอยู่กับ C" แต่ไม่ได้หมายความว่าพวกมันเป็น "ส่วนที่เหลือจาก C เพียงวันเดียว" ราวกับว่า C ++ อยู่เหนือ C
R .. GitHub STOP HELPING ICE

13
คุณสามารถอธิบายรายละเอียดเกี่ยวกับสิ่งที่ c ++ แทนสำหรับสหภาพแรงงานได้หรือเหตุใดจึงไม่มีประโยชน์ใน c ++
Russel

3
การแทนที่ของ C ++ สำหรับยูเนี่ยนคือคลาสและการสืบทอด - ยูเนี่ยนใน C ถูกใช้โดยเฉพาะสำหรับความหลากหลายของประเภทที่ปลอดภัย ชั้นเรียนบางอย่างดีกว่ามาก (ดูคำตอบของ vz0 สำหรับความหลากหลายสไตล์ C)
tobyodavies

8
@R .. : union ยังคงมีประโยชน์ในระดับปานกลางใน C ++ ดูคำตอบด้านล่าง
Michael

3
ยูเนี่ยนอาจมีค่ามากเป็นพิเศษในความกล้าของระบบปฏิบัติการหรือในแพ็คเกจที่ประกอบ / แยกชิ้นส่วนไฟล์เสียง ในบริบทดังกล่าวมีการใช้หลายวิธีเช่นการแปลงข้อมูล / ปลายทางความหลากหลายระดับต่ำและอื่น ๆ ใช่มีวิธีแก้ปัญหาอื่น ๆ สำหรับปัญหาเดียวกัน (ส่วนใหญ่เป็นการส่งระหว่างประเภทตัวชี้) แต่สหภาพแรงงานมักจะสะอาดกว่าและจัดทำเอกสารด้วยตนเองได้ดีกว่า
Hot Licks

คำตอบ:


107

โดยปกติสหภาพแรงงานจะใช้กับ บริษัท ของผู้แบ่งแยก: ตัวแปรที่ระบุว่าเขตข้อมูลใดของสหภาพแรงงานถูกต้อง ตัวอย่างเช่นสมมติว่าคุณต้องการสร้างประเภทตัวแปรของคุณเอง:

struct my_variant_t {
    int type;
    union {
        char char_value;
        short short_value;
        int int_value;
        long long_value;
        float float_value;
        double double_value;
        void* ptr_value;
    };
};

จากนั้นคุณจะใช้มันเช่น:

/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
    v->type = VAR_FLOAT;
    v->float_value = initial_value;
}

/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
    switch (v->type) {
    case VAR_FLOAT:
        v->float_value += n;
        break;

    case VAR_INT:
        v->int_value += n;
        break;
    ...
    }
}

นี่เป็นสำนวนที่ค่อนข้างธรรมดาโดยเฉพาะใน Visual Basic internals

สำหรับตัวอย่างที่จริงเห็น SDL ของสหภาพ SDL_Event ( ซอร์สโค้ดจริงที่นี่ ) มีtypeฟิลด์ที่ด้านบนสุดของยูเนี่ยนและมีการทำซ้ำฟิลด์เดียวกันในทุก SDL_ * Event struct จากนั้นในการจัดการเหตุการณ์ที่ถูกต้องคุณต้องตรวจสอบค่าของtypeฟิลด์

ประโยชน์นั้นง่ายมาก: มีประเภทข้อมูลเดียวที่จัดการเหตุการณ์ทุกประเภทโดยไม่ต้องใช้หน่วยความจำที่ไม่จำเป็น


2
เยี่ยมมาก! ในกรณีนั้นตอนนี้ฉันสงสัยว่าทำไมฟังก์ชัน Sdl จึงไม่ได้ใช้เป็นลำดับชั้นของคลาสเท่านั้น นั่นคือการทำให้ C เข้ากันได้ไม่ใช่แค่ C ++?
Russel

12
ไม่สามารถใช้คลาส @Russel C ++ จากโปรแกรม C ได้ แต่โครงสร้าง C / สหภาพสามารถเข้าถึงได้ง่ายจาก C ++ โดยใช้บล็อก 'extern "C"
vz0

1
รูปแบบตัวแปรนี้มักใช้สำหรับการเขียนโปรแกรมล่ามภาษาเช่นคำจำกัดความของstruct objectในgithub.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield

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

@ Stargazer712, Google's codesearch: google.com/…
kagali-san

87

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

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

ใช้กรณีที่ 1: กิ้งก่า

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

struct Batman;
struct BaseballBat;

union Bat
{
    Batman brucewayne;
    BaseballBat club;
};

ReturnType1 f(void)
{
    BaseballBat bb = {/* */};
    Bat b;
    b.club = bb;
    // do something with b.club
}

ReturnType2 g(Bat& b)
{
    // do something with b, but how do we know what's inside?
}

Bat returnsBat(void);
ReturnType3 h(void)
{
    Bat b = returnsBat();
    // do something with b, but how do we know what's inside?
}

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

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

Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;

และนั่นคือกรณีการใช้งานที่เป็นที่นิยมมากที่สุด (un) ของสหภาพแรงงาน การใช้งานอีกกรณีหนึ่งคือเมื่ออินสแตนซ์แบบร่วมมาพร้อมกับสิ่งที่บอกประเภทของมัน

ใช้กรณีที่ 2: "ยินดีที่ได้รู้จักฉันobjectมาจากClass"

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

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

ใช้กรณีที่ 3:

ในฐานะผู้เขียนข้อเสนอแนะสำหรับมาตรฐาน ISO C ++ได้กล่าวไว้ในปี 2008

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

และตอนนี้ตัวอย่างที่มีแผนภาพคลาส UML:

องค์ประกอบมากมายสำหรับคลาส A

สถานการณ์ในภาษาอังกฤษธรรมดา: ออบเจ็กต์ของคลาส A สามารถมีอ็อบเจ็กต์ของคลาสใดก็ได้ในหมู่ B1, ... , Bn และอย่างน้อยหนึ่งประเภทในแต่ละประเภทโดยnเป็นจำนวนที่ค่อนข้างมากให้พูดอย่างน้อย 10

เราไม่ต้องการเพิ่มฟิลด์ (สมาชิกข้อมูล) ใน A เช่นนี้:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

เนื่องจากnอาจแตกต่างกันไป (เราอาจต้องการเพิ่มคลาส Bx ในการผสม) และเนื่องจากสิ่งนี้จะทำให้เกิดความยุ่งเหยิงกับตัวสร้างและเนื่องจากวัตถุ A จะใช้พื้นที่มาก

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

สิ่งที่สามารถทำได้คือ:

union Bee
{
    B1 b1;
    .
    .
    .
    Bn bn;
};

enum BeesTypes { TYPE_B1, ..., TYPE_BN };

class A
{
private:
    std::unordered_map<int, Bee> data; // C++11, otherwise use std::map

public:
    Bee get(int); // the implementation is obvious: get from the unordered map
};

จากนั้นในการรับเนื้อหาของอินสแตนซ์แบบร่วมจากdataคุณใช้a.get(TYPE_B2).b2และไลค์อินสแตนซ์aคลาสAอยู่ที่ไหน

ทั้งหมดนี้มีประสิทธิภาพมากขึ้นเนื่องจากสหภาพแรงงานไม่ถูก จำกัด ใน C ++ 11 ดูเอกสารที่ลิงก์ด้านบนหรือบทความนี้เพื่อดูรายละเอียด


สิ่งนี้มีประโยชน์มากและบทความชุดที่สองนั้นให้ข้อมูลมาก ขอบคุณ.
Andrew

38

ตัวอย่างหนึ่งคือในขอบเขตที่ฝังซึ่งแต่ละบิตของการลงทะเบียนอาจมีความหมายแตกต่างกัน ตัวอย่างเช่นการรวมกันของจำนวนเต็ม 8 บิตและโครงสร้างที่มีบิตฟิลด์ 1 บิตแยกกัน 8 รายการช่วยให้คุณเปลี่ยนหนึ่งบิตหรือไบต์ทั้งหมด


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

11
ฉันจะไม่เรียกสิ่งนั้นว่า "ไม่แนะนำ" ในพื้นที่ฝังตัวมักจะสะอาดกว่าและเกิดข้อผิดพลาดน้อยกว่าทางเลือกอื่น ๆ ซึ่งโดยปกติแล้วอาจเกี่ยวข้องกับการร่ายและvoid*s หรือมาสก์และการเปลี่ยนแปลงที่ชัดเจนจำนวนมาก
bta

ห่า? แคสต์ที่ชัดเจนมากมาย? ดูเหมือนว่าฉันงบง่ายๆเช่นและREG |= MASK REG &= ~MASKหากนั่นเป็นข้อผิดพลาดง่ายให้ใส่ใน#define SETBITS(reg, mask)และ#define CLRBITS(reg, mask). อย่าพึ่งคอมไพเลอร์เพื่อรับบิตตามลำดับที่แน่นอน ( stackoverflow.com/questions/1490092/… )
Michael

26

Herb Sutterเขียนไว้ในGOTW เมื่อประมาณหกปีที่แล้วโดยเน้นที่ :

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

และสำหรับตัวอย่างที่มีประโยชน์น้อยดูนาน แต่สรุปไม่ได้คำถามGCC เข้มงวด-aliasing และหล่อผ่านสหภาพแรงงาน


23

ตัวอย่างการใช้งานตัวอย่างหนึ่งที่ฉันคิดได้คือ:

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} some32bittype;

จากนั้นคุณสามารถเข้าถึงส่วนที่แยกจากกัน 8 บิตของบล็อกข้อมูล 32 บิตนั้น อย่างไรก็ตามเตรียมพร้อมที่จะถูกกัดด้วยความอดทน

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

นอกจากนี้ยังมีวิธีการที่ปลอดภัยต่อผู้ใช้:

uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;

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


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

15

การใช้งานบางอย่างสำหรับสหภาพแรงงาน:

  • จัดเตรียมอินเทอร์เฟซ endianness ทั่วไปให้กับโฮสต์ภายนอกที่ไม่รู้จัก
  • จัดการข้อมูลจุดลอยตัวสถาปัตยกรรม CPU ภายนอกเช่นรับVAX G_FLOATSจากลิงค์เครือข่ายและแปลงเป็นIEEE 754 แบบยาวสำหรับการประมวลผล
  • ให้การเข้าถึงประเภทระดับที่สูงกว่า
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

ด้วยการประกาศนี้มันง่ายมากที่จะแสดงค่า hex byte ของ a long doubleเปลี่ยนเครื่องหมายของเลขชี้กำลังตรวจสอบว่าเป็นค่า denormal หรือใช้เลขคณิตคู่แบบยาวสำหรับ CPU ที่ไม่รองรับเป็นต้น

  • การประหยัดพื้นที่จัดเก็บเมื่อฟิลด์ขึ้นอยู่กับค่าบางอย่าง:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
    
  • รวมไฟล์รวมสำหรับใช้กับคอมไพเลอร์ของคุณ คุณจะพบกับการใช้งานหลายสิบถึงหลายร้อยรายการunion:

    [wally@zenetfedora ~]$ cd /usr/include
    [wally@zenetfedora include]$ grep -w union *
    a.out.h:  union
    argp.h:   parsing options, getopt is called with the union of all the argp
    bfd.h:  union
    bfd.h:  union
    bfd.h:union internal_auxent;
    bfd.h:  (bfd *, struct bfd_symbol *, int, union internal_auxent *);
    bfd.h:  union {
    bfd.h:  /* The value of the symbol.  This really should be a union of a
    bfd.h:  union
    bfd.h:  union
    bfdlink.h:  /* A union of information depending upon the type.  */
    bfdlink.h:  union
    bfdlink.h:       this field.  This field is present in all of the union element
    bfdlink.h:       the union; this structure is a major space user in the
    bfdlink.h:  union
    bfdlink.h:  union
    curses.h:    union
    db_cxx.h:// 4201: nameless struct/union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:typedef union
    _G_config.h:typedef union
    gcrypt.h:  union
    gcrypt.h:    union
    gcrypt.h:    union
    gmp-i386.h:  union {
    ieee754.h:union ieee754_float
    ieee754.h:union ieee754_double
    ieee754.h:union ieee854_long_double
    ifaddrs.h:  union
    jpeglib.h:  union {
    ldap.h: union mod_vals_u {
    ncurses.h:    union
    newt.h:    union {
    obstack.h:  union
    pi-file.h:  union {
    resolv.h:   union {
    signal.h:extern int sigqueue (__pid_t __pid, int __sig, __const union sigval __val)
    stdlib.h:/* Lots of hair to allow traditional BSD use of `union wait'
    stdlib.h:  (__extension__ (((union { __typeof(status) __in; int __i; }) \
    stdlib.h:/* This is the type of the argument to `wait'.  The funky union
    stdlib.h:   causes redeclarations with either `int *' or `union wait *' to be
    stdlib.h:typedef union
    stdlib.h:    union wait *__uptr;
    stdlib.h:  } __WAIT_STATUS __attribute__ ((__transparent_union__));
    thread_db.h:  union
    thread_db.h:  union
    tiffio.h:   union {
    wchar.h:  union
    xf86drm.h:typedef union _drmVBlank {
    

6
ทสสส! การโหวตสองครั้งและไม่มีคำอธิบาย ที่น่าผิดหวังคือ
wallyk

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

4
ฉันเดาว่าคุณได้รับการโหวตลดลงสำหรับสหภาพ "ตอน" หรือ "การตั้งครรภ์" มันเป็นบิตที่ไม่สบาย
akaltar

4
ใช่ฉันเดาว่ามันเป็นวันที่มืดมน
wallyk

14

สหภาพแรงงานมีประโยชน์เมื่อจัดการกับข้อมูลระดับไบต์ (ระดับต่ำ)

หนึ่งในการใช้งานล่าสุดของฉันคือการสร้างแบบจำลองที่อยู่ IP ซึ่งมีลักษณะดังนี้:

// Composite structure for IP address storage
union
{
    // IPv4 @ 32-bit identifier
    // Padded 12-bytes for IPv6 compatibility
    union
    {
        struct
        {
            unsigned char _reserved[12];
            unsigned char _IpBytes[4];
        } _Raw;

        struct
        {
            unsigned char _reserved[12];
            unsigned char _o1;
            unsigned char _o2;
            unsigned char _o3;
            unsigned char _o4;    
        } _Octet;    
    } _IPv4;

    // IPv6 @ 128-bit identifier
    // Next generation internet addressing
    union
    {
        struct
        {
            unsigned char _IpBytes[16];
        } _Raw;

        struct
        {
            unsigned short _w1;
            unsigned short _w2;
            unsigned short _w3;
            unsigned short _w4;
            unsigned short _w5;
            unsigned short _w6;
            unsigned short _w7;
            unsigned short _w8;   
        } _Word;
    } _IPv6;
} _IP;

7
อย่างไรก็ตามโปรดทราบว่าการเข้าถึงเนื้อหาดิบแบบนั้นไม่ได้มาตรฐานและอาจไม่ได้ผลกับคอมไพเลอร์ทั้งหมด
เลขที่

3
นอกจากนี้เป็นเรื่องปกติมากที่จะเห็นสิ่งนี้ใช้ในลักษณะที่ไม่รับประกันการจัดตำแหน่งซึ่งเป็นพฤติกรรมที่ไม่ได้กำหนด
หมูปิ้ง

10

ตัวอย่างเมื่อฉันใช้สหภาพ:

class Vector
{
        union 
        {
            double _coord[3];
            struct 
            {
                double _x;
                double _y; 
                double _z;
            };

        };
...
}

สิ่งนี้ทำให้ฉันสามารถเข้าถึงข้อมูลของฉันเป็นอาร์เรย์หรือองค์ประกอบได้

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

   union {   // dimension from left to right   // union for the left to right dimension
        uint32_t            m_width;
        uint32_t            m_sizeX;
        uint32_t            m_columns;
    };

    union {   // dimension from top to bottom   // union for the top to bottom dimension
        uint32_t            m_height;
        uint32_t            m_sizeY;
        uint32_t            m_rows;
    };

12
โปรดทราบว่าแม้ว่าโซลูชันนี้จะทำงานบนแพลตฟอร์มที่สังเกตได้ส่วนใหญ่ แต่การตั้งค่าเป็น _x, _y, _z และการเข้าถึง _coord เป็นพฤติกรรมที่ไม่ได้กำหนดไว้ วัตถุประสงค์หลักของสหภาพแรงงานคือการรักษาพื้นที่ คุณต้องเข้าถึงองค์ประกอบสหภาพเดียวกันกับที่คุณตั้งไว้ก่อนหน้านี้ทุกประการ
Suspieux

1
นี่คือวิธีที่ฉันใช้ด้วยเช่นกันฉันใช้ std :: array forr coords และ static_asserts
Viktor Sehr

1
รหัสนี้ละเมิดกฎการใช้นามแฝงที่เข้มงวดและไม่ควรแนะนำ
Walter

มีวิธีใดบ้างที่จะปรับปรุงสหภาพเพื่อให้สามารถทำเช่นนี้ได้?
Andrew

8

สหภาพแรงงานจัดให้มีความหลากหลายในภาษาซี


18
ฉันคิดvoid*อย่างนั้นนะ ^^

2
@ user166390 Polymorphism ใช้อินเทอร์เฟซเดียวกันเพื่อจัดการหลายประเภท โมฆะ * ไม่มีส่วนต่อประสาน
Alice

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

7

การใช้ยูเนี่ยนที่ยอดเยี่ยมคือการจัดตำแหน่งหน่วยความจำซึ่งฉันพบในซอร์สโค้ด PCL (Point Cloud Library) โครงสร้างข้อมูลเดียวใน API สามารถกำหนดเป้าหมายสถาปัตยกรรมได้สองแบบ ได้แก่ CPU ที่รองรับ SSE เช่นเดียวกับ CPU ที่ไม่มีการสนับสนุน SSE เช่นโครงสร้างข้อมูลสำหรับ PointXYZ คือ

typedef union
{
  float data[4];
  struct
  {
    float x;
    float y;
    float z;
  };
} PointXYZ;

ลูกลอยทั้ง 3 ตัวบุด้วยลูกลอยเพิ่มเติมสำหรับการจัดตำแหน่ง SSE ดังนั้นสำหรับ

PointXYZ point;

ผู้ใช้สามารถเข้าถึง point.data [0] หรือ point.x (ขึ้นอยู่กับการสนับสนุน SSE) เพื่อเข้าถึง say, พิกัด x รายละเอียดการใช้งานที่ดีกว่าที่คล้ายกันเพิ่มเติมอยู่ในลิงค์ต่อไปนี้: เอกสาร PCL ประเภท PointT


7

unionคำหลักในขณะที่ยังคงใช้ใน C ++ 03 1 , ส่วนใหญ่จะเป็นเศษเล็กเศษน้อยวันซี ปัญหาที่ชัดเจนที่สุดคือใช้งานได้กับ POD 1เท่านั้น

อย่างไรก็ตามแนวคิดเรื่องสหภาพแรงงานยังคงมีอยู่และแน่นอนว่าไลบรารี Boost มีคลาสที่เหมือนสหภาพ:

boost::variant<std::string, Foo, Bar>

ซึ่งมีประโยชน์ส่วนใหญ่union(ถ้าไม่ใช่ทั้งหมด) และเพิ่ม:

  • ความสามารถในการใช้ประเภทที่ไม่ใช่ POD ได้อย่างถูกต้อง
  • ความปลอดภัยประเภทคงที่

ในทางปฏิบัติมันแสดงให้เห็นว่ามันเทียบเท่ากับการรวมกันของunion+ enumและเปรียบเทียบว่ามันเร็ว (ในขณะที่boost::anyเป็นขอบเขตdynamic_castมากกว่าเนื่องจากใช้ RTTI)

1 Unions ได้รับการอัปเกรดใน C ++ 11 ( สหภาพที่ไม่ จำกัด ) และตอนนี้สามารถบรรจุวัตถุที่มีตัวทำลายได้แม้ว่าผู้ใช้จะต้องเรียกใช้ตัวทำลายด้วยตนเอง (ในสมาชิกสหภาพที่ใช้งานอยู่ในปัจจุบัน) การใช้ตัวแปรยังง่ายกว่ามาก


สิ่งนี้ไม่เป็นความจริงอีกต่อไปใน c ++ เวอร์ชันล่าสุด ดูคำตอบของ jrsala เช่น
Andrew

@ แอนดรูว์: ฉันอัปเดตคำตอบเพื่อระบุว่า C ++ 11 พร้อมสหภาพแรงงานที่ไม่ จำกัด อนุญาตประเภทที่มีตัวทำลายเพื่อเก็บไว้ในสหภาพ ฉันยังคงยืนหยัดด้วยจุดยืนของฉันว่าคุณดีกว่ามากในการใช้สหภาพแรงงานที่ติดแท็กเช่นboost::variantพยายามใช้สหภาพแรงงานด้วยตนเอง มีพฤติกรรมที่ไม่ได้กำหนดมากเกินไปรอบ ๆ สหภาพแรงงานซึ่งโอกาสของคุณในการทำให้ถูกต้องนั้นเลวร้ายมาก
Matthieu M.

3

จากบทความ Wikipedia เกี่ยวกับสหภาพแรงงาน :

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

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


2

ในช่วงแรก ๆ ของ C (เช่นตามเอกสารในปี 1974) โครงสร้างทั้งหมดใช้เนมสเปซร่วมกันสำหรับสมาชิกของตน ชื่อสมาชิกแต่ละคนเกี่ยวข้องกับประเภทและออฟเซ็ต ถ้า "wd_woozle" เป็น "int" ที่ชดเชย 12 ได้รับแล้วชี้pประเภทโครงสร้างใด ๆจะเทียบเท่ากับp->wd_woozle *(int*)(((char*)p)+12)ภาษากำหนดให้สมาชิกทุกประเภทของโครงสร้างทั้งหมดต้องมีชื่อเฉพาะยกเว้นว่าอนุญาตให้นำชื่อสมาชิกมาใช้ซ้ำได้อย่างชัดเจนในกรณีที่ทุกโครงสร้างที่ใช้ถือว่าเป็นลำดับเริ่มต้นทั่วไป

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

struct float1 { float f0;};
struct byte4  { char b0,b1,b2,b3; }; /* Unsigned didn't exist yet */

รหัสสามารถประกาศโครงสร้างประเภท "float1" จากนั้นใช้ "สมาชิก" b0 ... b3 เพื่อเข้าถึงแต่ละไบต์ในนั้น เมื่อภาษาถูกเปลี่ยนเพื่อให้โครงสร้างแต่ละส่วนได้รับเนมสเปซแยกกันสำหรับสมาชิกโค้ดที่อาศัยความสามารถในการเข้าถึงสิ่งต่างๆหลายวิธีจะแตก ค่าของการแยกเนมสเปซสำหรับโครงสร้างประเภทต่างๆนั้นเพียงพอที่จะกำหนดให้มีการเปลี่ยนแปลงโค้ดดังกล่าวเพื่อให้รองรับได้ แต่ค่าของเทคนิคดังกล่าวเพียงพอที่จะแสดงให้เห็นถึงการขยายภาษาเพื่อสนับสนุนต่อไป

รหัสที่ได้รับการเขียนเพื่อใช้ประโยชน์จากความสามารถในการเข้าถึงที่จัดเก็บภายในstruct float1ราวกับว่ามันเป็นstruct byte4อาจจะทำให้การทำงานในภาษาใหม่โดยการเพิ่มประกาศ: union f1b4 { struct float1 ff; struct byte4 bb; };ประกาศเป็นวัตถุชนิดunion f1b4;มากกว่าstruct float1และเปลี่ยนเข้าถึงf0, b0, b1ฯลฯ . ด้วยff.f0, bb.b0, bb.b1ฯลฯ ในขณะที่มีวิธีที่ดีกว่ารหัสดังกล่าวจะได้รับการสนับสนุนที่unionวิธีการอย่างน้อยก็ค่อนข้างที่สามารถทำงานได้อย่างน้อยกับการตีความ C89 ยุคของกฎการขจัดรอยหยัก


1

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

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


1

การเพิ่มล่าสุดอย่างหนึ่งของความสำคัญของสหภาพที่ได้รับการยกระดับแล้วโดยกฎการใช้นามแฝงที่เข้มงวดซึ่งแนะนำในมาตรฐาน C เวอร์ชันล่าสุด

คุณสามารถใช้ unions do เพื่อพิมพ์การลงโทษโดยไม่ละเมิดมาตรฐาน C
โปรแกรมนี้มีพฤติกรรมที่ไม่ระบุรายละเอียด (เนื่องจากฉันได้สันนิษฐานว่าfloatและunsigned intมีความยาวเท่ากัน) แต่ไม่ใช่พฤติกรรมที่ไม่ได้กำหนด (ดูที่นี่ )

#include <stdio.h> 

union float_uint
{
    float f;
    unsigned int ui;
};

int main()
{
    float v = 241;
    union float_uint fui = {.f = v};

    //May trigger UNSPECIFIED BEHAVIOR but not UNDEFINED BEHAVIOR 
    printf("Your IEEE 754 float sir: %08x\n", fui.ui);

    //This is UNDEFINED BEHAVIOR as it violates the Strict Aliasing Rule
    unsigned int* pp = (unsigned int*) &v;

    printf("Your IEEE 754 float, again, sir: %08x\n", *pp);

    return 0;
}

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

1

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

struct Number{
enum NumType{int32, float, double, complex}; NumType num_t;
union{int ival; float fval; double dval; ComplexNumber cmplx_val}
}

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

struct NumberBase
{
virtual Add(NumberBase n);
...
}
struct NumberInt: Number
{
//implement methods assuming Number's union contains int
NumberBase Add(NumberBase n);
...
}
struct NumberDouble: Number
{
 //implement methods assuming Number's union contains double
 NumberBase Add(NumberBase n);
 ...
}
//e.t.c. for all number types/or use templates
struct Number: NumberBase{
 union{int ival; float fval; double dval; ComplexNumber cmplx_val;}
 NumberBase* num_t;
 Set(int a)
 {
 ival=a;
  //still kind of hack, hope it works because derived classes of   Number    dont add any fields
 num_t = static_cast<NumberInt>(this);
 }
}

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


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

0

จากhttp://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

การใช้สหภาพแรงงานมีน้อยและห่างไกลกัน ในคอมพิวเตอร์ส่วนใหญ่ขนาดของตัวชี้และ int มักจะเท่ากันเนื่องจากทั้งคู่มักจะพอดีกับรีจิสเตอร์ใน CPU ดังนั้นหากคุณต้องการใช้ตัวชี้ชี้ไปที่ int หรือวิธีอื่นอย่างรวดเร็วและสกปรกให้ประกาศการรวมกัน

union intptr {   int i;   int * p; }; 
union intptr x; x.i = 1000; 
/* puts 90 at location 1000 */ 
*(x.p)=90; 

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

struct head {   int id;   int response;   int size; }; struct msgstring50 {    struct head fixed;    char message[50]; } struct

struct msgstring80 {หัวโครงสร้างคงที่; ข้อความถ่าน [80]; }
struct msgint10 {โครงสร้างส่วนหัวคงที่; ข้อความ int [10]; } struct msgack {โครงสร้างส่วนหัวคงที่; int ตกลง; } ยูเนี่ยน messagetype {
struct msgstring50 m50; โครงสร้าง msgstring80m80; โครงสร้าง msgint10 i10; โครงสร้าง msgack ack; }

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


0

สหภาพแรงงานจัดเตรียมวิธีการจัดการข้อมูลประเภทต่างๆในพื้นที่จัดเก็บเดียวโดยไม่ต้องฝังข้อมูลที่เป็นอิสระของเครื่องใด ๆ ในโปรแกรมซึ่งคล้ายคลึงกับระเบียนที่แตกต่างกันในภาษาปาสคาล

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

union u_tag {
     int ival;
     float fval;
     char  *sval;
} u;

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

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