Bir C ++ programcısının bilmesi gereken yaygın tanımsız davranışlar nelerdir? [kapalı]


201

Bir C ++ programcısının bilmesi gereken yaygın tanımsız davranışlar nelerdir?

De ki:

a[i] = i++;


3
Emin misiniz. İyi tanımlanmış görünüyor.
Martin York

17
6.2.2 C ++ programlama dilinde Değerlendirme Sırası [expr.evaluation] öyle söylüyor. Başka bir referansım yok
yesraaj

4
O sağ .. sadece C ++ Programlama Dili 6.2.2 baktı ve v [i] = i ++ undefined diyor
dancavallaro

4
Ben derlerim çünkü comiler yapmak i ++ önce veya sonra v [i] bellek konumunu hesaplamak yürütmek. eminim, her zaman orada görevlendirileceğim. ancak işlem sırasına bağlı olarak v [i] veya v [i + 1] 'e yazabilir ..
Evan Teran

2
C ++ Programlama Dili, "Bir ifade içindeki alt ifadelerin işlem sırası tanımsızdır. Özellikle, ifadenin soldan sağa değerlendirildiğini varsayamazsınız."
dancavallaro

Yanıtlar:


233

Işaretçi

  • NULLİşaretçinin kaydının silinmesi
  • Sıfır boyutta "yeni" bir ayırma ile döndürülen bir işaretçinin kaydı silme
  • Ömrü sona eren nesnelere işaretçi kullanma (örneğin, ayrılmış nesneleri veya silinmiş nesneleri yığınlama)
  • Henüz tamamen başlatılmamış bir işaretçiyi kaydı silme
  • Bir dizinin sınırları dışında (üstünde veya altında) sonuç veren işaretçi aritmetiği gerçekleştirmek.
  • İşaretçiyi dizinin sonunun ötesinde bir yerde kaydı silme.
  • İşaretçileri uyumsuz türdeki nesnelere dönüştürme
  • memcpyÇakışan arabellekleri kopyalamak için kullanma .

Arabellek taşmaları

  • Negatif olan veya bir nesnenin boyutunun ötesinde (yığın / yığın taşması) bir nesnede veya dizide okuma veya yazma

Tamsayı Taşmaları

  • İmzalı tamsayı taşması
  • Matematiksel olarak tanımlanmamış bir ifadeyi değerlendirme
  • Negatif bir miktara göre sola kaydırma değerleri (negatif miktarlara göre sağa kaydırma uygulamaları tanımlanmıştır)
  • Değerleri sayıdaki bit sayısından büyük veya ona eşit bir miktarda kaydırma (ör int64_t i = 1; i <<= 72. Tanımsız)

Türleri, Döküm ve İnş

  • Hedef türle temsil edilemeyen bir değere sayısal bir değer atama (doğrudan veya static_cast aracılığıyla)
  • Kesin olarak atanmadan önce otomatik bir değişken kullanma (ör. int i; i++; cout << i;)
  • Bir sinyalin alındığı volatileveya sig_atomic_talındığı herhangi bir türde nesnenin değerini kullanma
  • Bir dize hazır bilgisini veya başka herhangi bir const nesnesini kullanım ömrü boyunca değiştirmeye çalışmak
  • Ön işleme sırasında dar bir alanı geniş bir dize değişmeziyle birleştirmek

İşlev ve Şablon

  • Değer döndürme işlevinden değer döndürmeme (doğrudan veya bir try-block'tan akarak)
  • Aynı varlık için birden fazla farklı tanım (sınıf, şablon, numaralandırma, satır içi işlev, statik üye işlevi, vb.)
  • Şablonların somutlaştırılmasında sonsuz özyineleme
  • Bir işlevi farklı parametreler kullanarak veya işlevin kullanım olarak tanımlandığı parametrelerle bağlantıyla çağırma.

OOP

  • Statik depolama süresine sahip nesnelerin basamaklı yıkımları
  • Kısmen çakışan nesnelere atamanın sonucu
  • Statik nesnelerinin başlatılması sırasında bir işlevi tekrar tekrar girme
  • Bir nesnenin yapıcısından veya yıkıcısından saf sanal işlevlerine sanal işlev çağrıları yapma
  • İnşa edilmemiş veya zaten tahrip edilmiş nesnelerin statik olmayan üyelerine atıfta bulunmak

Kaynak dosya ve Önişleme

  • Yeni satırla bitmeyen veya ters eğik çizgi ile biten boş olmayan bir kaynak dosya (C ++ 11'den önce)
  • Bir ters eğik çizgi ve ardından bir karakter veya dize sabitinde belirtilen çıkış kodlarının parçası olmayan bir karakter (bu, C ++ 11'de uygulama tarafından tanımlanır).
  • Uygulama sınırlarını aşma (iç içe blok sayısı, bir programdaki işlev sayısı, kullanılabilir yığın alanı ...)
  • İle temsil edilemeyen önişlemci sayısal değerleri long int
  • İşlev benzeri bir makro tanımının sol tarafındaki önişleme yönergesi
  • Bir #ififadede tanımlanmış belirteci dinamik olarak oluşturma

Sınıflandırılacak

  • Statik depolama süresi olan bir programın imhası sırasında çıkış çağrısı

Hm ... NaN (x / 0) ve Infinity (0/0) IEE 754 kapsamındaydı, eğer C ++ daha sonra tasarlandıysa, x / 0'ı tanımsız olarak neden kaydediyor?
yeni123456

Yanıt: "Ters eğik çizgi ve ardından bir karakter veya dize sabitinde belirtilen çıkış kodlarının parçası olmayan bir karakter." Bu C89'daki (§3.1.3.4) ve C ++ 03 (C89'u içeren) UB'dir, ancak C99'da değildir. C99, "sonucun bir jeton olmadığı ve bir teşhisin gerekli olduğunu" söylüyor (§6.4.4.4). Muhtemelen C ++ 0x (C89'u içeren) aynı olacaktır.
Adam Rosenfield

1
C99 standardı, Ek J.2'de tanımlanmamış davranışların bir listesine sahiptir. Bu listeyi C ++ 'a uyarlamak biraz zaman alabilir. C99 yan tümceleri yerine doğru C ++ yan tümcelerine başvuruları değiştirmek, ilgisiz herhangi bir şeyi kaldırmak ve ayrıca tüm bu şeylerin C ve C ++ 'da gerçekten tanımsız olup olmadığını kontrol etmek zorunda kalacaksınız.
Steve Jessop

1
@ new123456 - tüm kayan nokta birimleri IEE754 uyumlu değildir. C ++, IEE754 uyumluluğunu gerektiriyorsa, derleyicilerin RHS'nin sıfır olduğu durumu açık bir denetim yoluyla test etmesi ve işlemesi gerekir. Davranışı tanımsız hale getirerek, "IEE754 olmayan bir FPU kullanırsanız, IEEE754 FPU davranışı elde edemezsiniz" diyerek bu ek yükü önleyebilir.
SecurityMatt

1
"Sonucu karşılık gelen türler aralığında olmayan bir ifadeyi değerlendirme" .... tamsayı taşması UNSIGNED integral tipleri için iyi tanımlanmış, sadece imzalı değil.
nacitar sevaht

31

İşlev parametrelerinin değerlendirilme sırası belirtilmemiş davranıştır . (Bu, programınızın tanımlanamayan davranışların aksine çökmesini, patlamasını veya pizza siparişi vermez .)

Tek gereksinim, işlev çağrılmadan önce tüm parametrelerin tam olarak değerlendirilmesi gerektiğidir.


Bu:

// The simple obvious one.
callFunc(getA(),getB());

Buna eşdeğer olabilir:

int a = getA();
int b = getB();
callFunc(a,b);

Veya bu:

int b = getB();
int a = getA();
callFunc(a,b);

İkisinden biri olabilir; derleyiciye kalmış. Sonuç, yan etkilere bağlı olarak önemli olabilir.


23
Sipariş tanımlanmamış, tanımsız.
Rob Kennedy

1
Bundan nefret ediyorum :) Bu vakalardan birini takip ettikten sonra bir gün iş kaybettim ... her neyse dersimi öğrendim ve neyse ki tekrar düşmedim
Robert Gould

2
@Rob: Sizinle buradaki anlam değişikliği hakkında tartışırdım, ancak standartlar komitesinin bu iki kelimenin tam tanımı konusunda çok seçici olduğunu biliyorum. Bu yüzden değiştireceğim :-)
Martin York

2
Bu konuda şanslıyım. Üniversitedeyken ısırıldım ve bir göz attı ve yaklaşık 5 saniye içinde sorunumu anlattı bir profesör vardı. Aksi takdirde hata ayıklama ne kadar zaman boşa olurdu söyleme.
Bill the Lizard

27

Derleyici, bir ifadenin değerlendirme bölümlerini yeniden sıralamakta serbesttir (anlamın değişmediği varsayılarak).

Orijinal sorudan:

a[i] = i++;

// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)

// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:

int rhs  = i++;
int lhs& = a[i];
lhs = rhs;

// or
int lhs& = a[i];
int rhs  = i++;
lhs = rhs;

Çift Kontrollü kilitleme. Yapması kolay bir hata.

A* a = new A("plop");

// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'

// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.

// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        a = new A("Plop");  // (Point A).
    }
}
a->doStuff();

// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
//           Remember (c) has been done thus 'a' is not NULL.
//           But the memory has not been initialized.
//           Thread 2 now executes doStuff() on an uninitialized variable.

// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        A* tmp = new A("Plop");  // (Point A).
        a = tmp;
    }
}
a->doStuff();

// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.

sıralama noktası ile ne demek?
yesraaj


1
Ooh ... özellikle Java'da önerilen yapıyı gördüğümden beri kötü
Tom

Bazı derleyicilerin bu durumda davranışı tanımladığını unutmayın. VC ++ 2005+ işletim sistemlerinde, örneğin, a geçici ise, çift bellek kilitlemenin çalışması için yönergelerin yeniden sıralanmasını önlemek için gereken bellek tamponları ayarlanır.
Eclipse

Martin York: <i> // (c) 'nin (a) ve (b)' den sonra olacağı garanti ediliyor, değil mi? Kuşkusuz bu belirli örnekte, 'i' bir donanım kaydına eşlenen değişken bir değişken olsaydı ve bir [i] ('i' nin eski değeri) buna taklit edilmiş olsa bile önemli olabilecek tek senaryo olurdu, ancak artışın bir dizi noktasından önce olacağını garanti eder misiniz?
supercat

5

Benim favorim "Şablonların somutlaştırılmasında sonsuz özyineleme" dir çünkü tanımlanmamış davranışın derleme zamanında gerçekleştiği tek şey olduğuna inanıyorum.


Bunu daha önce yaptım, ama nasıl tanımlandığını göremiyorum. Sonradan düşünülerek sonsuz bir özyineleme yaptığınız oldukça açıktır.
Robert Gould

Sorun, derleyicinin kodunuzu inceleyememesi ve sonsuz yinelemeden muzdarip olup olmayacağına kesin olarak karar verememesi. Bu durma sorununun bir örneğidir. Bkz. Stackoverflow.com/questions/235984/…
Daniel Earwicker

Evet, bu kesinlikle durma sorunu
Robert Gould

çok az bellek nedeniyle takas nedeniyle sistemimi çökertti.
Johannes Schaub - litb

2
Bir int'e uymayan önişlemci sabitleri de derleme süresidir.
Joshua

5

constKullanarak sıyırma işleminden sonra bir sabite atama const_cast<>:

const int i = 10; 
int *p =  const_cast<int*>( &i );
*p = 1234; //Undefined

5

Tanımlanmamış davranışların yanı sıra , aynı derecede kötü uygulama tanımlı davranış da vardır .

Tanımsız davranış, bir program sonucu standart tarafından belirtilmeyen bir şey yaptığında oluşur.

Uygulama tanımlı davranış, sonucu standart tarafından tanımlanmayan, ancak uygulamanın belgelendirilmesi gereken bir programın eylemidir. Yığın Taşması sorusundan bir örnek "Çokbaytlı karakter değişmezleri", bunu derleyemeyen bir C derleyicisi var mı? .

Uygulama tanımlı davranış yalnızca taşımaya başladığınızda sizi ısırır (ancak derleyicinin yeni sürümüne yükseltmek de taşır!)


4

Değişkenler bir ifadede yalnızca bir kez güncellenebilir (teknik olarak sıra noktaları arasında bir kez).

int i =1;
i = ++i;

// Undefined. Assignment to 'i' twice in the same expression.

Infact en azından bir kez, iki dizi nokta arasında.
Prasoon Saurav

2
@Prasoon: Sanırım demek istediniz: en fazla iki dizi noktası arasında. :-)
Nawaz

3

Çeşitli çevresel sınırların temel anlayışı. Tam liste C spesifikasyonunun 5.2.4.1 bölümünde bulunmaktadır. Burda biraz var;

  • Bir fonksiyon tanımında 127 parametre
  • Bir işlev çağrısında 127 bağımsız değişken
  • Bir makro tanımında 127 parametre
  • Bir makro çağrısında 127 bağımsız değişken
  • Mantıksal bir kaynak satırında 4095 karakter
  • Bir karakter dizesi değişmezinde veya geniş dize değişmezinde (birleştirme işleminden sonra) 4095 karakter
  • Bir nesnede 65535 bayt (yalnızca barındırılan bir ortamda)
  • #İncluded dosyalar için 15 düzey
  • Bir anahtar ifadesi için 1023 vaka etiketleri (anonsested anahtar ifadeleri hariç)

Aslında bir anahtar ifadesi için 1023 vaka etiketleri sınırında biraz şaşırdım, üretilen kod / lex / parsers için oldukça kolay bir şekilde aşıldığını görebilirsiniz.

Bu sınırlar aşılırsa, tanımlanmamış davranışınız (çökmeler, güvenlik kusurları, vb.) Vardır.

Doğru, bunun C belirtiminden olduğunu biliyorum, ancak C ++ bu temel destekleri paylaşıyor.


9
Bu sınırlara ulaşırsanız, tanımlanmamış davranıştan daha fazla sorununuz vardır.
yeni123456

STD :: vector
Demi

2

memcpyÇakışan bellek bölgeleri arasında kopyalama yapmak için kullanılır . Örneğin:

char a[256] = {};
memcpy(a, a, sizeof(a));

Davranış, C ++ 03 Standardı tarafından toplanan C Standardına göre tanımlanmamıştır.

7.21.2.1 Memcpy işlevi

özet

1 / #incid void * memcpy (void * kısıtlama s1, sabit boşluk * kısıtlama s2, boyut_t n);

Açıklama

2 / Memcpy işlevi s2 ile gösterilen nesneden n1 ile gösterilen nesneye n karakter kopyalar. Kopyalama çakışan nesneler arasında gerçekleşirse, davranış tanımsızdır. 3 döndürür Memcpy işlevi s1 değerini döndürür.

7.21.2.2 Memmove işlevi

özet

1 # void * memmove (void * s1, const void * s2, size_t n) ekleyin;

Açıklama

2 Memmove işlevi, s2 ile gösterilen nesneden n1 ile gösterilen nesneye n karakter kopyalar. Kopyalama, s2 ile gösterilen nesneden gelen n karakter, ilk olarak s1 ve s2 ile gösterilen nesnelerle çakışmayan geçici bir n karakter dizisine kopyalanır ve daha sonra geçici diziden n karakter kopyalanır nesne s1 ile işaret etti. İadeler

3 Memmove işlevi s1 değerini döndürür.


2

C ++ 'ın bir boyutu garanti ettiği tek türdür char. Ve boyut 1'dir. Diğer tüm türlerin boyutu platforma bağlıdır.


<cstdint> bunun için değil mi? Uint16_6 et cetera gibi türleri tanımlar.
Jasper Bekkers

Evet, ama çoğu tipin boyutu, uzun diyelim, iyi tanımlanmamış.
JaredPar

Ayrıca cstdint henüz mevcut c ++ standardının bir parçası değil. şu anda taşınabilir bir çözüm için boost / stdint.hpp adresine bakın.
Evan Teran

Bu tanımsız bir davranış değil. Standart, uyumlu platformun, bunları tanımlayan standarttan ziyade boyutları tanımladığını söylüyor.
Daniel Earwicker

1
@JaredPar: Çok sayıda konuşma dizisi içeren karmaşık bir yazı, bu yüzden hepsini burada topladım . Alt satır şu şekildedir: "5. İkili olarak -2147483647 ve +2147483647'yi temsil etmek için 32 bit gerekir."
John Dibling

2

Farklı derleme birimlerindeki ad alanı düzeyindeki nesneler, başlatma sırası hiçbir zaman birbirine bağlı olmamalıdır, çünkü başlatma sıraları tanımlanmamıştır.

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.