Menu

C'de Union: Paylaşılan Bellek, Union ile Struct ve Etiketli Birleşimler

Bir C birleşiminin birkaç türü aynı baytlarda nasıl sakladığı - boyutu neden en büyük üye, en son yazdığınız üyeyi neden okumanız gerekir ve etiketli bir birleşimin (enum artı union) bunu nasıl güvenli kıldığı.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Bir yapı "bunların hepsi, birlikte" der. Bir union "bunlardan tam olarak biri, bir seferde" der. Üyeler aynı adreste üst üste serilir, dolayısıyla birleşim yalnızca en büyük üyesi kadar büyüktür ve bir üyeye yazmak diğerlerini yok eder.

Bu bir tuzak gibi geliyor ve denetimsiz hâlde öyledir. Ama aynı zamanda C'nin, birkaç türden biri olabilecek bir değeri - sayı ya da metin olan bir belirteç, tıklama ya da tuş basımı olan bir mesaj - hepsi için aynı anda ödeme yapmadan ifade etme biçimidir.

Bir Birleşim Bildirmek ve Kullanmak

Söz dizimi bir yapıyı tam olarak yansıtır; yalnızca anahtar kelime değişir.

Üyelere tam olarak bir yapıda olduğu gibi . ile (ya da bir gösterici üzerinden -> ile) erişilir. Farklı olan, yalnızca en son yazılan üyenin anlamlı bir değer tutmasıdır. v.f = 3.5f'ten sonra v.i'yi okumak size 3 vermez - size 3.5f'in bit örüntüsünün hangi tam sayıyı yazdığını verir.

Boyut: En Büyük Üye Kazanır

Aynı üyelere sahip bir yapı ile bir birleşimin bellek yerleşimini karşılaştırın:

Tipik bir makinede yapı 24 bayttır (int için 4, double için 8, char için 1, artı dolgu), birleşim ise 8'dir - hizalama için yukarı yuvarlanmış double'ının boyutu. Yazdırılan üç adres özdeştir ki bu, tek bir çıktı satırında birleşimlerin tüm hikâyesidir.

%p için void * dönüşümlerine dikkat edin. printf, %p için tam olarak bunu bekler; farklı bir gösterici tipi geçirmek genellikle çalışıyor görünse de tanımsız davranıştır. Bkz. biçim belirteçleri.

Bir Birleşime İlk Değer Vermek

Belirteci olmayan süslü parantezli bir ilk değer ilk üyeye ilk değer verir:

Kullanılacak olan belirtilmiş biçimdir. {42}, sessizce üye sırasına bağlıdır, dolayısıyla bildirimi sonradan yeniden sıralamak hangi üyenin ilk değeri aldığını değiştirir - gerçekten kötü bir hata, çünkü kodla ilgili hiçbir şey farklı görünmez.

Asıl Sorun: Hangi Üye Canlı?

Bir birleşim en son hangi üyeyi yazdığınızı kaydetmez. Yalnızca baytlardır; bilgi sizin kafanızda yaşar ve bilginin kaybolduğu yer tam olarak orasıdır.

Yazdırılan sayı büyük ve tuhaftır - 1.0f'in bit örüntüsünün int olarak okunuşu. Hiçbir şey çökmedi, hiçbir şey uyarmadı ve program sessizce yanlıştır. Birleşim tam olarak söz verdiğini yaptı; hata, hangi üyenin canlı olduğunu unutan bizdik.

Çözüm: Etiketli Bir Birleşim

Standart çözüm, birleşimi canlı üyeyi kaydeden bir enum ile eşleştirip ikisini de bir yapıya sarmaktır. O birleşime etiketli birleşim (ya da ayırt edilmiş birleşim) denir ve uygulama kodundaki esasen her birleşimi böyle yazmalısınız.

Her okuma artık kind üzerindeki switch'ten geçer, dolayısıyla - her yazma etiketi de ayarladığı sürece - hiç yazılmamış bir üyeyi okumak imkânsızdır. Yazmaları küçük kurucu fonksiyonlara (value_from_int, value_from_string) sarmak, bunu unutmayı imkânsız kılmanın alışıldık yoludur.

Bellek tasarrufu gerçektir: buradaki her Value, üçünü birden tutan bir yapının 4 + 4 + 24'ü yerine 24 baytlık yük artı etikete mal olur. Yüz bin tanesiyle bu önemlidir.

-Wall ile derlemek ikinci bir güvenlik ağı ekler: sonradan enum'a bir VAL_BOOL ekleyip onun için bir case unutursanız GCC ele alınmamış numaralandırma değeri için uyarır.

İsimsiz Birleşimler

C11, bir yapının içinde ismi olmayan bir birleşim üyesine izin verir ve onun üyelerini dıştaki yapının isim alanına yükseltir:

Birleşimin kendisi isimsiz olduğu için s->as.circle.r değil s->circle.r yazarsınız. Okuması daha kısadır; bedeli, işin içinde bir birleşim olduğunu tamamen gizlemesidir - ki etiket hemen yanında olduğunda bu sorunsuzdur.

Birleşimler Ekmeğini Gerçekten Nerede Hak Eder

Tekrar eden dört kullanım:

  • Varyant değerler. Yorumlayıcılar, JSON ve yapılandırma ayrıştırıcıları ve mesaj kuyrukları hepsi tipi çalışma zamanında belirlenen değerler taşır. Etiketli birleşim klasik gösterimdir.
  • Belleği sıkışık kayıtlar. Bir yapının birbirini dışlayan birkaç alanı varsa ve onlardan milyonlarca taneniz varsa, onları üst üste bindirmek doğrudan bir tasarruftur.
  • Protokol ve donanım yerleşimleri. Yükü bir başlık baytına bağlı olan bir paket, aygıt yazmaçları gibi doğal olarak etiketli bir birleşime eşlenir.
  • Bayt incelemesi. Bir değeri bir unsigned char[] dizisiyle üst üste bindirmek, örneğin bayt sırasını belirlemek için tek tek baytlarına bakmanızı sağlar:

Bu tür yeniden yorumlamadır (type punning) - baytları bilerek farklı bir tip olarak geri okumak. Bir birleşim üzerinden böyle okumaya C'de gerçekleştirime bağlı bir sonuç olarak açıkça izin verilir (ilgisiz tipler arasında gösterici dönüştürmenin aksine; o takma ad kurallarını bozar) ve baytları unsigned char olarak incelemek her zaman güvenlidir. Bir int'i float olarak yeniden yorumlamak başka bir meseledir: sonuç tamamen platformunuzun gösterimine bağlıdır, dolayısıyla onu taşınabilir koddan uzak tutun.

Sık Yapılan Hatalar

  • Yazmadığınız bir üyeyi okumak. Başlıca tehlike. Bir etiket kullanın.
  • Bir birleşimin dönüştürdüğünü varsaymak. Dönüştürmez. u.i = 3; float f = u.f; bitleri yeniden yorumlar; int i = 3; float f = i; dönüştürür. Bkz. tür dönüşümü.
  • Bir birleşime gösterici koyup onun izini kaybetmek. Bir dal sizin tahsis ettiğiniz bir char * tutuyorsa, birleşimin üzerine farklı bir üyeyle yazmak onu sızdırır - tampona işaret eden hiçbir şey kalmaz. Dal değiştirmeden önce serbest bırakın.
  • Derleyicinin denetlemesini beklemek. Denetlemez. Birleşimler, dilin boyut ve hizalamanın ötesinde hiç yardım etmediği birkaç C özelliğinden biridir; etiket tek korkuluğunuzdur.

Sıkça Sorulan Sorular

C'de union nedir?

Birleşim, tüm üyelerinin aynı belleği paylaştığı bir tiptir. Bir üyeye yazmak diğerlerinin üzerine yazar, dolayısıyla bir birleşim aynı anda üyelerinden tam olarak birini tutar. Bir yapı gibi ama union anahtar kelimesiyle bildirilir: union Value { int i; float f; };.

C'de union ile struct arasındaki fark nedir?

Bir yapı her üyeye kendi depolamasını verir ve hepsini aynı anda tutar, dolayısıyla boyutu en az üyelerinin toplamı kadardır. Bir birleşim her üyeyi aynı adreste üst üste bindirir ve aynı anda birini tutar, dolayısıyla boyutu en büyük üyesininkidir.

C'de bir birleşimin boyutu nedir?

En büyük üyesi için yeterli büyüklükte, hizalama için yukarı yuvarlanmış. Bir int (4 bayt) ile bir double'ın (8 bayt) birleşimi 8 bayttır - 12 değil. Kendi platformunuzda kontrol etmenin yolu sizeof'tur.

Yazmadığınız bir birleşim üyesini okursanız ne olur?

Aynı baytları farklı bir tip olarak yeniden yorumlarsınız. u.i = 1 yazıp sonra u.f okumak dönüştürmez - tam sayının bit örüntüsünü bir float olarak okur ve anlamsız bir sayı verir. Standart C'de bu en iyi ihtimalle belirtilmemiştir; etiketli birleşim kalıbının var olmasının sebebi budur: yanında hangi üyenin canlı olduğunu söyleyen bir etiket tutun.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA