Boş bir örnekte bir üye işlevi çağırmak ne zaman tanımsız davranışla sonuçlanır?


120

Aşağıdaki kodu göz önünde bulundurun:

#include <iostream>

struct foo
{
    // (a):
    void bar() { std::cout << "gman was here" << std::endl; }

    // (b):
    void baz() { x = 5; }

    int x;
};

int main()
{
    foo* f = 0;

    f->bar(); // (a)
    f->baz(); // (b)
}

Biz bekliyoruz (b)hiçbir karşılık gelen üyesi olduğundan, çökmesine xnull işaretçi. Pratikte, işaretçi asla kullanılmadığı (a)için çökmez this.

Çünkü (b)dereferences thisişaretçi ( (*this).x = 5;) ve thisboş bir boş dereferencing zaman tanımsız davranış olduğu söylenir olarak, program tanımsız davranış girer.

(a)Tanımlanmamış davranışla sonuçlanır mı ? Ya her iki fonksiyon (ve x) statikse?


Her iki fonksiyon da statikse , x nasıl baz içinde belirtilebilir ? (x statik olmayan bir üye değişkendir)
legends2k

4
@ legends2k: Pretend xde statik yapıldı. :)
GManNickG

Elbette, ancak (a) durumu için her durumda aynı şekilde çalışır, yani işlev çağrılır. Bununla birlikte, işaretçinin değerini 0'dan 1'e değiştirirsek (örneğin, reinterpret_cast aracılığıyla), neredeyse her zaman çöker. 0 ve dolayısıyla NULL değer tahsisi, a durumunda olduğu gibi, derleyici için özel bir şeyi temsil ediyor mu? Neden her zaman kendisine tahsis edilen başka bir değerle çöküyor?
Siddharth Shankaran

5
İlginç: C ++ 'ın bir sonraki revizyonuna gelin, artık işaretçilerin referanslarının kaldırılması olmayacak. Şimdi işaretçiler aracılığıyla dolaylı yönlendirme yapacağız . Daha fazla bilgi edinmek için, lütfen bu bağlantı üzerinden indirimi
James McNellis

3
Bir boş göstericide bir üye işlevi çağırmak her zaman tanımsız bir davranıştır. Sadece kodunuza bakarak, tanımlanmamış davranışın yavaşça boynumdan yukarı doğru ilerlediğini hissedebiliyorum!
fredoverflow

Yanıtlar:


113

Her ikisi de (a)ve (b)tanımlanmamış davranışla sonuçlanır. Bir üye işlevi boş gösterici aracılığıyla çağırmak her zaman tanımsız bir davranıştır. İşlev statikse teknik olarak da tanımsızdır, ancak bazı anlaşmazlıklar vardır.


Anlaşılması gereken ilk şey, boş göstericiye başvurmanın neden tanımsız bir davranış olduğudur. C ++ 03'te, aslında burada biraz belirsizlik var.

Her ne kadar "tanımsız davranış bir boş gösterici sonuçlarını dereferencing" §1.9 / 4 ve §8.3.2 / 4'e hem notlarda belirtilen, açıkça belirtilmediği olmamıştı. (Notlar normatif değildir.)

Ancak, bunu §3.10 / 2'den çıkarmaya çalışabilirsiniz:

Bir değer, bir nesne veya işlevi ifade eder.

Başvuruyu kaldırırken, sonuç bir değerdir. Boş gösterici bir nesneye işaret etmez , bu nedenle lvalue kullandığımızda tanımsız davranışa sahip oluruz. Sorun, önceki cümlenin asla belirtilmemesidir, öyleyse ldeğerini "kullanmak" ne anlama gelir? Sadece onu üretmek mi, yoksa daha resmi anlamda l-değeri-değer dönüşümü gerçekleştirmek için kullanmak mı?

Ne olursa olsun, kesinlikle bir değer değerine dönüştürülemez (§4.1 / 1):

Ldeğerin başvurduğu nesne T türünde bir nesne değilse ve T'den türetilmiş türde bir nesne değilse veya nesne başlatılmamışsa, bu dönüştürmeyi gerektiren bir programın tanımsız davranışı vardır.

Burada kesinlikle tanımlanmamış bir davranış.

Belirsizlik, tanımlanmamış davranış olup olmadığından kaynaklanır, ancak geçersiz bir göstericiden gelen değeri kullanmaz (yani, bir l değeri alır, ancak onu bir r değerine dönüştürmez). Değilse int *i = 0; *i; &(*i);, iyi tanımlanmıştır. Bu aktif bir sorundur .

Bu nedenle, katı bir "boş göstericiye başvurma, tanımsız davranış elde etme" görünümü ve zayıf bir "referans alınmayan boş gösterici kullanma, tanımsız davranış elde etme" görünümüne sahibiz.

Şimdi soruyu ele alıyoruz.


Evet, (a)tanımlanmamış davranışla sonuçlanır. Aslında, thisboş ise, fonksiyonun içeriğinden bağımsız olarak sonuç tanımsızdır.

Bu, §5.2.5 / 3'ten itibaren:

Eğer E1türü vardır “sınıfı X işaretçiyi,” daha sonra ifade E1->E2eşdeğer forma dönüştürülür(*(E1)).E2;

*(E1)katı bir yorumlama ile tanımlanmamış davranışla sonuçlanacak ve .E2bunu bir değer değerine dönüştürerek zayıf yorumlama için tanımsız davranış haline getirecektir.

Ayrıca, doğrudan (§9.3.1 / 1) adresinden tanımlanmamış davranış olduğunu da takip eder:

Bir X sınıfının statik olmayan üye işlevi, X türünde olmayan veya X'ten türetilmiş bir türden bir nesne için çağrılırsa, davranış tanımsızdır.


Statik fonksiyonlarda, katı ve zayıf yorum farkı yaratır. Kesin olarak, tanımsızdır:

Statik bir üyeye, sınıf üyesi erişim sözdizimi kullanılarak başvurulabilir, bu durumda nesne ifadesi değerlendirilir.

Yani, sanki durağan değilmiş gibi değerlendirilir ve bir kez daha boş göstericiye başvururuz (*(E1)).E2.

Ancak, E1statik üye işlev çağrısında kullanılmadığından, zayıf yorumlama kullanırsak çağrı iyi tanımlanmıştır. *(E1)bir ldeğerle sonuçlanır, statik fonksiyon çözümlenir, *(E1)atılır ve fonksiyon çağrılır. Değer-değer dönüşümü yoktur, dolayısıyla tanımsız davranış yoktur.

C ++ 0x'de, n3126'dan itibaren belirsizlik kalır. Şimdilik güvende olun: katı yorumu kullanın.


5
+1. Bilgisizliğin devamında, "zayıf tanım" altında, durağan olmayan üye işlevi "X tipi olmayan bir nesne için" çağrılmamıştır. Hiç bir nesne olmayan bir değer için çağrıldı. Dolayısıyla, önerilen çözüm, alıntı yaptığınız cümleye "veya ldeğer boş bir değer ise" metnini ekler.
Steve Jessop

Biraz açıklayabilir misin? Özellikle "kapalı sorun" ve "aktif sorun" bağlantılarınızda sorun numaraları nelerdir? Ayrıca, bu kapalı bir sorunsa, statik işlevler için evet / hayır yanıtı tam olarak nedir? Cevabınızı anlamaya çalışırken son adımı kaçırdığımı hissediyorum.
Brooks Musa

4
CWG hatası 315'in "kapalı sorunlar" sayfasındaki varlığının ima ettiği kadar "kapalı" olduğunu düşünmüyorum. Mantık, buna izin verilmesi gerektiğini söyler çünkü " ldeğer bir r değerine dönüştürülmedikçe boş *polduğunda bir hata değildir p." Bununla birlikte, bu, CWG hatası 232 için önerilen çözümün bir parçası olan , ancak benimsenmemiş olan "boş bir değer" kavramına dayanmaktadır . Dolayısıyla, hem C ++ 03 hem de C ++ 0x diliyle, boş göstericiye başvurunun kaldırılması, lvalue-rvalue dönüşümü olmasa bile hala tanımsızdır.
James McNellis

1
@JamesMcNellis: Anladığım kadarıyla, pokunduğunda bazı eylemleri tetikleyecek, ancak bildirilmemiş bir donanım adresi olsaydı volatile, ifadenin bu adresi gerçekten okumasına *p;gerek olmazdı , ancak izin verilirdi; ancak açıklamanın &(*p);bunu yapması yasaktır. Eğer *pvardı volatile, okuma gerekecektir. Her iki durumda da, işaretçi geçersizse, ilk ifadenin nasıl Tanımsız Davranış olmayacağını göremiyorum, ancak ikinci ifadenin neden olacağını da anlayamıyorum.
supercat

1
".E2 bunu bir r değerine dönüştürür," - Ah, hayır öyle değil
MM

30

Açıkçası oluyor demektir tanımsız tanımlanmamış , ama bazen öngörülebilir olabilir. Kesinlikle garanti edilmediğinden, sağlamak üzere olduğum bilgiler çalışma kodu için asla güvenilmemelidir, ancak hata ayıklama sırasında faydalı olabilir.

Bir nesne işaretçisi üzerinde bir işlev çağırmanın işaretçiye referans vermeyeceğini ve UB'ye neden olacağını düşünebilirsiniz. Pratikte, eğer işlev sanal değilse, derleyici bunu, göstericiyi ilk parametre olarak geçiren, bir düz işlev çağrısına dönüştürür , bu , başvuruyu atlayarak ve çağrılan üye işlevi için bir saatli bomba oluşturur. Üye işlevi herhangi bir üye değişkene veya sanal işleve başvurmuyorsa, aslında hatasız olarak başarılı olabilir. Başarının "tanımlanmamış" evrenine düştüğünü unutmayın!

Microsoft'un MFC işlevi GetSafeHwnd aslında bu davranışa dayanır. Ne içtiklerini bilmiyorum.

Sanal bir işlevi çağırıyorsanız, vtable'a ulaşmak için işaretçinin referansının kaldırılması gerekir ve kesinlikle UB alacaksınız (muhtemelen bir çökme, ancak hiçbir garantinin olmadığını unutmayın).


1
GetSafeHwnd önce! Bu denetimi yapar ve doğruysa NULL döndürür. Ardından bir SEH çerçevesi başlar ve işaretçiyi kaldırır. Bellek Erişim İhlali varsa (0xc0000005) bu yakalanır ve arayan kişiye NULL döndürülür :) Aksi takdirde HWND döndürülür.
Петър Петров

@ ПетърПетров Koda baktığımdan bu yana epeyce yıl geçti GetSafeHwnd, o zamandan beri onu geliştirmiş olmaları mümkün. Ve derleyici çalışmaları hakkında içeriden bilgiye sahip olduklarını unutmayın!
Mark Ransom

Aynı etkiye sahip olası bir örnek uygulama olduğunu söylüyorum, gerçekten yaptığı şey bir hata ayıklayıcı kullanarak tersine mühendislik uygulamaktır :)
Петър Петров

1
"derleyici çalışmaları hakkında içeriden bilgiye sahipler!" - g ++ 'nın Windows API'yi çağıran kodu derlemesine izin vermeye çalışan MinGW gibi projeler için ebedi sorunun nedeni
MM

@MM Sanırım hepimiz bunun haksız olduğu konusunda hemfikiriz. Ve bu nedenle, uyumlulukla ilgili bir yasanın da öyle kalmasını biraz yasa dışı kıldığını düşünüyorum.
v.oddou
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.