Etkin olmayan sendika üyesine ve tanımlanmamış davranışa erişiyor musunuz?


129

unionSon gruptan başka bir üyeye erişmenin UB olduğu izlenimine kapıldım, ancak sağlam bir referans bulamıyorum (UB olduğunu iddia eden ancak standarttan herhangi bir destek olmadan cevaplar dışında).

Öyleyse, tanımlanmamış bir davranış mı?


3
C99 (ve ben de C ++ 11'e inanıyorum) sendikalarla tür karıştırmaya açıkça izin veriyor. Bu yüzden "uygulama tanımlı" davranışa girdiğini düşünüyorum.
Mysticial

1
Bireysel int'ten karaktere dönüştürmek için birkaç kez kullandım. Bu yüzden kesinlikle tanımsız olmadığını biliyorum. Sun CC derleyicisinde kullandım. Dolayısıyla, yine de derleyiciye bağımlı olabilir.
go4sri

42
@ go4sri: Açıkça, davranışın tanımsız olmasının ne anlama geldiğini bilmiyorsunuz. Bazı durumlarda sizin için işe yarıyor gibi görünmesi, onun belirsizliğiyle çelişmez.
Benjamin Lindley


4
@Mysticial, bağlantı verdiğiniz blog yazısı özellikle C99 ile ilgili; bu soru yalnızca C ++ için etiketlenmiştir.
davmac

Yanıtlar:


131

Buradaki karışıklık, C'nin bir birleşim yoluyla tür-punning'e açıkça izin vermesidir, oysa C ++ () böyle bir izne sahip değildir.

6.5.2.3 Yapı ve sendika üyeleri

95) Bir birleşim nesnesinin içeriğini okumak için kullanılan üye, nesnede bir değer depolamak için en son kullanılan üye ile aynı değilse, değerin nesne temsilinin uygun kısmı, yeni nesnede bir nesne temsili olarak yeniden yorumlanır. 6.2.6'da açıklandığı gibi yazın (bazen '' tip punning '' olarak adlandırılan bir işlem). Bu bir tuzak temsili olabilir.

C ++ ile durum:

9.5 Sendikalar [class.union]

Bir birleşimde, statik olmayan veri üyelerinden en fazla biri herhangi bir zamanda aktif olabilir, yani statik olmayan veri üyelerinden en fazla birinin değeri herhangi bir zamanda bir birleşimde depolanabilir.

C ++ daha sonra structortak başlangıç ​​dizilerine sahip s içeren birliklerin kullanımına izin veren bir dile sahiptir ; ancak bu tür karıştırmaya izin vermez.

Sendika tip-cinaslı olmadığını belirlemek için olan C izin ++, daha fazla aramak zorunda. Hatırlamak C ++ 11 için normatif bir referanstır (ve C99, birleşim tipi çalıştırmaya izin veren C11'e benzer dile sahiptir):

3.9 Türler [basic.types]

4 - T tipi bir nesnenin nesne temsili, N'nin sizeof (T) 'ye eşit olduğu, T tipi nesne tarafından alınan N işaretsiz karakter nesnesi dizisidir. Bir nesnenin değer temsili, T tipi değerini tutan bit kümesidir. Önemsiz şekilde kopyalanabilir türler için, değer temsili, bir uygulamanın ayrı bir öğesi olan bir değeri belirleyen nesne gösteriminde bir bit kümesidir. tanımlanmış değerler kümesi. 42
42) Amaç, C ++ bellek modelinin ISO / IEC 9899 Programlama Dili C ile uyumlu olmasıdır.

Okurken özellikle ilginçleşiyor

3.8 Nesne ömrü [basic.life]

T tipi bir nesnenin ömrü şu durumlarda başlar: - T tipi için uygun hizalama ve boyuta sahip depolama elde edildiğinde ve - nesne önemsiz olmayan bir başlatmaya sahipse, başlatması tamamlanır.

Dolayısıyla , bir birleşimde bulunan ( ipso facto'nun önemsiz başlatmaya sahip olduğu) ilkel bir tür için , nesnenin ömrü, en azından birleşmenin kendi yaşam süresini kapsar. Bu bizi çağırmamızı sağlar

3.9.2 Bileşik türleri [temel bileşik]

Bir A adresinde T tipi bir nesne bulunuyorsa, değeri A adresi olan cv T * türünde bir göstericinin, değerin nasıl elde edildiğine bakılmaksızın bu nesneyi işaret ettiği söylenir.

İlgilendiğimiz işlemin tip-punning olduğunu varsayarsak, yani aktif olmayan bir sendika üyesinin değerini almak ve yukarıda belirtilen üye tarafından atıfta bulunulan nesneye geçerli bir referansımız olduğu için, bu işlem lvalue-to -rvalue dönüşümü:

4.1 Ldeğer-değer dönüşümü [dönş.lval]

İşlevsiz, dizi olmayan türden Tbir glvalue, prvalue'ya dönüştürülebilir. Eğer Teksik bir tip, kötü oluşturulan bu dönüşümü gerektiren bir programdır. Glvalue'nun başvurduğu Tnesne bir tür Tnesnesi değilse ve türetilen türde bir nesne değilse veya nesne başlatılmamışsa, bu dönüştürmeyi gerektiren bir programın tanımlanmamış bir davranışı vardır.

O zaman soru, aktif olmayan bir birlik üyesi olan bir nesnenin, aktif birlik üyesine depolama tarafından başlatılıp başlatılmadığıdır. Söyleyebileceğim kadarıyla durum böyle değil ve bu nedenle eğer:

  • bir birleşim chardizi depolamaya kopyalanır ve geri alınır (3.9: 2) veya
  • bir birleşim aynı türden başka bir sendikaya kopyalanır (3.9: 3) veya
  • ISO / IEC 9899'a (tanımlandığı kadarıyla) (3.9: 4 not 42) uygun bir program öğesi tarafından dil sınırları ötesinde bir birliğe erişilir, o zaman

aktif olmayan bir üye tarafından bir birliğe erişim tanımlanır ve nesne ve değer temsilini takip edecek şekilde tanımlanır, yukarıdaki ara konumlardan biri olmadan erişim tanımsız bir davranıştır. Uygulama, elbette tanımsız davranışın meydana gelmediğini varsayabileceğinden, bunun, böyle bir programda gerçekleştirilmesine izin verilen optimizasyonlara etkileri vardır.

Yani, aktif olmayan bir sendika üyesine meşru olarak bir değer oluşturabilsek de (bu nedenle, inşaat olmadan aktif olmayan bir üyeye atama yapmak uygundur) başlatılmamış olarak kabul edilir.


5
3.8 / 1, depolama alanı yeniden kullanıldığında bir nesnenin ömrünün sona erdiğini söylüyor. Bu bana, bir sendikanın yaşamının aktif olmayan bir üyesinin, deposu aktif üye için yeniden kullanıldığı için sona erdiğini gösteriyor. Bu, üyeyi kullanma şeklinizin sınırlı olduğu anlamına gelir (3.8 / 6).
bames53

2
Bu yoruma göre, her bir bellek parçası eşzamanlı olarak, önemsiz bir şekilde başlatılabilen ve uygun hizalamaya sahip tüm türlerdeki nesneleri içerir ... Böylece, önemsiz bir şekilde başlatılabilir herhangi bir türün ömrü, deposu diğer tüm türler için yeniden kullanıldığında hemen sona erer ( ve önemsiz bir şekilde başlatılamadıkları için yeniden başlamıyorlar)?
bames53

3
4.1 ifadesi tamamen ve tamamen bozuktur ve o zamandan beri yeniden yazılmıştır. Tamamen geçerli olan her türlü şeye izin vermedi: özel memcpyuygulamalara izin vermedi (nesnelere unsigned charldeğerleri kullanarak erişme ), *psonrasına erişime izin vermedi int *p = 0; const int *const *pp = &p;('den' int**e örtük dönüştürme const int*const*geçerli olsa bile), csonrasına erişime bile izin vermedi struct S s; const S &c = s;. CWG sorunu 616 . Yeni ifade izin veriyor mu? Ayrıca [basic.lval] var.

2
@Omnifarious: &Bir sendika üyesine uygulandığında tekli operatörün ne anlama geldiğini açıklığa kavuşturması gerekse de (ve C Standardının ayrıca açıklığa kavuşturması gerekir) bu mantıklı olacaktır . Ortaya çıkan göstericinin en azından bir sonraki sefere başka bir üye lvalue'nun bir sonraki doğrudan veya dolaylı kullanımına kadar üyeye erişmek için kullanılabilir olması gerektiğini düşünüyorum, ancak gcc'de işaretçi o kadar uzun süre bile kullanılamıyor, bu da bir soruyu gündeme getiriyor. &operatör ortalama gerekiyordu.
süper araba

4
"C99'un C ++ 11 için normatif bir referans olduğunu hatırlayın" ile ilgili bir soru Bu sadece c ++ standardının açıkça C standardına atıfta bulunduğu (örneğin c kütüphanesi fonksiyonları için) geçerli değil midir?
MikeMB

28

C ++ 11 standardı bu şekilde söylüyor

9.5 Birlikler

Bir birleşimde, statik olmayan veri üyelerinden en fazla biri herhangi bir zamanda aktif olabilir, yani statik olmayan veri üyelerinden en fazla birinin değeri herhangi bir zamanda bir birleşimde depolanabilir.

Yalnızca bir değer saklanıyorsa, diğerini nasıl okuyabilirsiniz? Sadece orada değil.


Gcc belgeleri bunu Uygulama tanımlı davranış altında listeler

  • Bir birleşim nesnesinin üyesine, farklı türden bir üye kullanılarak erişilir (C90 6.3.2.3).

Nesnenin temsilinin ilgili baytları, erişim için kullanılan türde bir nesne olarak değerlendirilir. Bkz. Yazma işlemi. Bu bir tuzak temsili olabilir.

bunun C standardı için gerekli olmadığını gösterir.


2016-01-05: Yorumlar aracılığıyla , C standart belgesine dipnot olarak benzer bir metin ekleyen C99 Kusur Raporu # 283'e bağlandım :

78a) Bir birleşim nesnesinin içeriğine erişmek için kullanılan üye, nesnede bir değer depolamak için en son kullanılan üye ile aynı değilse, değerin nesne temsilinin uygun kısmı, yeni nesnede bir nesne temsili olarak yeniden yorumlanır. 6.2.6'da açıklandığı gibi yazın (bazen "tür punning" olarak adlandırılan bir işlem). Bu bir tuzak temsili olabilir.

Bir dipnotun standart için normatif olmadığını düşünürsek, pek açıklığa kavuşturup açmadığından emin değilim.


10
@LuchianGrigore: UB, standardın UB dediği şey değildir, bunun yerine standardın nasıl çalışması gerektiğini açıklamadığı şeydir. Bu tam olarak böyle bir durumdur. Standart ne olacağını açıklıyor mu? Uygulama tanımlı olduğunu söylüyor mu? Hayır ve hayır. Yani UB. Dahası, "üyeler aynı hafıza adresini paylaşıyor" argümanıyla ilgili olarak, sizi tekrar UB'ye götürecek olan takma ad kurallarına başvurmanız gerekecektir.
Yakov Galka

5
@Luchian: Aktifin ne anlama geldiği oldukça açık, "yani, statik olmayan veri üyelerinden en fazla birinin değeri herhangi bir zamanda bir birleşimde depolanabilir."
Benjamin Lindley

5
@LuchianGrigore: Evet var. Standardın ele almadığı (ve edemediği) sonsuz sayıda durum vardır. (C ++ bir Turing eksiksiz sanal makinesidir, bu yüzden eksiktir.) Peki ne? "Aktif" kelimesinin ne anlama geldiğini açıklıyor, "o" dan sonra yukarıdaki alıntıya bakın.
Yakov Galka

8
@LuchianGrigore: Tanımlar bölümüne göre, açık davranış tanımının ihmal edilmesi de, tanımlanmamış davranış olarak değerlendirilmez.
jxh

5
@Claudiu Bu UB'nin farklı bir nedeni var - katı bir takma adı ihlal ediyor.
Gizemli

18

Sanırım standardın tanımlanmamış davranış olduğunu söylemeye en yakın olanı, ortak bir başlangıç ​​dizisi içeren bir birleşim için davranışı tanımladığı yerdir (C99, §6.5.2.3 / 5):

Sendikaların kullanımını basitleştirmek için özel bir garanti verilir: Bir birlik ortak bir başlangıç ​​sırasını paylaşan birkaç yapı içeriyorsa (aşağıya bakınız) ve birleşim nesnesi şu anda bu yapılardan birini içeriyorsa, ortak olanı incelemeye izin verilir. sendikanın tam türünün bir beyanının görülebildiği herhangi bir yerin ilk kısmı. Karşılık gelen üyelerin bir veya daha fazla ilk üye dizisi için uyumlu tipleri (ve bit alanları için aynı genişlikleri) varsa, iki yapı ortak bir başlangıç ​​dizisini paylaşır.

C ++ 11, §9.2 / 19'da benzer gereksinimleri / izni verir:

Bir standart düzen birleşimi, ortak bir ilk sırayı paylaşan iki veya daha fazla standart düzen yapısı içeriyorsa ve standart düzen birleşim nesnesi şu anda bu standart yerleşim yapılarından birini içeriyorsa, herhangi bir ortak başlangıç ​​bölümünü incelemesine izin verilir. onların. Karşılık gelen üyelerin mizanpajla uyumlu türleri varsa ve hiçbir üye bir bit alanı değilse veya her ikisi de bir veya daha fazla ilk üye dizisi için aynı genişliğe sahip bit alanları ise, iki standart-yerleşim yapısı ortak bir başlangıç ​​sırasını paylaşır.

Her ikisi de doğrudan ifade etmese de, her ikisi de bir üyeyi "incelemeye" (okumaya) yalnızca "izin verildiğine" dair güçlü bir ima taşır. 1) üye en son (bir parçası) yazıyorsa veya 2) ortak bir baş harfin parçasıysa sıra.

Bu, başka türlü yapmanın tanımlanmamış davranış olduğuna dair doğrudan bir ifade değil, ama benim en yakın bildiğim şey bu.


Bunu tamamlamak için, C ++ için "düzen uyumlu türlerin" veya C için "uyumlu türlerin" ne olduğunu bilmeniz gerekir.
Michael Anderson,

2
@MichaelAnderson: Evet ve hayır. Bir şeyin bu istisna kapsamına girip girmediğinden emin olmak istediğinizde / istersen bunlarla ilgilenmeniz gerekir - ancak buradaki asıl soru, istisnanın açıkça dışında kalan bir şeyin gerçekten UB verip vermediğidir. Bence bu, amacı netleştirmek için burada yeterince güçlü bir şekilde ima edildi, ancak bunun doğrudan ifade edildiğini sanmıyorum.
Jerry Tabut

Bu "ortak ilk sekans" olayı, projelerimden 2 veya 3'ünü Yeniden Yazma Kutusu'ndan kurtarmış olabilir. unionBelirli bir blog tarafından bunun iyi olduğu izlenimi verildiğinden ve etrafında birkaç büyük yapı ve proje inşa ettiğimden, tanımlanmamış s'nin en gizli kullanımlarını ilk okuduğumda çok öfkeliydim . Şimdi düşünüyorum benim beri, sonuçta Tamam olabilir unionler ön aynı türde olan sınıfları ihtiva ettiğini ortaya çıkarmaktadır
underscore_d

@JerryCoffin, Beni aynı soruya ima düşünüyorum: Ne bizim eğer unioniçeren örneğin bir uint8_tve class Something { uint8_t myByte; [...] };- Ben de burada geçerli olacak bu koşulu karşılamaktadır varsayılabilir, ama çok kasıtlı sadece izin vermek için ifadeli oluyor structs. Neyse ki ham ilkellerin yerine bunları zaten kullanıyorum: O
altçizgi_d

@underscore_d: C standardı en azından şu soruyu kapsar: "Uygun şekilde dönüştürülmüş bir yapı nesnesine bir işaretçi, başlangıç ​​üyesine (veya bu üye bir bit-alanı ise, o zaman içinde bulunduğu birime) işaret eder ve tam tersi. "
Jerry Coffin

12

Mevcut cevaplarda henüz bahsedilmeyen bir şey, 6.2.5 bölümünün 21. paragrafındaki 37 no'lu dipnottur:

Birleştirme türüne sahip bir nesne aynı anda yalnızca bir üye içerebileceğinden, toplama türünün birleşim türünü içermediğini unutmayın.

Bu gereklilik açıkça bir üyeye yazıp başka birinde okumamanız gerektiğini ima ediyor gibi görünüyor. Bu durumda, spesifikasyon eksikliği nedeniyle tanımlanmamış bir davranış olabilir.


Çoğu uygulama, depolama biçimlerini ve düzen kurallarını belgeler. Bu tür bir belirtim, birçok durumda, bir türden okuma depolamanın ve bir başkası gibi yazmanın etkisinin, derleyicilerin, nesnelerin işaretçiler kullanılarak okunması ve yazılması haricinde, tanımlanmış depolama biçimini kullanmak zorunda olmadığını söyleyen kuralların yokluğunda ne olacağını ima eder. bir karakter türünün.
supercat

-3

Bunu bir örnekle iyi açıklarım.
aşağıdaki birliğe sahip olduğumuzu varsayalım:

union A{
   int x;
   short y[2];
};

Bunun sizeof(int)4 verdiğini ve bunun sizeof(short)da 2
verdiğini varsayıyorum, union A a = {10}bu kadar iyi yazarsanız , A tipi yeni bir varyasyon yaratın, içine 10 değerini koyun.

hafızanız şöyle görünmeli: (tüm sendika üyelerinin aynı yeri aldığını unutmayın)

       | x |
       | y [0] | y [1] |
       -----------------------------------------
   a-> | 0000 0000 | 0000 0000 | 0000 0000 | 0000 1010 |
       -----------------------------------------

görebileceğiniz gibi, ax'in değeri 10, ay 1'in değeri 10 ve ay [0] 'ın değeri 0'dır.

şimdi, bunu yaparsam ne olur?

a.y[0] = 37;

hafızamız şöyle görünecek:

       | x |
       | y [0] | y [1] |
       -----------------------------------------
   a-> | 0000 0000 | 0010 0101 | 0000 0000 | 0000 1010 |
       -----------------------------------------

bu ax değerini 2424842'ye (ondalık olarak) çevirecektir.

şimdi, sendikanızda bir kayan nokta varsa veya iki katına çıkarsa, tam sayıları kaydetme şekliniz nedeniyle hafıza haritanız daha karışık olabilir. buradan daha fazla bilgi edinebilirsiniz .


18
:) Sorduğum bu değil. İçeride ne olduğunu biliyorum. Çalıştığını biliyorum. Standartta olup olmadığını sordum.
Luchian Grigore
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.