İşaretçiler neden varsayılan olarak NULL ile başlatılmıyor?


118

Birisi lütfen işaretçilerin neden başlatılmadığını açıklayabilir NULLmi?
Misal:

  void test(){
     char *buf;
     if (!buf)
        // whatever
  }

Program if'in içine bufgirmez çünkü boş değildir.

Neden, hangi durumda, özellikle hafızadaki çöpleri ele alan işaretçilerle birlikte bir değişkene ihtiyacımız var?


13
Eh, çünkü temel türler başlatılmamış olarak bırakılmıştır. Öyleyse "gerçek" sorunuzu varsayıyorum: neden temel türler başlatılmıyor?
GManNickG

11
"program if'in içine girmez çünkü buf boş değildir". Bu doğru değil. Eğer tampon bilmiyorum yana olduğunu , bunun ne bilemeyiz değil .
Drew Dormann

Java gibi bir şeyin aksine, C ++ geliştiriciye bir ton daha fazla sorumluluk verir.
Rishi

tamsayılar, işaretçiler, () yapıcı kullanırsanız varsayılan olarak 0 olur.
Erik Aronesty

C ++ kullanan birinin ne yaptığını bildiği varsayımından dolayı, ayrıca akıllı bir işaretçi üzerinden ham işaretçi kullanan biri ne yaptığını bilir (hatta daha fazlasını yapar)!
Lofty Lion

Yanıtlar:


161

Hepimiz işaretçinin (ve diğer POD türlerinin) başlatılması gerektiğinin farkındayız.
Soru daha sonra 'onları kimin başlatması gerektiği' olur.

Temelde iki yöntem var:

  • Derleyici onları başlatır.
  • Geliştirici bunları başlatır.

Derleyicinin, geliştirici tarafından açıkça başlatılmamış herhangi bir değişkeni başlattığını varsayalım. Sonra, değişkeni başlatmanın önemsiz olmadığı ve geliştiricinin bunu bildirim noktasında yapmamasının nedeni, bir işlem gerçekleştirmesi ve sonra atama yapması gerektiğiydi.

Şimdi, derleyicinin, değişkeni NULL olarak başlatan koda fazladan bir talimat eklediğini ve daha sonra doğru başlatmayı yapmak için geliştirici kodunun eklendiğini görüyoruz. Veya diğer koşullar altında değişken potansiyel olarak asla kullanılmaz. Pek çok C ++ geliştiricisi, her iki koşulda da bu ekstra talimatın bedeli olarak faul çığlık atabilir.

Bu sadece zamanla ilgili değil. Ama aynı zamanda boşluk. Her iki kaynağın da premium olduğu ve geliştiricilerin de pes etmek istemediği birçok ortam var.

AMA : Başlatmaya zorlamanın etkisini simüle edebilirsiniz. Çoğu derleyici, sizi başlatılmamış değişkenler hakkında uyaracaktır. Bu yüzden uyarı seviyemi her zaman mümkün olan en yüksek seviyeye çeviriyorum. Sonra derleyiciye tüm uyarıları hata olarak ele almasını söyleyin. Bu koşullar altında çoğu derleyici, başlatılmamış ancak kullanılan değişkenler için bir hata oluşturacak ve bu nedenle kodun oluşturulmasını engelleyecektir.


5
Bob Tabor, "Çok fazla insan başlatma için yeterince düşünmedi!" Tüm değişkenleri otomatik olarak başlatmak için 'dostça', ancak zaman alıyor ve yavaş programlar 'dostça değil'. Malloc'un rastgele bulduğu çöpü gösteren bir hesap tablosu veya düzenleyiciler kabul edilemez olacaktır. Eğitimli kullanıcılar için keskin bir araç olan C (kötüye kullanılırsa tehlikeli), otomatik değişkenleri başlatmak için zaman ayırmamalıdır. Değişkenleri başlatmak için bir eğitim çarkı makrosu olabilir, ancak çoğu kişi ayağa kalkmanın, dikkatli olmanın ve biraz kanamanın daha iyi olduğunu düşünüyor. Bir tutam, pratik yaptığınız gibi çalışırsınız. Öyleyse sahip olduğunuz gibi pratik yapın.
Bill IV

2
Sadece birisinin tüm başlatmalarını düzelterek kaç hatadan kaçınılacağına şaşıracaksınız. Derleyici uyarıları olmasaydı bu sıkıcı bir iş olurdu.
Jonathan Henson

4
@Loki, senin fikrini takip etmekte zorlanıyorum. Ben sadece cevabınızı faydalı bulmaya çalışıyordum, umarım bunu anlamışsınızdır. Eğer değilse üzgünüm.
Jonathan Henson

3
İşaretçi ilk önce NULL olarak ayarlanmışsa ve sonra herhangi bir değere ayarlanmışsa, derleyici bunu algılayabilmeli ve ilk NULL başlatmayı optimize edebilmelidir, değil mi?
Korchkidu

1
@Korchkidu: Bazen. Yine de en büyük sorunlardan biri, varsayılanın kullanımınız için mükemmel olmadığını bilemeyeceği için, başlatmayı yapmayı unuttuğunuz konusunda sizi uyarmasının bir yolu olmamasıdır.
Tekilleştirici

41

Bjarne Stroustrup'tan TC ++ PL'de alıntı yapmak (Özel Sürüm s.22):

Bir özelliğin uygulanması, onu gerektirmeyen programlara önemli genel giderler yüklememelidir.


ve seçeneği de vermeyin. Görünüşe göre
Jonathan

8
@ Jonathan hiçbir şey, işaretçiyi sıfıra veya C ++ 'da standart olduğu gibi 0'a başlatmanızı engellemez.
stefanB

8
Evet, ancak Stroustrup, göstericiyi sıfır olarak başlatarak, varsayılan sözdizimini performans yerine program doğruluğunu tercih edebilir ve programcının açıkça göstericinin başlatılmamış olmasını istemesini sağlayabilirdi. Sonuçta, çoğu insan doğru-ama-yavaş'ı hızlı-ama-yanlış yerine tercih eder, çünkü küçük bir miktarda kodu optimize etmenin tüm programdaki hataları düzeltmekten genellikle daha kolaydır. Özellikle de çoğu iyi bir derleyici tarafından yapılabildiğinde.
Robert Tuck

1
Uyumluluğu bozmaz. Fikir, "int * x = __uninitialized" ile bağlantılı olarak düşünülmüştür - varsayılan olarak güvenlik, amaca göre hız.
MSalters

4
Ne yaptığını seviyorum D. Başlatma istemiyorsanız, bu sözdizimini float f = void;veya int* ptr = void;. Şimdi varsayılan olarak başlatıldı, ancak gerçekten ihtiyacınız varsa, derleyicinin bunu yapmasını durdurabilirsiniz.
deft_code

23

Çünkü yeniden başlatma zaman alır. Ve C ++ 'da, herhangi bir değişkenle yapmanız gereken ilk şey, onu açıkça başlatmaktır:

int * p = & some_int;

veya:

int * p = 0;

veya:

class A {
   public:
     A() : p( 0 ) {}  // initialise via constructor
   private:
     int * p;
};

1
k, eğer başlatma zaman alırsa ve ben hala istiyorum, işaretçilerimi manuel olarak ayarlamadan nasıl boşa getirebilirim? Bakın, bunu düzeltmek istemediğim için değil, çünkü adreslerinde çöp olan tek parça işaretçileri asla kullanmayacağım gibi görünüyor
Jonathan

1
Sınıf üyelerini sınıfın yapıcısında başlatırsınız - C ++ böyle çalışır.

3
@Jonathan: ama null da bir çöp. Boş gösterici ile yararlı hiçbir şey yapamazsınız. Birinin başvurusunu iptal etmek, aynı derecede bir hatadır. Boş değerlere değil, uygun değerlere sahip işaretçiler oluşturun.
DrPizza

2
Apointer'ı Nnull olarak başlatmak mantıklı bir şey olabilir ve boş işaretçiler üzerinde gerçekleştirebileceğiniz birkaç işlem vardır - onları test edebilir ve üzerlerinde silme diyebilirsiniz.

4
Açıkça başlatmadan bir işaretçiyi asla kullanmayacaksanız, ona bir değer vermeden önce ne içerdiği önemli değildir ve C ve C ++ ilkesine göre yalnızca kullandığınız kadar ödeme yapılmaz. otomatik olarak. Kabul edilebilir bir varsayılan değer varsa (genellikle boş gösterici), onu başlatmalısınız. Seçiminize göre başlatabilir veya sıfırlanmamış bırakabilirsiniz.
David Thornley

20

Çünkü C ++ 'ın sloganlarından biri:


Kullanmadığın şey için ödeme yapmazsın


Tam da bu nedenle, operator[]bir vectordizin örneğin sınırlar dışında ise sınıfta denetlemez.


12

Tarihsel nedenlerden ötürü, esas olarak C'de böyle yapıldığı için, C'de neden böyle yapıldığı başka bir sorudur, ancak bence bu tasarım kararına bir şekilde sıfır genel gider prensibi dahil edilmiştir.


Sanırım C, belleğe kolay erişime sahip daha düşük seviyeli bir dil olarak kabul edilir (diğer adıyla işaretçiler), bu yüzden size istediğinizi yapma özgürlüğü verir ve her şeyi başlatarak ek yük getirmez. BTW Bunun platforma bağlı olduğunu düşünüyorum çünkü kullanmadan önce tüm belleğini 0 olarak
başlatan

8

Ayrıca, onu ne zaman uçurduğunuza dair bir uyarımız var: "muhtemelen bir değer atanmadan önce kullanılır" veya derleyicinize bağlı olarak benzer bir kelime.

Uyarılarla derlersiniz, değil mi?


Ve izleyen derleyicilerin hatalı olabileceğinin kabulü olarak mümkündür .
Tekilleştirici

6

Bir değişkenin başlatılmamış olmasının anlamlı olduğu ve varsayılan başlatmanın küçük bir maliyeti olduğu, kaybolan birkaç durum vardır, öyleyse neden yapalım?

C ++, C89 değildir. Cehennem, C bile C89 değil. Bildirimleri ve kodu karıştırabilirsiniz, böylece bildirimi başlatmak için uygun bir değere sahip oluncaya kadar ertelemelisiniz.


2
O zaman sadece her değerin iki kez yazılması gerekir - bir kez derleyicinin kurulum rutini ve yine kullanıcının programı tarafından. Genelde büyük bir sorun değildir, ancak bu daha da artar (örneğin, 1 milyon öğeden oluşan bir dizi oluşturuyorsanız). Otomatik başlatma istiyorsanız, bunu yapan kendi türlerinizi her zaman oluşturabilirsiniz; ancak bu şekilde, istemiyorsanız gereksiz ek yükü kabul etmek zorunda kalmazsınız.
Jeremy Friesner

3

İşaretçi sadece başka bir türdür. Bir veya başka bir POD türü oluşturursanız int, charsıfıra başlatılmaz, öyleyse neden bir işaretçi yapsın? Bu, böyle bir program yazan biri için gereksiz ek yük olarak kabul edilebilir.

char* pBuf;
if (condition)
{
    pBuf = new char[50];
}
else
{
    pBuf = m_myMember->buf();
}

Eğer onu başlatacağınızı biliyorsanız pBuf, yöntemin en üstünde ilk oluşturduğunuzda program neden bir maliyet oluştursun ? Bu sıfır genel gider ilkesidir.


1
Öte yandan, char * pBuf = koşulu yapabilirsiniz? yeni karakter [50]: m_myMember-> buf (); Bu daha çok bir sözdizimi meselesi daha sonra verimlilik meselesi gibi ama yine de size katılıyorum.
the_drow

1
@the_drow: Biri onu daha karmaşık hale getirebilir, böylece böyle bir yeniden yazma mümkün olmaz.
Tekilleştirici

2

Her zaman NULL olarak başlatılan bir işaretçi istiyorsanız, bu işlevselliği taklit etmek için bir C ++ şablonu kullanabilirsiniz:

template<typename T> class InitializedPointer
{
public:
    typedef T       TObj;
    typedef TObj    *PObj;
protected:
    PObj        m_pPointer;

public:
    // Constructors / Destructor
    inline InitializedPointer() { m_pPointer=0; }
    inline InitializedPointer(PObj InPointer) { m_pPointer = InPointer; }
    inline InitializedPointer(const InitializedPointer& oCopy)
    { m_pPointer = oCopy.m_pPointer; }
    inline ~InitializedPointer() { m_pPointer=0; }

    inline PObj GetPointer() const  { return (m_pPointer); }
    inline void SetPointer(PObj InPtr)  { m_pPointer = InPtr; }

    // Operator Overloads
    inline InitializedPointer& operator = (PObj InPtr)
    { SetPointer(InPtr); return(*this); }
    inline InitializedPointer& operator = (const InitializedPointer& InPtr)
    { SetPointer(InPtr.m_pPointer); return(*this); }
    inline PObj operator ->() const { return (m_pPointer); }
    inline TObj &operator *() const { return (*m_pPointer); }

    inline bool operator!=(PObj pOther) const
    { return(m_pPointer!=pOther); }
    inline bool operator==(PObj pOther) const
    { return(m_pPointer==pOther); }
    inline bool operator!=(const InitializedPointer& InPtr) const
    { return(m_pPointer!=InPtr.m_pPointer); }
    inline bool operator==(const InitializedPointer& InPtr) const
    { return(m_pPointer==InPtr.m_pPointer); }

    inline bool operator<=(PObj pOther) const
    { return(m_pPointer<=pOther); }
    inline bool operator>=(PObj pOther) const
    { return(m_pPointer>=pOther); }
    inline bool operator<=(const InitializedPointer& InPtr) const
    { return(m_pPointer<=InPtr.m_pPointer); }
    inline bool operator>=(const InitializedPointer& InPtr) const
    { return(m_pPointer>=InPtr.m_pPointer); }

    inline bool operator<(PObj pOther) const
    { return(m_pPointer<pOther); }
    inline bool operator>(PObj pOther) const
    { return(m_pPointer>pOther); }
    inline bool operator<(const InitializedPointer& InPtr) const
    { return(m_pPointer<InPtr.m_pPointer); }
    inline bool operator>(const InitializedPointer& InPtr) const
    { return(m_pPointer>InPtr.m_pPointer); }
};

1
Bunu uyguluyor olsaydım, kopya ctor veya atama işlemiyle uğraşmazdım - varsayılanlar oldukça iyi. Ve yok ediciniz anlamsız. Elbette, bazı durumlarda daha az operater ve all) kullanarak işaretçileri test edebilirsiniz, bu yüzden onları sağlamalısınız.

Tamam, uygulanması önemsiz olandan daha azı. Yıkıcıya sahiptim, böylece nesne kapsam dışına çıkarsa (yani, bir işlevin alt kapsamı içinde yerel olarak tanımlanmışsa), ancak yine de yığın üzerinde yer kaplıyorsa, bellek çöp için sallanan bir işaretçi olarak bırakılmaz. Ama dostum cidden, bunu 5 dakikanın altında yazdım. Mükemmel olması gerekmiyor.
Adisak

Tamam, tüm karşılaştırma operatörlerini ekledi. Varsayılan geçersiz kılmalar gereksiz olabilir, ancak bunlar bir örnek olduğu için açıkça buradadır.
Adisak

1
Manuel olarak ayarlamadan bunun tüm işaretçileri nasıl boş bırakacağını anlayamadım, burada ne yaptığınızı açıklayabilir misiniz lütfen?
Jonathan

1
@Jonathan: Bu temelde işaretçiyi null olarak ayarlamaktan başka hiçbir şey yapmayan bir "akıllı işaretçi". Bunun yerine IE'yi Foo *akullanırsınız InitializedPointer<Foo> a- Daha Foo *a=0az yazarak tamamen akademik bir egzersiz . Bununla birlikte, yukarıdaki kod, eğitim açısından çok yararlıdır. Küçük bir modifikasyonla ("yer tutucu" ctor / dtor ve atama işlemlerinde), kapsamlı işaretçiler (yıkıcıda ücretsiz olan) ve inc / m_pPointer ayarlandığında veya temizlendiğinde dec işlemleri.
Adisak

2

Statik verilerin 0 olarak başlatıldığını unutmayın (aksi belirtilmedikçe).

Ve evet, değişkenlerinizi her zaman olabildiğince geç ve bir başlangıç ​​değeri ile bildirmelisiniz. Kod gibi

int j;
char *foo;

okuduğunuzda alarm zillerini çalmalısınız. % 100 yasal olduğu için, herhangi bir tüy bırakmanın bu konuda sazan yapmaya ikna edilip edilemeyeceğini bilmiyorum .


Bu GARANTİLİ mi yoksa sadece günümüzün derleyicileri tarafından kullanılan yaygın bir uygulama mı?
gha.st

1
statik değişkenler 0 olarak başlatılır, bu da işaretçiler için doğru olanı yapar (yani onları NULL olarak ayarlar, tüm bitler 0 değil). Bu davranış standart tarafından garanti edilmektedir.
Alok Singhal

1
Statik verilerin sıfıra başlatılması C ve C ++ standardı tarafından garanti edilir, bu sadece yaygın bir uygulama değildir
groovingandi

1
belki de bazı insanlar yığınlarının düzgün bir şekilde hizalandığından emin olmak istedikleri için, işlevin üstündeki tüm değişkenleri önceden bildirirler? Belki de bunu GEREKTİREN ac lehçesiyle yazıyorlar?
KitsuneYMG

1

Bunun bir başka olası nedeni, bağlantı zamanı işaretçilerine bir adres verilmesidir, ancak bir işaretçiye dolaylı adresleme / referansı kaldırma sorumluluğu programcının sorumluluğundadır. Genellikle, derleyici daha az umursamaz, ancak işaretçileri yönetmek ve bellek sızıntısı olmadığından emin olmak için yük programcıya aktarılır.

Gerçekte, kısaca, bağlantı anında işaretçi değişkenine bir adres verilmesi anlamında başlatılırlar. Yukarıdaki örnek kodunuzda, bunun bir SIGSEGV çökmesi veya oluşturması garantilidir.

Akıl sağlığı uğruna, işaretçileri her zaman NULL olarak başlatın, bu şekilde herhangi bir girişimde bulunmadan başvurmadan kaldırmaya çalışın mallocya newda programcıya programın neden yanlış davrandığına dair ipucu verir.

Umarım bu yardımcı olur ve mantıklıdır,


0

C ++ işaretçileri başlattıysa, "C ++ C'den daha yavaştır" diyen C milletinin asıl tutacağı gerçek bir şey olurdu;)


Bu benim sebebim değil. Benim nedenim, eğer donanımda 512 bayt ROM ve 128 bayt RAM varsa ve sıfırlama için fazladan bir talimat, tüm programın oldukça büyük bir yüzdesi olan bir işaretçi bile bir bayttır. O bayta ihtiyacım var!
Jerry Jeremiah

0

C ++, C arka planından gelir ve bundan geri dönen birkaç neden vardır:

C, C ++ 'dan daha fazlası, bir derleme dili değişimidir. Yapmasını söylemediğiniz hiçbir şeyi yapmaz. Bunun için: Eğer BOŞLUK istiyorsanız - yapın!

Ayrıca, C gibi çıplak metal bir dilde şeyleri sıfırlarsanız, tutarlılık soruları otomatik olarak ortaya çıkar: Eğer bir şey malloc ise - otomatik olarak sıfırlanmalı mı? Yığın üzerinde oluşturulan bir yapıya ne dersiniz? tüm baytlar sıfırlanmalı mı? Küresel değişkenler ne olacak? peki ya "(* 0x18);" gibi bir ifade bu, 0x18 bellek konumunun sıfırlanması gerektiği anlamına gelmez mi?


Aslında, C'de, tamamen sıfır bellek ayırmak istiyorsanız kullanabilirsiniz calloc().
David Thornley

1
sadece demek istediğim - eğer yapmak istiyorsan, yapabilirsin, ama otomatik olarak senin için yapılmadı
gha.st

0

Bahsettiğiniz bu işaretçiler neler?

İstisna güvenliği için, her zaman kullanmak auto_ptr, shared_ptr, weak_ptrve onların diğer varyantları.
İyi kodun ayırt edici özelliği, tek bir çağrı içermeyen koddur delete.


3
C ++ 11'den beri, uzaklaşın auto_ptrve değiştirin unique_ptr.
Tekilleştirici

-2

Oh oğlum. Gerçek cevap, örneğin bir işaretçi için temel başlatma olan belleği sıfırlamanın kolay olmasıdır. Ayrıca nesnenin kendisini başlatmakla hiçbir ilgisi yoktur.

Çoğu derleyicinin en yüksek seviyelerde verdiği uyarıları göz önünde bulundurursak, en yüksek seviyede programlamayı ve bunları hata olarak ele almayı hayal edemiyorum. Bunları açmak beni asla büyük miktarda kodda bir hata bile kurtarmadığından bunu tavsiye edemem.


İşaretçi değilse beklenen olmak NULL, yani bunu başlatılıyor tıpkı bir hatadır.
Tekilleştirici
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.