ทำไมเราถึงต้องการสหภาพยูเนี่ยน


คำตอบ:


252

สหภาพมักใช้เพื่อแปลงระหว่างการแทนเลขฐานสองของจำนวนเต็มและลอย:

union
{
  int i;
  float f;
} u;

// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);

แม้ว่านี่จะเป็นพฤติกรรมที่ไม่ได้กำหนดทางเทคนิคตามมาตรฐาน C (คุณควรอ่านเฉพาะฟิลด์ที่เขียนล่าสุด) แต่จะทำงานในลักษณะที่กำหนดไว้อย่างดีในคอมไพเลอร์แทบทุกตัว

สหภาพแรงงานบางครั้งก็ใช้ในการดำเนินการหลอก - polymorphism ใน C โดยให้โครงสร้างบางแท็กระบุประเภทของวัตถุที่มันมีอยู่แล้วสหภาพประเภทที่เป็นไปได้ด้วยกัน:

enum Type { INTS, FLOATS, DOUBLE };
struct S
{
  Type s_type;
  union
  {
    int s_ints[2];
    float s_floats[2];
    double s_double;
  };
};

void do_something(struct S *s)
{
  switch(s->s_type)
  {
    case INTS:  // do something with s->s_ints
      break;

    case FLOATS:  // do something with s->s_floats
      break;

    case DOUBLE:  // do something with s->s_double
      break;
  }
}

สิ่งนี้อนุญาตให้มีขนาดเท่ากับstruct S12 ไบต์แทนที่จะเป็น 28


ควรมี uy แทน uf
Amit Singh Tomar

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

3
@spin_eight: ไม่ใช่ "แปลง" จาก float เป็น int "การตีความการแทนค่าฐานสองของการลอยซ้ำอีกครั้งราวกับว่ามันเป็น int" ผลลัพธ์ไม่ได้เป็น 3: ideone.com/MKjwon ฉันไม่แน่ใจว่าทำไมอดัมพิมพ์ด้วยเลขฐานสิบหก
endolith

@ Adam Rosenfield ฉันไม่เข้าใจการแปลงจริงๆฉันไม่ได้รับจำนวนเต็มในผลลัพธ์: p
The Beast

2
ฉันรู้สึกว่าข้อจำกัดความรับผิดชอบเกี่ยวกับพฤติกรรมที่ไม่ได้กำหนดควรถูกลบ ในความเป็นจริงมันเป็นพฤติกรรมที่กำหนดไว้ ดูเชิงอรรถ 82 ของมาตรฐาน C99: หากสมาชิกที่ใช้ในการเข้าถึงเนื้อหาของวัตถุสหภาพไม่เหมือนสมาชิกล่าสุดที่ใช้ในการเก็บค่าในวัตถุส่วนที่เหมาะสมของการเป็นตัวแทนวัตถุของค่าถูกตีความใหม่เป็น การแสดงวัตถุในรูปแบบใหม่ตามที่อธิบายไว้ใน 6.2.6 (กระบวนการบางครั้งเรียกว่า "type punning") นี่อาจเป็นการแสดงกับดัก
Christian Gibbons

136

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

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

จากนั้นคุณสามารถเข้าถึง reg ดังนี้

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

หลักสูตร Endianness (สั่งไบต์) และโปรเซสเซอร์มีความสำคัญแน่นอน

คุณสมบัติที่มีประโยชน์อีกอย่างคือตัวปรับเปลี่ยนบิต:

typedef union
{
    struct {
        unsigned char b1:1;
        unsigned char b2:1;
        unsigned char b3:1;
        unsigned char b4:1;
        unsigned char reserved:4;
    } bits;
    unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;

ด้วยรหัสนี้คุณสามารถเข้าถึงบิตโดยตรงในที่อยู่การลงทะเบียน / หน่วยความจำ:

x = reg.bits.b2;

3
คำตอบของคุณที่นี่ร่วมกับคำตอบ @Adam Rosenfield ข้างต้นทำให้คู่เสริมที่สมบูรณ์แบบ: คุณแสดงให้เห็นถึงการใช้โครงสร้างภายในสหภาพและเขาแสดงให้เห็นถึงการใช้สหภาพภายใน struct ปรากฎว่าฉันต้องการทั้งสองอย่างทันที: struct ภายในกลุ่มภายใน structเพื่อใช้ความหลากหลายของข้อความที่ส่งผ่านข้อความใน C ระหว่างเธรดในระบบฝังตัวและฉันจะไม่ได้ตระหนักว่าถ้าฉันไม่เห็นคำตอบของคุณทั้งสองเข้าด้วยกัน .
Gabriel Staples

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

64

การเขียนโปรแกรมระบบระดับต่ำเป็นตัวอย่างที่สมเหตุสมผล

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

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

typedef union {
    unsigned char control_byte;
    struct {
        unsigned int nibble  : 4;
        unsigned int nmi     : 1;
        unsigned int enabled : 1;
        unsigned int fired   : 1;
        unsigned int control : 1;
    };
} ControlRegister;

3
นี่เป็นตัวอย่างที่ยอดเยี่ยม! นี่คือตัวอย่างของวิธีที่คุณสามารถใช้เทคนิคนี้ในซอฟต์แวร์ฝังตัว: edn.com/design/integrated-circuit-design/4394915//
rzetterberg

34

ฉันเคยเห็นมันในห้องสมุดสองแห่งเพื่อแทนที่การสืบทอดเชิงวัตถุ

เช่น

        Connection
     /       |       \
  Network   USB     VirtualConnection

หากคุณต้องการให้การเชื่อมต่อ "คลาส" เป็นแบบใดแบบหนึ่งข้างต้นคุณสามารถเขียนดังนี้:

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};

ตัวอย่างการใช้ใน libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#74


33

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

ในตัวอย่างต่อไปนี้:

union {
   int a;
   int b;
   int c;
} myUnion;

การรวมนี้จะใช้พื้นที่ของ int เดียวมากกว่า 3 ค่า int ที่แยกจากกัน หากผู้ใช้ตั้งค่าของaและจากนั้นตั้งค่าของbก็จะเขียนทับค่าของaเนื่องจากพวกเขาทั้งสองแบ่งปันตำแหน่งหน่วยความจำเดียวกัน


29

ประเพณีมากมาย เพียงแค่ทำgrep union /usr/include/*หรือในไดเรกทอรีที่คล้ายกัน กรณีส่วนใหญ่ที่unionถูกห่อในstructและสมาชิกหนึ่งคนของ struct บอกว่าองค์ประกอบใดในสหภาพที่จะเข้าถึง ตัวอย่างเช่นการชำระเงินman elfสำหรับการใช้งานในชีวิตจริง

นี่คือหลักการพื้นฐาน:

struct _mydata {
    int which_one;
    union _data {
            int a;
            float b;
            char c;
    } foo;
} bar;

switch (bar.which_one)
{
   case INTEGER  :  /* access bar.foo.a;*/ break;
   case FLOATING :  /* access bar.foo.b;*/ break;
   case CHARACTER:  /* access bar.foo.c;*/ break;
}

สิ่งที่ฉันกำลังมองหา! มีประโยชน์มากที่จะเปลี่ยนบางจุดไข่ปลาพารามิเตอร์ :)
นิโคลัส Voron

17

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

set a to b times 7.

ประกอบด้วยองค์ประกอบภาษาต่อไปนี้:

  • สัญลักษณ์ [SET]
  • ตัวแปร [เป็น]
  • สัญลักษณ์ [ไป]
  • ตัวแปร [b]
  • สัญลักษณ์ [ครั้ง]
  • คง [7]
  • สัญลักษณ์[.]

องค์ประกอบภาษาถูกกำหนดเป็น#defineค่า '' ดังนั้น:

#define ELEM_SYM_SET        0
#define ELEM_SYM_TO         1
#define ELEM_SYM_TIMES      2
#define ELEM_SYM_FULLSTOP   3
#define ELEM_VARIABLE     100
#define ELEM_CONSTANT     101

และโครงสร้างต่อไปนี้ถูกใช้เพื่อจัดเก็บแต่ละองค์ประกอบ:

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;

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

ในการสร้างองค์ประกอบ "set" คุณจะต้องใช้:

tElem e;
e.typ = ELEM_SYM_SET;

ในการสร้างองค์ประกอบ "ตัวแปร [b]" คุณจะต้องใช้:

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

ในการสร้างองค์ประกอบ "ค่าคงที่ [7]" คุณจะต้องใช้:

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

และคุณสามารถขยายเพื่อรวม Floats ( float flt) หรือ rationals ( struct ratnl {int num; int denom;}) และประเภทอื่น ๆ

ขั้นพื้นฐานคือการที่strและvalไม่ได้อยู่ติดกันในหน่วยความจำที่พวกเขาจริงทับซ้อนกันดังนั้นจึงเป็นวิธีการรับมุมมองที่แตกต่างกันในบล็อกเดียวกันของหน่วยความจำแสดงที่นี่ที่โครงสร้างเป็นไปตามที่ตั้งของหน่วยความจำ0x1010และจำนวนเต็มและตัวชี้มีทั้ง 4 ไบต์:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+

ถ้ามันอยู่ในโครงสร้างมันจะเป็นแบบนี้:

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+

make sure you free this laterความคิดเห็นควรถูกลบออกจากองค์ประกอบคงที่หรือไม่
เทรเวอร์

ใช่ @ เทรเวอร์แม้ว่าฉันจะไม่เชื่อว่าคุณเป็นคนแรกที่เห็นมันในช่วง 4 ปีที่ผ่านมา :-) แก้ไขแล้วและขอบคุณสำหรับสิ่งนั้น
paxdiablo

7

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

struct variant {
    int type;
    double number;
    char *string;
};

ในระบบ 32 บิตนี้จะส่งผลให้อย่างน้อย 96 บิตหรือ 12 variantไบต์ถูกใช้สำหรับอินสแตนซ์ของแต่ละ

การใช้สหภาพคุณสามารถลดขนาดลงเหลือ 64 บิตหรือ 8 ไบต์:

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};

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


5

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

สหภาพเป็นประเภทที่แตกต่างกันเล็กน้อยในภาษาอื่น ๆ - พวกเขาสามารถเก็บได้ครั้งละอย่างเดียว แต่สิ่งนั้นอาจจะเป็น int, float ฯลฯ ขึ้นอยู่กับวิธีที่คุณประกาศ

ตัวอย่างเช่น:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};

MyUnion จะมีเฉพาะเป็น int หรือลอยขึ้นอยู่กับที่คุณเพิ่งตั้ง ดังนั้นการทำสิ่งนี้:

MYUNION u;
u.MyInt = 10;

ตอนนี้คุณถือ int เท่ากับ 10;

u.MyFloat = 1.0;

ตอนนี้คุณลอยเท่ากับ 1.0 มันไม่ได้มีความตั้งใจ เห็นได้ชัดว่าตอนนี้ถ้าคุณลองทำ printf ("MyInt =% d", u.MyInt); แล้วคุณอาจจะได้รับข้อผิดพลาดแม้ว่าฉันจะไม่แน่ใจในพฤติกรรมที่เฉพาะเจาะจง

ขนาดของสหภาพถูกกำหนดโดยขนาดของสนามที่ใหญ่ที่สุดในกรณีนี้ลอย


1
sizeof(int) == sizeof(float)( == 32) โดยปกติ
Nick T

1
สำหรับเร็กคอร์ดการกำหนดให้กับ float จากนั้นการพิมพ์ int จะไม่ทำให้เกิดข้อผิดพลาดเนื่องจากทั้งคอมไพเลอร์และสภาพแวดล้อมรันไทม์ไม่ทราบว่าค่าใดที่ถูกต้อง แน่นอนว่า int ที่ได้รับการตีพิมพ์จะไม่มีความหมายสำหรับวัตถุประสงค์ส่วนใหญ่ มันจะเป็นเพียงการแสดงความทรงจำของลอยที่ตีความว่าเป็น int
Jerry B

4

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


4

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

typedef union
{
    UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
                               // the entire set of fields (including the payload)

    struct
    {
        UINT8 size;
        UINT8 cmd;
        UINT8 payload[PAYLOAD_SIZE];
        UINT8 crc;
    } fields;

}PACKET_T;

// This should be called every time a new byte of data is ready 
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);

void packet_builder(UINT8* buffer, UINT8 data)
{
    static UINT8 received_bytes = 0;

    // All range checking etc removed for brevity

    buffer[received_bytes] = data;
    received_bytes++;

    // Using the struc only way adds lots of logic that relates "byte 0" to size
    // "byte 1" to cmd, etc...
}

void packet_handler(PACKET_T* packet)
{
    // Process the fields in a readable manner
    if(packet->fields.size > TOO_BIG)
    {
        // handle error...
    }

    if(packet->fields.cmd == CMD_X)
    {
        // do stuff..
    }
}

แก้ไข ความคิดเห็นเกี่ยวกับ endianness และ struct padding นั้นถูกต้องและน่ากังวลมาก ฉันใช้รหัสนี้เกือบทั้งหมดในซอฟต์แวร์ฝังตัวซึ่งส่วนใหญ่ฉันมีการควบคุมปลายทั้งสองของไปป์


1
รหัสนี้จะใช้งานไม่ได้ (ส่วนใหญ่) หากมีการแลกเปลี่ยนข้อมูลระหว่าง 2 แพลตฟอร์มที่แตกต่างกันเนื่องจากเหตุผลดังต่อไปนี้: 1) Endianness อาจแตกต่างกัน 2) แพ็ดดิ้งในโครงสร้าง
วงมโหรี

@Ravi ฉันเห็นด้วยกับข้อกังวลเกี่ยวกับ endianness และ padding อย่างไรก็ตามควรทราบว่าฉันได้ใช้สิ่งนี้เฉพาะในโครงการฝังตัว ส่วนใหญ่ฉันควบคุมปลายทั้งสองของท่อ
Adam Lewis

1

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

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

กิจกรรมสหภาพ
{
  eventCode แบบยาวที่ไม่ได้ลงชื่อ
  char eventParts ที่ไม่ได้ลงชื่อ [4];
};

1

สิ่งที่เกี่ยวกับVARIANTที่ใช้ในอินเตอร์เฟส COM? มันมีสองช่อง - "ประเภท" และสหภาพถือค่าจริงที่ถือว่าขึ้นอยู่กับฟิลด์ "ประเภท"


1

ที่โรงเรียนฉันใช้สหภาพอย่างนี้

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

ฉันใช้มันเพื่อจัดการกับสีได้ง่ายขึ้นแทนที่จะใช้ >> และตัวดำเนินการ << ฉันต้องผ่านดัชนีที่แตกต่างกันของอาร์เรย์ char ของฉัน


1

ฉันใช้ยูเนี่ยนเมื่อฉันถูกเข้ารหัสสำหรับอุปกรณ์ฝังตัว ฉันมี C int ที่มีความยาว 16 บิต และฉันต้องการเรียก 8 บิตที่สูงกว่าและ 8 บิตที่ต่ำกว่าเมื่อฉันต้องอ่านจาก / store ไปยัง EEPROM ดังนั้นฉันจึงใช้วิธีนี้:

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};

ไม่จำเป็นต้องเลื่อนเพื่อให้โค้ดอ่านง่ายขึ้น

ในทางกลับกันฉันเห็นโค้ดเก่า ๆ ของ C ++ stl ที่ใช้ union สำหรับ stl allocator หากคุณสนใจคุณสามารถอ่านซอร์สโค้ดsgi stl นี่คือส่วนหนึ่งของมัน:

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};

1
คุณไม่ต้องการจัดกลุ่มstructรอบhigher/ ของคุณlowerหรือ ตอนนี้ทั้งสองควรชี้ไปที่ไบต์แรกเท่านั้น
มาริโอ

@Mario อาใช่ฉันแค่เขียนมันด้วยมือแล้วลืมมันไปขอบคุณ
Mu Qiao

1
  • ไฟล์ที่มีประเภทบันทึกที่แตกต่างกัน
  • อินเตอร์เฟสเครือข่ายที่มีประเภทคำขอที่แตกต่างกัน

ดูที่นี่: การจัดการคำสั่งบัฟเฟอร์ X.25

หนึ่งในคำสั่ง X.25 ที่เป็นไปได้จำนวนมากได้รับเข้าบัฟเฟอร์และจัดการโดยใช้ UNION ของโครงสร้างที่เป็นไปได้ทั้งหมด


คุณช่วยอธิบายตัวอย่างทั้งสองนี้ได้ไหมฉันหมายถึงสิ่งเหล่านี้เกี่ยวข้องกับสหภาพอย่างไร
Amit Singh Tomar

1

ในรุ่นแรก ๆ ของ C การประกาศโครงสร้างทั้งหมดจะแชร์ชุดของฟิลด์ทั่วไป ได้รับ:

struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};

คอมไพเลอร์จะสร้างตารางขนาดของโครงสร้าง (และอาจจัดตำแหน่ง) และแยกชื่อสมาชิกโครงสร้างและออฟเซ็ตของโครงสร้าง คอมไพเลอร์ไม่ได้ติดตามว่าสมาชิกใดเป็นของโครงสร้างใดและจะอนุญาตให้โครงสร้างสองรายการมีสมาชิกที่มีชื่อเดียวกันเฉพาะเมื่อประเภทและออฟเซ็ตจับคู่ (เช่นเดียวกับสมาชิกqของstruct xและstruct y ) ถ้า p เป็นตัวชี้ไปยังประเภทโครงสร้างใด ๆ p-> q จะเพิ่มออฟเซ็ตของ "q" ไปยังตัวชี้ p แล้วดึง "int" จากที่อยู่ที่ได้

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

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


คุณสามารถให้ตัวอย่างนี้: `มันเป็นไปได้ที่จะเขียนฟังก์ชั่นที่สามารถดำเนินการบางอย่างที่มีประโยชน์ในโครงสร้างหลายชนิดสลับกันได้ ' จะใช้หลายโครงสร้างสมาชิกด้วยชื่อเดียวกันได้อย่างไร หากโครงสร้างสองโครงสร้างมีการจัดเรียงข้อมูลเหมือนกันและทำให้สมาชิกที่มีชื่อเดียวกันและออฟเซ็ตเดียวกันเหมือนกับในตัวอย่างของคุณจากนั้นโครงสร้างใดที่ฉันจะให้ข้อมูลจริง (คุ้มค่า) สองโครงสร้างมีการจัดตำแหน่งเดียวกันและสมาชิกเดียวกัน แต่มีค่าต่างกัน คุณช่วยอธิบายได้ไหม
Herdsman

@Herdsman: ในรุ่นแรก ๆ ของ C ชื่อสมาชิก struct ห่อหุ้มประเภทและออฟเซ็ต สมาชิกสองคนของโครงสร้างที่แตกต่างกันอาจมีชื่อเดียวกันหากและตรงกับประเภทและออฟเซ็ตของพวกเขา ถ้าสมาชิก struct fooคือการintชดเชย 8 anyPointer->foo = 1234;หมายถึง "นำที่อยู่ใน anyPointer, แทนที่ด้วย 8 ไบต์และดำเนินการเก็บจำนวนเต็มของค่า 1234 ไปยังที่อยู่ที่ได้รับผลรวบรวมคอมไพเลอร์ไม่จำเป็นต้องรู้หรือสนใจว่าanyPointerระบุ โครงสร้างชนิดใดก็ได้ที่fooระบุไว้ในหมู่สมาชิก
supercat

ด้วยตัวชี้คุณสามารถอ้างถึงที่อยู่ใด ๆ โดยไม่คำนึงถึง 'ต้นกำเนิด' ของตัวชี้ที่เป็นจริง แต่แล้วจุดของคอมไพเลอร์ในการเก็บตารางโครงสร้างสมาชิกและชื่อของพวกเขาคืออะไร (ถ้าคุณสามารถดึงข้อมูลได้) ด้วยตัวชี้ใด ๆ เพียงรู้ที่อยู่ของสมาชิกในโครงสร้างเฉพาะ และถ้าคอมไพเลอร์ไม่ทราบว่าanyPointerindentify กับสมาชิก struct แล้วคอมไพเลอร์จะตรวจสอบเงื่อนไขเหล่านี้to have a member with the same name only if the type and offset matchedของโพสต์ของคุณอย่างไร
Herdsman

@Herdsman: คอมไพเลอร์จะเก็บรายชื่อสมาชิกโครงสร้างเพราะพฤติกรรมที่แม่นยำของจะขึ้นอยู่กับชนิดและชดเชยp->foo fooเป็นหลักได้รับการจดชวเลขp->foo *(typeOfFoo*)((unsigned char*)p + offsetOfFoo)สำหรับคำถามหลังของคุณเมื่อคอมไพเลอร์พบคำนิยามสมาชิก struct นั้นต้องการสมาชิกที่ไม่มีชื่อนั้นอยู่หรือสมาชิกที่มีชื่อนั้นมีประเภทและออฟเซตเดียวกัน ฉันเดาว่าจะมีการดักฟังหากคำจำกัดความของสมาชิก struct ไม่ตรงกัน แต่ฉันไม่รู้ว่ามันจัดการข้อผิดพลาดได้อย่างไร
supercat

0

ตัวอย่างที่ง่ายและมีประโยชน์มากก็คือ ....

Imagine:

คุณมีuint32_t array[2]และต้องการเข้าถึงไบต์ที่ 3 และ 4 ของเครือข่ายไบต์ *((uint16_t*) &array[1])คุณสามารถทำ แต่สิ่งนี้น่าเศร้าที่ทำลายกฎนามแฝงที่เข้มงวด!

แต่คอมไพเลอร์ที่รู้จักจะอนุญาตให้คุณทำสิ่งต่อไปนี้:

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}

ในทางเทคนิคนี่ยังคงเป็นการละเมิดกฎ แต่มาตรฐานที่รู้จักทั้งหมดรองรับการใช้งานนี้

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