Tujuan Serikat di C dan C ++

254

Saya telah menggunakan serikat pekerja sebelumnya dengan nyaman; hari ini saya terkejut ketika saya membaca posting ini dan mengetahui kode ini

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

sebenarnya adalah perilaku yang tidak terdefinisi. Yaitu membaca dari anggota serikat selain yang baru-baru ini ditulis untuk mengarah pada perilaku yang tidak terdefinisi. Jika ini bukan tujuan penggunaan serikat, lalu apa? Bisakah seseorang menjelaskannya dengan terperinci?

Memperbarui:

Saya ingin mengklarifikasi beberapa hal di belakang.

  • Jawaban untuk pertanyaan tidak sama untuk C dan C ++; diri saya yang lebih muda dan bodoh menandainya sebagai C dan C ++.
  • Setelah menjelajahi melalui standar C ++ 11, saya tidak dapat secara meyakinkan mengatakan bahwa itu memanggil mengakses / memeriksa anggota serikat yang tidak aktif tidak ditentukan / tidak ditentukan / implementasi-didefinisikan. Yang bisa saya temukan adalah §9.5 / 1:

    Jika penyatuan tata letak standar berisi beberapa struct tata letak standar yang berbagi urutan awal yang sama, dan jika objek tipe penyatuan tata letak standar ini berisi salah satu struktur tata letak standar, maka diizinkan untuk memeriksa urutan awal umum dari setiap anggota struct tata letak standar. §9.2 / 19: Dua struct tata letak standar berbagi urutan awal yang sama jika anggota yang sesuai memiliki tipe yang kompatibel dengan tata letak dan tidak ada anggota yang merupakan bidang bit atau keduanya adalah bidang bit dengan lebar yang sama untuk urutan satu atau lebih inisial anggota

  • Sementara di C, ( C99 TC3 - DR 283 dan seterusnya) adalah sah untuk melakukannya ( terima kasih kepada Pascal Cuoq untuk membawa ini). Namun, upaya untuk melakukannya masih dapat menyebabkan perilaku tidak terdefinisi , jika nilai yang dibaca tidak valid (disebut "representasi perangkap") untuk jenis yang akan dibaca. Jika tidak, nilai yang dibaca adalah implementasi yang ditentukan.
  • C89 / 90 menyebutkan hal ini berdasarkan perilaku yang tidak ditentukan (Lampiran J) dan buku K&R mengatakan bahwa implementasinya didefinisikan. Kutipan dari K&R:

    Ini adalah tujuan dari persatuan - variabel tunggal yang dapat secara sah menampung salah satu dari beberapa jenis. [...] selama penggunaannya konsisten: jenis yang diambil harus jenis yang paling baru disimpan. Merupakan tanggung jawab programmer untuk melacak jenis apa yang saat ini disimpan dalam serikat pekerja; hasilnya tergantung pada implementasi jika sesuatu disimpan sebagai satu jenis dan diekstraksi sebagai yang lain.

  • Ekstrak dari Stroustrup's TC ++ PL (tambang penekanan)

    Penggunaan serikat pekerja dapat menjadi sangat penting untuk kesesuaian data [...] terkadang disalahgunakan untuk "konversi tipe ".

Di atas segalanya, pertanyaan ini (yang judulnya tetap tidak berubah sejak permintaan saya) diajukan dengan maksud untuk memahami tujuan serikat pekerja DAN tidak pada standar yang memungkinkan. Misalnya, menggunakan pewarisan untuk penggunaan kembali kode, tentu saja, diizinkan oleh standar C ++, tetapi itu bukan tujuan atau niat awal untuk memperkenalkan warisan sebagai fitur bahasa C ++ . Inilah alasan mengapa jawaban Andrey tetap seperti yang diterima.

legends2k
sumber
11
Secara sederhana, kompiler diperbolehkan memasukkan padding antar elemen dalam suatu struktur. Dengan demikian, b, g, r,dan amungkin tidak berdekatan, dan dengan demikian tidak cocok dengan tata letak a uint32_t. Ini merupakan tambahan untuk masalah Endianess yang telah ditunjukkan oleh orang lain.
Thomas Matthews
8
Inilah mengapa Anda tidak seharusnya menandai pertanyaan C dan C ++. Jawabannya berbeda, tetapi karena penjawab bahkan tidak memberi tahu untuk tag apa yang mereka jawab (apakah mereka tahu?), Anda mendapatkan sampah.
Pascal Cuoq
5
@ downvoter Terima kasih karena tidak menjelaskan, saya mengerti bahwa Anda ingin saya memahami keluhan Anda secara ajaib dan tidak mengulanginya di masa mendatang: P
legends2k
1
Mengenai niat awal untuk memiliki serikat pekerja , ingatlah bahwa standar C pasca-tanggal serikat C oleh beberapa tahun. Pandangan cepat pada Unix V7 menunjukkan beberapa jenis konversi melalui serikat.
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...Betulkah? Anda mengutip catatan pengecualian , bukan poin utama tepat di awal paragraf : "Dalam sebuah serikat, paling banyak salah satu anggota data non-statis dapat aktif kapan saja, yaitu nilai paling banyak dari satu anggota data non-statis dapat disimpan dalam serikat kapan saja. " - dan turun ke p4: "Secara umum, seseorang harus menggunakan panggilan destruktor eksplisit dan penempatan operator baru untuk mengubah anggota aktif serikat "
underscore_d

Jawaban:

407

Tujuan serikat agak jelas, tetapi untuk beberapa alasan orang sering melewatkannya.

Tujuan penyatuan adalah untuk menghemat memori dengan menggunakan wilayah memori yang sama untuk menyimpan objek yang berbeda pada waktu yang berbeda. Itu dia.

Itu seperti sebuah kamar di sebuah hotel. Orang yang berbeda hidup di dalamnya selama periode waktu yang tidak tumpang tindih. Orang-orang ini tidak pernah bertemu, dan umumnya tidak tahu apa-apa tentang satu sama lain. Dengan mengelola pembagian waktu kamar dengan benar (yaitu dengan memastikan orang yang berbeda tidak ditugaskan ke satu kamar pada saat yang sama), sebuah hotel yang relatif kecil dapat menyediakan akomodasi untuk sejumlah besar orang, yang merupakan hotel adalah untuk.

Persis seperti itulah yang dilakukan serikat pekerja. Jika Anda tahu bahwa beberapa objek dalam program Anda memiliki nilai dengan nilai-nilai yang tidak tumpang tindih, maka Anda dapat "menggabungkan" objek-objek ini menjadi satu kesatuan dan dengan demikian menghemat memori. Sama seperti kamar hotel memiliki paling banyak satu penyewa "aktif" pada setiap saat, serikat memiliki paling banyak satu anggota "aktif" pada setiap saat waktu program. Hanya anggota "aktif" yang dapat dibaca. Dengan menulis ke anggota lain Anda mengubah status "aktif" ke anggota lain itu.

Untuk beberapa alasan, tujuan awal serikat pekerja ini "ditimpa" dengan sesuatu yang sama sekali berbeda: menulis satu anggota serikat pekerja dan kemudian memeriksanya melalui anggota lain. Reinterpretasi memori jenis ini (alias "tipe punning") bukan penggunaan serikat yang valid. Secara umum hal ini mengarah pada perilaku tidak terdefinisi yang digambarkan sebagai menghasilkan perilaku implementasi-didefinisikan dalam C89 / 90.

EDIT: Menggunakan serikat untuk tujuan penghukuman jenis (yaitu menulis satu anggota dan kemudian membaca yang lain) diberi definisi yang lebih rinci dalam salah satu Corrigenda Teknis dengan standar C99 (lihat DR # 257 dan DR # 283 ). Namun, perlu diingat bahwa ini secara formal tidak melindungi Anda dari berlari ke perilaku yang tidak terdefinisi dengan mencoba membaca representasi perangkap.

Semut
sumber
37
+1 untuk menjadi rumit, memberikan contoh praktis sederhana dan mengatakan tentang warisan serikat pekerja!
legends2k
6
Masalah yang saya miliki dengan jawaban ini adalah bahwa sebagian besar OS yang saya lihat memiliki file header yang melakukan hal ini. Sebagai contoh saya pernah melihatnya di versi lama (pra-64-bit) <time.h>pada Windows dan Unix. Mengabaikannya sebagai "tidak valid" dan "tidak terdefinisi" tidak cukup memadai jika saya akan dipanggil untuk memahami kode yang bekerja dengan cara yang tepat seperti ini.
TED
31
@AndreyT “Belum pernah hukum untuk menggunakan serikat pekerja untuk jenis hukuman hingga saat ini”: 2004 bukan “sangat baru”, terutama mengingat bahwa hanya C99 yang awalnya dengan kata-kata yang ceroboh, tampaknya membuat hukuman jenis melalui serikat tidak terdefinisi. Pada kenyataannya, jenis-menghukum meskipun serikat adalah legal di C89, legal di C11, dan itu legal di C99 selama ini meskipun butuh sampai 2004 bagi komite untuk memperbaiki kata-kata yang salah, dan kemudian merilis TC3. open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm
Pascal Cuoq
6
@ legends2k Bahasa pemrograman ditentukan oleh standar. Teknis Corrigendum 3 dari standar C99 secara eksplisit memungkinkan jenis-hukuman dalam catatan kaki 82, yang saya mengundang Anda untuk membaca sendiri. Ini bukan TV di mana bintang rock diwawancarai dan mengungkapkan pendapat mereka tentang perubahan iklim. Pendapat Stroustrup tidak memiliki pengaruh pada apa yang dikatakan standar C.
Pascal Cuoq
6
@ legends2k " Saya tahu bahwa pendapat individu mana pun tidak masalah dan hanya standar yang berlaku " Pendapat penulis kompiler jauh lebih penting daripada "spesifikasi" bahasa (sangat buruk).
curiousguy
38

Anda bisa menggunakan serikat pekerja untuk membuat struct seperti berikut ini, yang berisi bidang yang memberitahu kami komponen serikat mana yang sebenarnya digunakan:

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

    union
    {
        int intValue;
        double dblValue;
        char *strValue;
    } value;
} object;
Erich Kitzmueller
sumber
Saya sepenuhnya setuju, tanpa memasuki kekacauan perilaku yang tidak terdefinisi, mungkin ini adalah perilaku serikat yang paling diinginkan yang dapat saya pikirkan; tetapi tidak akan membuang-buang ruang ketika saya hanya menggunakan, mengatakan intatau char*untuk 10 item objek []; dalam hal ini, saya benar-benar dapat mendeklarasikan struct terpisah untuk setiap tipe data, bukan VAROBJECT? Bukankah itu mengurangi kekacauan dan menggunakan ruang yang lebih kecil?
legends2k
3
legenda: Dalam beberapa kasus, Anda tidak bisa melakukannya. Anda menggunakan sesuatu seperti VAROBJECT di C dalam kasus yang sama ketika Anda menggunakan Object di Java.
Erich Kitzmueller
Struktur data serikat yang ditandai tampaknya menjadi satu-satunya penggunaan serikat yang sah, seperti yang Anda jelaskan.
legends2k
Juga berikan contoh cara menggunakan nilai.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功 Sebagian contoh dari C ++ Primer , mungkin membantu. wandbox.org/permlink/cFSrXyG02vOSdBk2
Rick
34

Perilaku tidak terdefinisi dari sudut pandang bahasa. Pertimbangkan bahwa platform yang berbeda dapat memiliki batasan yang berbeda dalam penyelarasan memori dan endianness. Kode dalam big endian versus mesin endian kecil akan memperbarui nilai dalam struct secara berbeda. Memperbaiki perilaku dalam bahasa akan membutuhkan semua implementasi untuk menggunakan endianness yang sama (dan kendala penyelarasan memori ...) membatasi penggunaan.

Jika Anda menggunakan C ++ (Anda menggunakan dua tag) dan Anda benar-benar peduli tentang portabilitas, maka Anda bisa menggunakan struct dan menyediakan setter yang mengambil uint32_tdan mengatur bidang dengan tepat melalui operasi bitmask. Hal yang sama dapat dilakukan dalam C dengan suatu fungsi.

Sunting : Saya mengharapkan Pemrogram untuk menuliskan jawaban untuk memilih dan menutup yang ini. Seperti yang ditunjukkan beberapa komentar, endianness dibahas di bagian lain standar dengan membiarkan setiap implementasi memutuskan apa yang harus dilakukan, dan pelurusan dan bantalan juga dapat ditangani secara berbeda. Sekarang, aturan aliasing yang ketat yang mengacu oleh AProgrammer adalah poin penting di sini. Kompiler diperbolehkan untuk membuat asumsi tentang modifikasi (atau kurangnya modifikasi) variabel. Dalam kasus gabungan, kompiler dapat menyusun ulang instruksi dan memindahkan pembacaan masing-masing komponen warna di atas tulisan ke variabel warna.

David Rodríguez - dribeas
sumber
+1 untuk jawaban yang jelas dan sederhana! Saya setuju, untuk portabilitas, metode yang Anda berikan dalam paragraf ke-2 bagus; tetapi dapatkah saya menggunakan cara yang telah saya masukkan dalam pertanyaan, jika kode saya terikat ke satu arsitektur (membayar harga protabilitas), karena menghemat 4 byte untuk setiap nilai piksel dan beberapa waktu yang dihemat dalam menjalankan fungsi itu ?
legends2k
Masalah endian tidak memaksa standar untuk menyatakannya sebagai perilaku tidak terdefinisi - reinterpret_cast memiliki masalah endian yang persis sama, tetapi memiliki perilaku implementasi yang ditentukan.
JoeG
1
@ legends2k, masalahnya adalah pengoptimal dapat mengasumsikan bahwa uint32_t tidak dimodifikasi dengan menulis ke uint8_t dan sehingga Anda mendapatkan nilai yang salah ketika penggunaan dioptimalkan dengan asumsi ... @Joe, perilaku yang tidak ditentukan muncul segera setelah Anda mengakses pointer (saya tahu, ada beberapa pengecualian).
Pemrogram
1
@ legends2k / Pemrogram: Hasil reinterpret_cast didefinisikan sebagai implementasi. Menggunakan pointer yang dikembalikan tidak menghasilkan perilaku yang tidak ditentukan, hanya dalam implementasi perilaku yang ditentukan. Dengan kata lain, perilaku harus konsisten dan didefinisikan, tetapi tidak portabel.
JoeG
1
@ legends2k: pengoptimal yang layak akan mengenali operasi bitwise yang memilih seluruh byte dan menghasilkan kode untuk membaca / menulis byte, sama seperti gabungan tetapi didefinisikan dengan baik (dan portabel). mis. getint uint8_t () const {return color & 0x000000FF; } membatalkan setRed (uint8_t r) {color = (color & ~ 0x000000FF) | r; }
Ben Voigt
22

Yang paling umum penggunaan unionsaya secara teratur menemukan yang aliasing .

Pertimbangkan yang berikut ini:

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

Apa fungsinya? Hal ini memungkinkan bersih, akses rapi dari Vector3f vec;anggota 's dengan baik nama:

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

atau dengan akses integer ke dalam array

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

Dalam beberapa kasus, mengakses dengan nama adalah hal paling jelas yang dapat Anda lakukan. Dalam kasus lain, terutama ketika sumbu dipilih secara terprogram, hal yang lebih mudah dilakukan adalah mengakses sumbu dengan indeks numerik - 0 untuk x, 1 untuk y, dan 2 untuk z.

bobobobo
sumber
3
Ini juga disebut type-punningyang juga disebutkan dalam pertanyaan. Contoh dalam pertanyaan juga menunjukkan contoh yang serupa.
legends2k
4
Ini bukan jenis hukuman. Dalam contoh saya jenis cocok , jadi tidak ada "pun", itu hanya aliasing.
bobobobo
3
Ya, tapi tetap saja, dari sudut pandang absolut dari standar bahasa, anggota yang ditulis dan dibaca berbeda, yang tidak didefinisikan sebagaimana disebutkan dalam pertanyaan.
legends2k
3
Saya berharap bahwa standar di masa depan akan memperbaiki kasus khusus ini untuk diizinkan di bawah aturan "common initialitialence". Namun, array tidak berpartisipasi dalam aturan tersebut di bawah kata-kata saat ini.
Ben Voigt
3
@curiousguy: Jelas tidak ada persyaratan bahwa anggota struktur ditempatkan tanpa padding sewenang-wenang. Jika tes kode untuk penempatan struktur-anggota atau ukuran struktur, kode harus bekerja jika akses dilakukan langsung melalui serikat, tetapi pembacaan yang ketat terhadap Standar akan menunjukkan bahwa mengambil alamat serikat atau anggota struct menghasilkan pointer yang tidak dapat digunakan sebagai pointer dari tipenya sendiri, tetapi pertama-tama harus dikonversi kembali ke pointer ke tipe penutup atau tipe karakter. Kompiler yang dapat dikerjakan dari jarak jauh akan memperluas bahasa dengan membuat lebih banyak hal berfungsi daripada ...
supercat
10

Seperti yang Anda katakan, ini adalah perilaku yang sangat tidak terdefinisi, meskipun itu akan "bekerja" pada banyak platform. Alasan sebenarnya untuk menggunakan serikat adalah untuk membuat catatan varian.

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;

Tentu saja, Anda juga perlu semacam diskriminator untuk mengatakan apa varian sebenarnya mengandung. Dan perhatikan bahwa dalam C ++ serikat tidak banyak digunakan karena mereka hanya dapat berisi tipe POD - secara efektif yang tanpa konstruktor dan destruktor.


sumber
Sudahkah Anda menggunakannya (seperti dalam pertanyaan) ?? :)
legends2k
Agak aneh, tapi saya tidak bisa menerima "catatan varian". Yaitu, saya yakin mereka ada dalam pikiran, tetapi jika mereka menjadi prioritas mengapa tidak menyediakannya? "Berikan blok penyusun karena mungkin berguna untuk membangun hal-hal lain juga" sepertinya secara intuitif lebih mungkin. Terutama mengingat setidaknya satu aplikasi lagi yang mungkin ada dalam pikiran - memori I / O register dipetakan, di mana input dan output register (sementara tumpang tindih) adalah entitas yang berbeda dengan nama, jenis, dll.
Steve314
@ Stev314 Jika itu adalah penggunaan yang ada dalam pikiran mereka, mereka bisa membuatnya menjadi perilaku yang tidak terdefinisi.
@Neil: +1 untuk orang pertama yang mengatakan tentang penggunaan aktual tanpa memukul perilaku yang tidak terdefinisi. Saya kira mereka bisa membuat implementasi itu didefinisikan seperti operasi penghukuman jenis lainnya (reinterpret_cast, dll.). Tapi seperti yang saya tanyakan, apakah Anda menggunakannya untuk jenis-hukuman?
legends2k
@ Neil - contoh register yang dipetakan memori tidak terdefinisi, endian / etc yang biasa dikesampingkan dan diberi flag "volatile". Menulis ke alamat dalam model ini tidak merujuk register yang sama dengan membaca alamat yang sama. Oleh karena itu tidak ada masalah "apa yang Anda baca kembali" karena Anda tidak membaca kembali - apa pun output yang Anda tulis ke alamat itu, ketika Anda membaca Anda hanya membaca input independen. Satu-satunya masalah adalah memastikan Anda membaca sisi input serikat dan menulis sisi output. Adalah hal umum dalam hal yang disematkan - mungkin masih demikian.
Steve314
8

Di C itu adalah cara yang bagus untuk mengimplementasikan sesuatu seperti varian.

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;

Pada saat memori litlle struktur ini menggunakan memori kurang dari struct yang memiliki semua anggota.

By the way C menyediakan

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

untuk mengakses nilai bit.

Totonga
sumber
Meskipun kedua contoh Anda didefinisikan dengan sempurna dalam standar; tapi, hei, menggunakan bidang bit pasti ditembak kode yang tidak dapat diakses, bukan?
legends2k
Bukan itu. Sejauh yang saya tahu ini didukung secara luas.
Totonga
1
Dukungan kompiler tidak diterjemahkan ke dalam portabel. Buku C : C (dengan demikian C ++) tidak memberikan jaminan pemesanan bidang dalam kata-kata mesin, jadi jika Anda menggunakannya untuk alasan yang terakhir, program Anda tidak hanya akan non-portabel, itu juga akan bergantung pada kompiler.
legends2k
5

Meskipun ini adalah perilaku yang sangat tidak terdefinisi, dalam praktiknya ia akan bekerja dengan hampir semua kompiler. Ini adalah paradigma yang banyak digunakan sehingga setiap kompiler yang menghargai diri sendiri perlu melakukan "hal yang benar" dalam kasus-kasus seperti ini. Tentunya lebih disukai daripada tipe-punning, yang mungkin menghasilkan kode rusak dengan beberapa kompiler.

Paul R
sumber
2
Apakah tidak ada masalah endian? Perbaikan yang relatif mudah dibandingkan dengan "tidak terdefinisi", tetapi layak dipertimbangkan untuk beberapa proyek jika demikian.
Steve314
5

Dalam C ++, Boost Variant mengimplementasikan versi serikat yang aman, yang dirancang untuk mencegah perilaku tidak terdefinisi sebanyak mungkin.

Penampilannya identik dengan enum + unionkonstruk (stack dialokasikan terlalu dll) tetapi menggunakan daftar templat jenis bukan enum:)

Matthieu M.
sumber
5

Perilaku tersebut mungkin tidak terdefinisi, tetapi itu berarti tidak ada "standar". Semua kompiler yang layak menawarkan #pragma untuk mengontrol pengemasan dan perataan, tetapi mungkin memiliki standar yang berbeda. Standarnya juga akan berubah tergantung pada pengaturan optimasi yang digunakan.

Juga, serikat pekerja tidak hanya untuk menghemat ruang. Mereka dapat membantu kompiler modern dengan jenis hukuman. Jika Anda reinterpret_cast<>semuanya kompiler tidak dapat membuat asumsi tentang apa yang Anda lakukan. Mungkin harus membuang apa yang diketahui tentang jenis Anda dan mulai lagi (memaksa menulis kembali ke memori, yang sangat tidak efisien hari ini dibandingkan dengan kecepatan jam CPU).

Nick
sumber
4

Secara teknis itu tidak terdefinisi, tetapi dalam kenyataannya sebagian besar (semua?) Kompiler memperlakukannya persis sama dengan menggunakan reinterpret_castdari satu jenis ke yang lain, yang hasilnya didefinisikan implementasi. Saya tidak akan kehilangan tidur karena kode Anda saat ini.

JoeG
sumber
" reinterpret_cast dari satu jenis ke yang lain, yang hasilnya didefinisikan implementasinya. " Tidak, tidak. Implementasi tidak harus mendefinisikannya, dan sebagian besar tidak mendefinisikannya. Juga, apa yang akan menjadi perilaku yang diizinkan implementasi yang ditetapkan dari casting beberapa nilai acak ke sebuah pointer?
curiousguy
4

Untuk satu contoh lagi tentang penggunaan aktual serikat pekerja, kerangka kerja CORBA membuat serialisasi objek menggunakan pendekatan serikat yang ditandai. Semua kelas yang ditentukan pengguna adalah anggota satu serikat (besar), dan pengenal bilangan bulat memberi tahu demarshaller cara menafsirkan serikat.

Cubbi
sumber
4

Yang lain menyebutkan perbedaan arsitektur (little - big endian).

Saya membaca masalah bahwa karena memori untuk variabel dibagi, kemudian dengan menulis ke satu, yang lain berubah dan, tergantung pada jenisnya, nilainya bisa menjadi tidak berarti.

misalnya. union {float f; int i; } x;

Menulis ke xi akan menjadi tidak berarti jika Anda kemudian membaca dari xf - kecuali jika itu yang Anda maksud untuk melihat tanda, komponen eksponen atau mantissa dari float.

Saya pikir ada juga masalah perataan: Jika beberapa variabel harus selaras kata maka Anda mungkin tidak mendapatkan hasil yang diharapkan.

misalnya. union {char c [4]; int i; } x;

Jika, secara hipotetis, pada beberapa mesin char harus sejajar kata maka c [0] dan c [1] akan berbagi penyimpanan dengan i tetapi tidak c [2] dan c [3].

philcolbourn
sumber
Satu byte yang harus disejajarkan dengan kata? Itu tidak masuk akal. Sebuah byte tidak memiliki persyaratan keselarasan, menurut definisi.
curiousguy
Ya, saya mungkin seharusnya menggunakan contoh yang lebih baik. Terima kasih.
philcolbourn
@curiousguy: Ada banyak kasus di mana seseorang mungkin ingin agar array byte disejajarkan dengan kata. Jika seseorang memiliki banyak array misalnya 1024 byte dan akan sering ingin menyalin satu sama lain, meminta mereka menyelaraskan kata pada banyak sistem menggandakan kecepatan memcpy()dari satu ke yang lain. Beberapa sistem mungkin secara spekulatif menyelaraskan char[]alokasi yang terjadi di luar struktur / serikat pekerja untuk alasan itu dan lainnya. Dalam contoh yang ada, asumsi yang iakan tumpang tindih semua untuk elemen c[]adalah non-portabel, tapi itu karena tidak ada jaminan itu sizeof(int)==4.
supercat
4

Dalam bahasa C seperti yang didokumentasikan pada tahun 1974, semua anggota struktur berbagi ruang nama yang sama, dan arti "ptr-> anggota" didefinisikan sebagai menambahkan perpindahan anggota ke "ptr" dan mengakses alamat yang dihasilkan menggunakan jenis anggota. Desain ini memungkinkan untuk menggunakan ptr yang sama dengan nama anggota yang diambil dari definisi struktur yang berbeda tetapi dengan offset yang sama; programmer menggunakan kemampuan itu untuk berbagai keperluan.

Ketika anggota struktur diberi ruang nama mereka sendiri, menjadi tidak mungkin untuk mendeklarasikan dua anggota struktur dengan perpindahan yang sama. Menambahkan serikat pekerja ke bahasa memungkinkan untuk mencapai semantik yang sama yang telah tersedia di versi bahasa sebelumnya (meskipun ketidakmampuan untuk memiliki nama diekspor ke konteks terlampir mungkin masih perlu menggunakan find / replace untuk mengganti foo-> anggota ke foo-> type1.member). Yang penting bukanlah bahwa orang-orang yang menambahkan serikat memiliki target penggunaan tertentu dalam pikiran, tetapi bahwa mereka menyediakan sarana dimana programmer yang mengandalkan semantik sebelumnya, untuk tujuan apa pun , masih harus dapat mencapai semantik yang sama bahkan jika mereka harus menggunakan sintaks yang berbeda untuk melakukannya.

supercat
sumber
Menghargai pelajaran sejarah, namun dengan standar yang mendefinisikan ini dan itu seperti tidak terdefinisi, yang tidak terjadi di era C dulu di mana buku K&R adalah satu-satunya "standar", kita harus yakin tidak menggunakannya untuk tujuan apa pun dan masukkan tanah UB.
legends2k
2
@ legends2k: Ketika Standar ini ditulis, mayoritas implementasi C memperlakukan serikat dengan cara yang sama, dan perlakuan semacam itu bermanfaat. Namun, beberapa tidak, dan penulis Standar enggan menyebut implementasi yang ada sebagai "tidak sesuai". Sebaliknya, mereka berpendapat bahwa jika pelaksana tidak membutuhkan Standar untuk memberitahu mereka untuk melakukan sesuatu (sebagaimana dibuktikan oleh fakta bahwa mereka sudah melakukannya ), membiarkannya tidak ditentukan atau tidak ditentukan hanya akan mempertahankan status quo . Gagasan bahwa hal itu harus membuat hal-hal yang kurang didefinisikan daripada sebelum Standar ditulis ...
supercat
2
... sepertinya inovasi yang jauh lebih baru. Apa yang sangat menyedihkan tentang semua ini adalah bahwa jika penulis kompiler yang menargetkan aplikasi kelas atas adalah untuk mengetahui cara menambahkan arahan optimasi yang bermanfaat untuk bahasa yang paling banyak diterapkan oleh kompiler pada tahun 1990-an, daripada membuat fitur dan jaminan yang didukung oleh "hanya "90% implementasi, hasilnya akan menjadi bahasa yang dapat melakukan lebih baik dan lebih andal daripada hiper-modern C.
supercat
2

Anda dapat menggunakan serikat pekerja karena dua alasan utama:

  1. Cara praktis untuk mengakses data yang sama dengan cara yang berbeda, seperti dalam contoh Anda
  2. Cara untuk menghemat ruang ketika ada anggota data yang berbeda yang hanya satu yang bisa 'aktif'

1 Benar-benar lebih dari peretasan gaya-C untuk memotong kode penulisan dengan dasar Anda tahu bagaimana arsitektur memori sistem target bekerja. Seperti yang sudah dikatakan, Anda biasanya bisa lolos jika Anda tidak benar-benar menargetkan banyak platform yang berbeda. Saya percaya beberapa penyusun mungkin membiarkan Anda menggunakan arahan pengepakan juga (saya tahu mereka lakukan pada struct)?

Contoh 2. yang baik dapat ditemukan dalam tipe VARIAN yang digunakan secara luas dalam COM.

Pak Boy
sumber
2

Seperti yang disebutkan lainnya, serikat pekerja yang dikombinasikan dengan enumerasi dan dibungkus dengan struct dapat digunakan untuk mengimplementasikan serikat yang ditandai. Salah satu penggunaan praktis adalah untuk mengimplementasikan Rust Result<T, E>, yang awalnya diimplementasikan menggunakan murni enum(Rust dapat menyimpan data tambahan dalam varian enumerasi). Berikut ini adalah contoh 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;
}
Kotauskas
sumber