Kapan ada orang yang menggunakan serikat pekerja? Apakah ini sisa dari hari C-only?

133

Saya telah belajar tetapi tidak benar-benar mendapatkan serikat pekerja. Setiap teks C atau C ++ yang saya lalui memperkenalkan mereka (kadang-kadang secara sepintas), tetapi mereka cenderung memberikan sedikit contoh praktis mengapa atau di mana menggunakannya. Kapan serikat akan berguna dalam kasus modern (atau bahkan warisan)? Hanya dua tebakan saya yang akan memprogram mikroprosesor ketika Anda memiliki ruang yang sangat terbatas untuk bekerja, atau ketika Anda sedang mengembangkan API (atau yang serupa) dan Anda ingin memaksa pengguna akhir untuk hanya memiliki satu instance dari beberapa objek / tipe di satu kali. Apakah kedua tebakan ini mendekati benar?

Russel
sumber
31
C / C ++ bukan bahasa. Serikat pekerja cukup berguna di C dan sebagian besar tidak berguna di C ++. Akan benar untuk mengatakan bahwa dalam C ++ mereka adalah "sisa dari C ++ yang didasarkan pada C", tetapi tidak untuk mengatakan mereka "sisa dari C hanya beberapa hari" seolah-olah C ++ mengungguli C.
R .. GitHub STOP BANTUAN ICE
12
Bisakah Anda menguraikan apa pengganti c ++ untuk serikat pekerja, atau mengapa mereka tidak berguna di c ++?
Russel
3
Pengganti C ++ untuk serikat adalah kelas & warisan - serikat pekerja di C hampir secara eksklusif digunakan untuk polimorfisme tipe-aman. Kelas sesuatu jauh lebih baik. (Lihat jawaban vz0 untuk polimorfisme gaya-C)
tobyodavies
6
@ R ..: union masih cukup berguna di C ++. Lihat jawaban di bawah ini.
Michael
2
Serikat pekerja dapat sangat berharga dalam nyali sistem operasi, atau dalam, misalnya, paket yang merakit / membongkar file suara. Dalam konteks seperti itu mereka digunakan berbagai cara - konversi data / endian, polimorfisme tingkat rendah, dkk. Ya, ada solusi lain untuk masalah yang sama (terutama casting di antara tipe pointer), tetapi serikat pekerja seringkali lebih bersih dan lebih baik dalam mendokumentasikan diri.
Hot Licks

Jawaban:

105

Serikat pekerja biasanya digunakan dengan perusahaan diskriminator: variabel yang menunjukkan bidang mana dari serikat pekerja yang valid. Misalnya, katakanlah Anda ingin membuat jenis Varian Anda sendiri :

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;
    };
};

Maka Anda akan menggunakannya seperti:

/* 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;
    ...
    }
}

Ini sebenarnya idiom yang cukup umum, khususnya pada Visual Basic internal.

Untuk contoh nyata, lihat penyatuan SDL_Event SDL . ( kode sumber aktual di sini ). Ada typebidang di bagian atas serikat, dan bidang yang sama diulang pada setiap SDL_ * Acara struktur. Kemudian, untuk menangani acara yang benar, Anda perlu memeriksa nilai typebidang.

Manfaatnya sederhana: ada satu tipe data tunggal untuk menangani semua jenis acara tanpa menggunakan memori yang tidak perlu.

vz0
sumber
2
Bagus! Dalam hal ini, saya sekarang bertanya-tanya mengapa fungsi Sdl tidak hanya diimplementasikan sebagai hierarki kelas. Apakah itu untuk membuatnya kompatibel dengan C dan bukan hanya C ++?
Russel
12
@Russel C ++ kelas tidak dapat digunakan dari program C, tetapi C struct / serikat dapat dengan mudah diakses dari C ++ menggunakan blok 'extern "C"'.
vz0
1
Varian pola ini juga sering digunakan untuk penerjemah bahasa pemrograman, misalnya definisi struct objectdalam github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield
1
Penjelasan luar biasa. Saya selalu tahu apa serikat pekerja, tetapi tidak pernah melihat alasan dunia nyata mengapa ada orang yang cukup gila untuk menggunakannya :) Terima kasih atas contohnya.
riwalk
@ Stargazer712, pencarian kode Google: google.com/...
kagali-san
87

Saya menemukan serikat C ++ cukup keren. Tampaknya orang biasanya hanya memikirkan use case di mana seseorang ingin mengubah nilai instance serikat "di tempat" (yang, tampaknya, berfungsi hanya untuk menghemat memori atau melakukan konversi yang meragukan).

Faktanya, serikat pekerja dapat memiliki kekuatan besar sebagai alat rekayasa perangkat lunak, bahkan ketika Anda tidak pernah mengubah nilai instance serikat apa pun .

Gunakan case 1: bunglon

Dengan serikat pekerja, Anda dapat mengelompokkan kembali sejumlah kelas sewenang-wenang di bawah satu denominasi, yang bukan tanpa kesamaan dengan kasus kelas dasar dan kelas turunannya. Namun, perubahan apa yang bisa dan tidak bisa Anda lakukan dengan contoh serikat pekerja tertentu:

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?
}

Tampaknya programmer harus yakin dengan jenis konten instance serikat tertentu ketika dia ingin menggunakannya. Ini adalah kasus dalam fungsi di fatas. Namun, jika suatu fungsi menerima instance serikat sebagai argumen yang disahkan, seperti halnya dengan di gatas, maka tidak akan tahu apa yang harus dilakukan dengan itu. Hal yang sama berlaku untuk fungsi mengembalikan instance serikat, lihat h: bagaimana penelepon tahu apa yang ada di dalamnya?

Jika instance serikat tidak pernah dianggap sebagai argumen atau sebagai nilai balik, maka itu pasti memiliki kehidupan yang sangat monoton, dengan lonjakan kegembiraan ketika programmer memilih untuk mengubah kontennya:

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

Dan itulah kasus penggunaan serikat yang paling populer. Kasus penggunaan lain adalah ketika contoh serikat datang dengan sesuatu yang memberitahu Anda jenisnya.

Gunakan case 2: "Senang bertemu Anda, saya object, dari Class"

Misalkan seorang programmer terpilih untuk selalu memasangkan instance union dengan deskriptor tipe (saya akan menyerahkannya pada kebijaksanaan pembaca untuk membayangkan implementasi untuk satu objek seperti itu). Ini mengalahkan tujuan serikat itu sendiri jika apa yang diinginkan programmer adalah untuk menghemat memori dan bahwa ukuran deskriptor jenis tidak dapat diabaikan sehubungan dengan serikat. Tetapi mari kita anggap bahwa sangat penting bahwa contoh serikat pekerja dapat disahkan sebagai argumen atau sebagai nilai pengembalian dengan callee atau penelepon yang tidak mengetahui apa yang ada di dalamnya.

Kemudian programmer harus menulis switchpernyataan aliran kontrol untuk memberi tahu Bruce Wayne terpisah dari tongkat kayu, atau sesuatu yang setara. Ini tidak terlalu buruk ketika hanya ada dua jenis konten di serikat tetapi jelas, serikat tidak skala lagi.

Gunakan case 3:

Sebagai penulis rekomendasi untuk Standar ISO C ++ mengembalikannya pada tahun 2008,

Banyak domain masalah penting memerlukan sejumlah besar objek atau sumber daya memori terbatas. Dalam situasi ini, menghemat ruang sangat penting, dan penyatuan sering kali merupakan cara yang sempurna untuk melakukan itu. Faktanya, kasus penggunaan umum adalah situasi di mana serikat pekerja tidak pernah mengubah anggota aktifnya selama masa hidupnya. Itu dapat dibangun, disalin, dan dihancurkan seolah-olah itu adalah struct yang hanya mengandung satu anggota. Aplikasi khas ini adalah untuk membuat koleksi heterogen dari jenis yang tidak terkait yang tidak dialokasikan secara dinamis (mungkin mereka di tempat dibangun di peta, atau anggota array).

Dan sekarang, sebuah contoh, dengan diagram kelas UML:

banyak komposisi untuk kelas A

Situasi dalam bahasa Inggris biasa: objek kelas A dapat memiliki objek kelas apa pun di antara B1, ..., Bn, dan paling banyak satu dari setiap tipe, dengan n menjadi angka yang cukup besar, katakan setidaknya 10.

Kami tidak ingin menambahkan bidang (anggota data) ke A seperti:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

karena n mungkin bervariasi (kami mungkin ingin menambahkan kelas Bx ke dalam campuran), dan karena ini akan menyebabkan kekacauan dengan konstruktor dan karena objek A akan memakan banyak ruang.

Kita bisa menggunakan wadah aneh dari void*pointer ke Bxobjek dengan gips untuk mengambilnya, tapi itu gaya C dan jelek ... tapi yang lebih penting itu akan meninggalkan kita dengan masa hidup dari banyak objek yang dialokasikan secara dinamis untuk dikelola.

Sebaliknya, yang bisa dilakukan adalah ini:

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
};

Kemudian, untuk mendapatkan konten instance serikat data, Anda menggunakan a.get(TYPE_B2).b2dan suka, di mana instance akelas A.

Ini semakin kuat karena serikat pekerja tidak dibatasi dalam C ++ 11. Lihat dokumen yang ditautkan ke atas atau artikel ini untuk detailnya.

jrsala
sumber
Ini sangat membantu, dan seri artikel kedua itu sangat informatif. Terima kasih.
Andrew
38

Salah satu contohnya adalah di bidang tertanam, di mana setiap bit register dapat berarti sesuatu yang berbeda. Sebagai contoh, penyatuan integer 8-bit dan struktur dengan 8 bitfield 1-bit yang terpisah memungkinkan Anda untuk mengubah satu bit atau seluruh byte.

Kevin
sumber
7
Ini sangat umum pada driver perangkat juga. Beberapa tahun yang lalu saya menulis banyak kode menggunakan serikat pekerja seperti ini untuk sebuah proyek. Ini biasanya tidak disarankan, dan dapat dikompilasi khusus dalam beberapa kasus, tetapi berfungsi.
thkala
11
Saya tidak akan menyebutnya "tidak direkomendasikan". Di ruang tertanam sering jauh lebih bersih dan lebih rentan kesalahan daripada alternatif, yang biasanya melibatkan banyak gips dan void*s eksplisit atau topeng dan pergeseran.
bta
heh? Banyak gips eksplisit? Menurut saya pernyataan sederhana seperti REG |= MASKdan REG &= ~MASK. Jika itu rawan kesalahan maka letakkan di a #define SETBITS(reg, mask)dan #define CLRBITS(reg, mask). Jangan mengandalkan kompiler untuk mendapatkan bit dalam urutan tertentu ( stackoverflow.com/questions/1490092/… )
Michael
26

Herb Sutter menulis di GOTW sekitar enam tahun lalu, dengan penekanan ditambahkan:

"Tapi jangan berpikir bahwa serikat hanya peninggalan dari masa sebelumnya. Serikat mungkin paling berguna untuk menghemat ruang dengan memungkinkan data tumpang tindih, dan ini masih diinginkan di C ++ dan di dunia modern saat ini. Misalnya, beberapa yang paling lanjutan C ++implementasi perpustakaan standar di dunia sekarang hanya menggunakan teknik ini untuk menerapkan "optimasi string kecil," sebuah alternatif optimisasi hebat yang menggunakan kembali penyimpanan di dalam objek string itu sendiri: untuk string besar, ruang di dalam objek string menyimpan pointer biasa ke dinamis buffer yang dialokasikan dan informasi rumah tangga seperti ukuran buffer; untuk string kecil, ruang yang sama digunakan kembali untuk menyimpan konten string secara langsung dan sepenuhnya menghindari alokasi memori dinamis. Untuk informasi lebih lanjut tentang optimasi string kecil (dan optimisasi string dan pesimisasi lainnya secara mendalam), lihat ... "

Dan untuk contoh yang kurang bermanfaat, lihat pertanyaan panjang tapi tidak meyakinkan gcc, aliasing, dan casting melalui serikat pekerja .

Joseph Quinsey
sumber
23

Nah, satu contoh use case yang bisa saya pikirkan adalah ini:

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

Anda kemudian dapat mengakses bagian terpisah 8-bit dari blok data 32-bit itu; namun, bersiaplah untuk berpotensi digigit oleh endianness.

Ini hanyalah satu contoh hipotetis, tetapi setiap kali Anda ingin membagi data dalam bidang menjadi bagian-bagian komponen seperti ini, Anda bisa menggunakan gabungan.

Yang mengatakan, ada juga metode yang aman-endian:

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

Sebagai contoh, karena operasi biner itu akan dikonversi oleh kompiler ke endianness yang benar.


sumber
Saya pikir pertanyaannya adalah yang terbaik diambil ketika seseorang harus menggunakan serikat pekerja. Anda memberikan jawaban tentang di mana serikat pekerja bukanlah alat yang tepat, yang menurut saya harus lebih jelas dalam jawaban ini.
Michael
15

Beberapa kegunaan untuk serikat pekerja:

  • Menyediakan antarmuka endianness umum ke host eksternal yang tidak dikenal.
  • Memanipulasi data arsitektur asing floating point, seperti menerima VAX G_FLOATS dari tautan jaringan dan mengonversinya menjadi IEEE 754 real lama untuk diproses.
  • Berikan akses memutar-mutar sedikit langsung ke tipe tingkat yang lebih tinggi.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Dengan deklarasi ini, mudah untuk menampilkan nilai hex byte dari long double, mengubah tanda eksponen, menentukan apakah itu nilai yang tidak normal, atau menerapkan aritmatika ganda panjang untuk CPU yang tidak mendukungnya, dll.

  • Menghemat ruang penyimpanan saat bidang bergantung pada nilai-nilai tertentu:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
  • Grep file include untuk digunakan dengan kompiler Anda. Anda akan menemukan puluhan hingga ratusan penggunaan 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 {
wallyk
sumber
5
Ck tsk! Dua downvotes dan tidak ada penjelasan. Itu mengecewakan.
wallyk
Contoh dengan seseorang yang bisa memegang pria dan wanita adalah desain yang sangat buruk di mata saya. Mengapa seseorang bukan kelas dasar dan seorang pria dan wanita yang mendapatkannya? Maaf, tetapi secara manual mencari variabel untuk menentukan tipe yang disimpan dalam bidang data adalah ide yang buruk sama sekali. Ini adalah kode c buatan tangan yang tidak pernah terlihat selama bertahun-tahun. Tapi jangan downvote, itu hanya sudut pandang saya :-)
Klaus
4
Saya kira Anda mendapatkan downvotes untuk serikat "dikebiri" atau "kehamilan". Agak sakit.
akaltar
2
Ya, saya kira itu hari yang gelap.
wallyk
14

Serikat pekerja berguna ketika berurusan dengan data level-byte (level rendah).

Salah satu penggunaan terakhir saya adalah pada pemodelan alamat IP yang terlihat seperti di bawah ini:

// 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;
YeenFei
sumber
7
Perlu diingat, bahwa mengakses barang mentah seperti itu tidak standar, dan mungkin tidak berfungsi seperti yang diharapkan dengan semua kompiler.
no
3
Juga, sangat umum untuk melihat ini digunakan dengan cara yang tidak menjamin keberpihakan, yang merupakan perilaku tidak terdefinisi.
Mooing Duck
10

Contoh ketika saya menggunakan serikat pekerja:

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

        };
...
}

ini memungkinkan saya untuk mengakses data saya sebagai array atau elemen.

Saya telah menggunakan serikat pekerja untuk menunjuk istilah yang berbeda ke nilai yang sama. Dalam pemrosesan gambar, apakah saya sedang mengerjakan kolom atau lebar atau ukuran dalam arah X, itu bisa membingungkan. Untuk mengatasi masalah ini, saya menggunakan gabungan sehingga saya tahu deskripsi mana yang cocok.

   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;
    };
DannyK
sumber
12
Perhatikan, bahwa meskipun solusi ini berfungsi pada sebagian besar platform yang dapat diamati, menetapkan nilai ke _x, _y, _z dan mengakses _coord adalah perilaku yang tidak ditentukan. Tujuan utama serikat pekerja adalah melestarikan ruang. Anda harus mengakses elemen penyatuan yang sama persis dengan yang Anda atur sebelumnya.
anxieux
1
ini adalah bagaimana saya menggunakannya juga, meskipun saya menggunakan std :: array forr coords, dan beberapa static_asserts
Viktor Sehr
1
Kode ini melanggar aturan aliasing yang ketat dan tidak boleh direkomendasikan.
Walter
Apakah mungkin ada cara untuk meningkatkan serikat sehingga dapat diandalkan untuk melakukan ini?
Andrew
8

Serikat pekerja memberikan polimorfisme dalam C.

Null Set
sumber
18
Saya pikir void*melakukan itu ^^
2
@ user166390 Polimorfisme menggunakan antarmuka yang sama untuk memanipulasi banyak jenis; void * tidak memiliki antarmuka.
Alice
2
Di C, polimorfisme biasanya diimplementasikan melalui tipe-tipe yang tidak jelas dan / atau pointer fungsi. Saya tidak tahu bagaimana atau mengapa Anda akan menggunakan serikat pekerja untuk mencapainya. Kedengarannya seperti ide yang benar-benar buruk.
Lundin
7

Penggunaan union yang brilian adalah penyelarasan memori, yang saya temukan dalam kode sumber PCL (Point Cloud Library). Struktur data tunggal dalam API dapat menargetkan dua arsitektur: CPU dengan dukungan SSE serta CPU tanpa dukungan SSE. Sebagai contoh: struktur data untuk PointXYZ adalah

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

3 pelampung diisi dengan pelampung tambahan untuk perataan SSE. Sehingga untuk

PointXYZ point;

Pengguna dapat mengakses point.data [0] atau point.x (tergantung pada dukungan SSE) untuk mengakses katakanlah, koordinat x. Detail penggunaan yang lebih baik dan lebih mirip ada di tautan berikut: Dokumentasi PCL Jenis-jenis PointT

Shubham Verma
sumber
7

Kata unionkunci, sementara masih digunakan dalam C ++ 03 1 , sebagian besar adalah sisa dari hari C. Masalah yang paling mencolok adalah bahwa ia hanya bekerja dengan POD 1 .

Namun, gagasan serikat pekerja masih ada, dan memang perpustakaan Boost menampilkan kelas seperti serikat pekerja:

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

Yang memiliki sebagian besar manfaat union(jika tidak semua) dan menambahkan:

  • kemampuan untuk menggunakan tipe non-POD dengan benar
  • keamanan tipe statis

Dalam praktiknya, telah ditunjukkan bahwa itu setara dengan kombinasi union+ enum, dan membandingkannya dengan cepat (sementara boost::anylebih merupakan ranah dynamic_cast, karena menggunakan RTTI).

1 Serikat ditingkatkan di C ++ 11 ( serikat tidak terbatas ), dan sekarang dapat berisi objek dengan destruktor, meskipun pengguna harus memanggil destruktor secara manual (pada anggota serikat aktif saat ini). Masih jauh lebih mudah menggunakan varian.

Matthieu M.
sumber
Ini tidak lagi benar dalam versi terbaru c ++. Lihat jawaban jrsala, misalnya.
Andrew
@Andrew: Saya memperbarui jawaban untuk menyebutkan bahwa C ++ 11, dengan serikat pekerja tidak terbatas, memungkinkan tipe dengan destruktor disimpan dalam penyatuan. Saya masih berdiri dengan sikap saya bahwa Anda benar-benar jauh lebih baik menggunakan serikat yang ditandai seperti boost::variantdaripada mencoba menggunakan serikat sendiri. Ada terlalu banyak perilaku tak terdefinisi di sekitar serikat pekerja sehingga peluang Anda untuk memperbaikinya benar-benar buruk.
Matthieu M.
3

Dari artikel Wikipedia tentang serikat pekerja :

Kegunaan utama serikat adalah untuk menghemat ruang , karena menyediakan cara untuk membiarkan berbagai jenis disimpan dalam ruang yang sama. Serikat pekerja juga memberikan polimorfisme kasar . Namun, tidak ada pemeriksaan tipe, jadi terserah programmer untuk memastikan bahwa bidang yang tepat diakses dalam konteks yang berbeda. Bidang yang relevan dari variabel gabungan biasanya ditentukan oleh keadaan variabel lain, mungkin dalam struct terlampir.

Satu idiom pemrograman C umum menggunakan serikat pekerja untuk melakukan apa yang oleh C ++ disebut reinterpret_cast, dengan menetapkan satu bidang persatuan dan membaca dari bidang lainnya, seperti yang dilakukan dalam kode yang tergantung pada representasi mentah dari nilai-nilai tersebut.

thkala
sumber
2

Pada hari-hari awal C (misalnya seperti yang didokumentasikan pada tahun 1974), semua struktur berbagi ruang nama yang sama untuk anggota mereka. Setiap nama anggota dikaitkan dengan tipe dan offset; jika "wd_woozle" adalah "int" pada offset 12, maka diberi pointer pdari semua jenis struktur, p->wd_woozleakan setara dengan *(int*)(((char*)p)+12). Bahasa mensyaratkan bahwa semua anggota dari semua jenis struktur memiliki nama unik kecuali bahwa itu secara eksplisit memungkinkan penggunaan kembali nama anggota dalam kasus di mana setiap struct di mana mereka digunakan memperlakukan mereka sebagai urutan awal umum.

Fakta bahwa tipe-tipe struktur dapat digunakan secara sembarangan memungkinkan struktur berperilaku seolah-olah mengandung bidang-bidang yang tumpang tindih. Misalnya, diberikan definisi:

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

kode dapat mendeklarasikan struktur tipe "float1" dan kemudian menggunakan "anggota" b0 ... b3 untuk mengakses masing-masing byte di dalamnya. Ketika bahasa diubah sehingga setiap struktur akan menerima namespace terpisah untuk anggotanya, kode yang bergantung pada kemampuan untuk mengakses berbagai hal akan rusak. Nilai memisahkan ruang nama untuk jenis struktur yang berbeda sudah cukup untuk mengharuskan kode tersebut diubah untuk mengakomodasi itu, tetapi nilai teknik tersebut cukup untuk membenarkan perluasan bahasa untuk terus mendukungnya.

Kode yang telah ditulis untuk mengeksploitasi kemampuan untuk mengakses penyimpanan dalam struct float1seolah-olah itu adalah sebuah struct byte4bisa dibuat untuk bekerja dalam bahasa baru dengan menambahkan deklarasi: union f1b4 { struct float1 ff; struct byte4 bb; };, menyatakan objek sebagai jenis union f1b4;bukan struct float1, dan mengganti akses ke f0, b0, b1, dll . dengan ff.f0, bb.b0, bb.b1, dll Meskipun ada cara yang lebih baik kode tersebut bisa saja yang didukung, unionpendekatan setidaknya agak bisa diterapkan, setidaknya dengan interpretasi C89-era aturan aliasing.

supercat
sumber
1

Katakanlah Anda memiliki berbagai jenis konfigurasi (hanya menjadi seperangkat variabel yang menentukan parameter). Dengan menggunakan enumerasi tipe konfigurasi, Anda dapat menentukan struktur yang memiliki ID tipe konfigurasi, bersama dengan gabungan semua tipe konfigurasi yang berbeda.

Dengan cara ini, di mana pun Anda lulus konfigurasi dapat menggunakan ID untuk menentukan cara menginterpretasikan data konfigurasi, tetapi jika konfigurasi itu besar Anda tidak akan dipaksa untuk memiliki struktur paralel untuk setiap tipe potensial ruang buang.

Gavin H
sumber
1

Satu dorongan baru-baru ini pada pentingnya serikat pekerja telah diberikan oleh Peraturan Aliasing Ketat diperkenalkan dalam versi terbaru dari standar C.

Anda dapat menggunakan serikat pekerja untuk mengetik-punning tanpa melanggar standar C.
Program ini memiliki perilaku yang tidak ditentukan (karena saya berasumsi floatdan unsigned intmemiliki panjang yang sama) tetapi tidak memiliki perilaku yang tidak jelas (lihat di sini ).

#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;
}
Komunitas
sumber
Aturan akses-jenis tidak hanya dalam versi "terkini" dari Standar. Setiap versi C pada dasarnya telah memasukkan aturan yang sama. Apa yang telah berubah adalah bahwa kompiler yang digunakan untuk memperhatikan catatan kaki "Maksud dari daftar ini adalah untuk menentukan keadaan di mana suatu objek mungkin atau tidak dapat alias." sebagai menunjukkan bahwa aturan tersebut tidak dimaksudkan untuk diterapkan dalam kasus yang tidak melibatkan aliasing seperti yang tertulis , tetapi mereka sekarang memperlakukannya sebagai undangan untuk menulis ulang kode untuk membuat alias di mana tidak ada.
supercat
1

Saya ingin menambahkan satu contoh praktis yang baik untuk menggunakan serikat pekerja - menerapkan rumus kalkulator / juru bahasa atau menggunakan semacam itu dalam perhitungan (misalnya, Anda ingin menggunakan bagian yang dapat dimodifikasi saat run-time dari rumus komputasi Anda - menyelesaikan persamaan secara numerik - hanya dengan menghitung sebagai contoh). Jadi, Anda mungkin ingin mendefinisikan bilangan / konstanta dari tipe yang berbeda (integer, floating-point, bahkan bilangan kompleks) seperti ini:

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

Jadi Anda menghemat memori dan apa yang lebih penting - Anda menghindari alokasi dinamis untuk jumlah yang mungkin ekstrem (jika Anda menggunakan banyak angka run-time) dari objek kecil (dibandingkan dengan implementasi melalui warisan kelas / polimorfisme). Tetapi yang lebih menarik, Anda masih dapat menggunakan kekuatan polimorfisme C ++ (jika Anda penggemar pengiriman ganda, misalnya;) dengan tipe struct ini. Cukup tambahkan pointer antarmuka "dummy" ke kelas induk dari semua jenis nomor sebagai bidang struct ini, menunjuk ke instance ini alih-alih / di samping tipe mentah, atau gunakan pointer fungsi C tua yang baik.

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);
 }
}

jadi Anda bisa menggunakan polimorfisme alih-alih mengetikkan centang dengan sakelar (tipe) - dengan implementasi yang efisien-memori (tidak ada alokasi dinamis objek kecil) - jika Anda memerlukannya, tentu saja.

Dalang
sumber
Ini mungkin berguna saat membuat bahasa yang dinamis. Masalah yang saya pikir akan diselesaikan adalah memodifikasi variabel tipe massa yang tidak diketahui tanpa mengimplementasikan modifikasi N kali. Makro menghebohkan untuk ini dan templating hampir mustahil.
Andrew
0

Dari http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

Penggunaan serikat sedikit dan jarang. Pada kebanyakan komputer, ukuran pointer dan int biasanya sama-ini karena keduanya biasanya masuk ke register di CPU. Jadi, jika Anda ingin melakukan lemparan pointer yang cepat dan kotor ke int atau dengan cara lain, nyatakan serikat pekerja.

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

Penggunaan lain dari gabungan adalah dalam perintah atau protokol pesan di mana pesan ukuran yang berbeda dikirim dan diterima. Setiap jenis pesan akan menyimpan informasi yang berbeda tetapi masing-masing akan memiliki bagian yang tetap (mungkin sebuah struct) dan sedikit bagian variabel. Ini adalah bagaimana Anda dapat mengimplementasikannya ..

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

struct msging80 {struct head fixed; pesan char [80]; }
struct msgint10 {struct head diperbaiki; pesan int [10]; } struct msgack {struct head fix; int ok; } union messagetype {
struct msging50 m50; struct msging80 m80; struct msgint10 i10; struct msgack ack; }

Dalam praktiknya, meskipun serikat memiliki ukuran yang sama, masuk akal untuk hanya mengirim data yang bermakna dan tidak membuang-buang ruang. Sebuah msgack hanya berukuran 16 byte sementara sebuah msging80 berukuran 92 byte. Jadi ketika variabel pesan awal diinisialisasi, ia memiliki bidang ukuran yang ditetapkan sesuai dengan jenisnya. Ini kemudian dapat digunakan oleh fungsi lain untuk mentransfer jumlah byte yang benar.

ανώνυμος
sumber
0

Serikat pekerja menyediakan cara untuk memanipulasi berbagai jenis data dalam satu area penyimpanan tanpa menanamkan informasi independen mesin apa pun dalam program. Mereka analog dengan catatan varian dalam pascal

Sebagai contoh seperti yang dapat ditemukan di manajer tabel simbol kompiler, misalkan konstanta dapat berupa int, float, atau penunjuk karakter. Nilai konstanta tertentu harus disimpan dalam variabel tipe yang tepat, namun akan lebih mudah bagi manajemen tabel jika nilainya menempati jumlah penyimpanan yang sama dan disimpan di tempat yang sama terlepas dari jenisnya. Ini adalah tujuan dari persatuan - variabel tunggal yang dapat secara sah menampung salah satu dari beberapa jenis. Sintaks didasarkan pada struktur:

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

Variabel u akan cukup besar untuk menampung yang terbesar dari ketiga jenis; ukuran spesifik tergantung pada implementasi. Setiap tipe ini dapat ditugaskan untuk Anda dan kemudian digunakan dalam ekspresi, selama penggunaannya konsisten

Khushal
sumber