[] Silme işlemi bir dizi olduğunu nasıl biliyor?


136

Tamam, sanırım hepimiz, aşağıdaki kodla olanların, geçirilene bağlı olarak tanımsız olduğunu kabul ediyoruz,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

İşaretçi her türlü farklı şey olabilir ve bu nedenle delete[]üzerinde koşulsuz bir işlem yapmak tanımsızdır. Ancak, bir dizi işaretçisini gerçekten geçtiğimizi varsayalım,

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

Sorum işaretçisi bu durumda, bir olan bir dizidir, bu bilmesidir? Yani, dilin / derleyicinin bakış açısından, arrbir dizi işaretçisinin tek bir int'e karşı bir işaretçi olup olmadığı hakkında hiçbir fikri yoktur . Heck, arrdinamik olarak yaratılıp yaratılmadığını bile bilmiyor . Ancak bunun yerine aşağıdakileri yaparsam,

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

İşletim sistemi, yalnızca bir int'i silecek kadar akıllıdır ve belleğin geri kalanını bu noktanın ötesinde silerek bir tür 'öldürme çılgınlığına' devam etmeyecektir (bununla strlenve \0sonlandırılmamış bir dizenin kontrastı - devam edene kadar devam edecektir. isabet 0).

Peki bunları hatırlamak kimin işi? İşletim sistemi arka planda bir tür kayıt tutuyor mu? (Yani, bu göreve ne olduğunu tanımsız olarak söyleyerek başladığımı anlıyorum, ama gerçek şu ki, 'öldürme çılgınlığı' senaryosu gerçekleşmedi, bu nedenle birisinin hatırladığı pratik dünyada .)




msgstr "işaretçi bir dizidir". Hayır, işaretçiler asla diziler değildir. Genellikle dizinin ilk öğesine işaret ederler, ancak bu farklı bir şeydir.
Aaron McDaid

Yanıtlar:


99

Derleyici bunun bir dizi olduğunu bilmiyor, programcıya güveniyor. Tek bir işaretçi silinmesi intile delete []tanımsız davranışlara neden olacaktır. İkinci main()örneğiniz hemen çökmese bile güvensizdir.

Derleyici, bir şekilde kaç nesnenin silinmesi gerektiğini takip etmek zorundadır. Bunu, dizi boyutunu depolayacak kadar fazla tahsis ederek yapabilir. Daha fazla ayrıntı için C ++ Süper SSS bölümüne bakın .


14
Aslında, yeni ile oluşturulan bir şeyi silmek için delete [] kullanmak sömürülebilir. taossa.com/index.php/2007/01/03/…
Rodrigo

23
@Rodrigo Yorumunuzdaki bağlantı koptu, ancak şükür ki geri dönüş makinesinin replay.web.archive.org/20080703153358/http://taossa.com/…
David Gardner

103

Şimdiye kadar verilen cevapların ele alınmadığı bir soru var: Çalışma zamanı kitaplıkları (işletim sistemi değil, gerçekten) dizideki şeylerin sayısını izleyebilirse, neden delete[]sözdizimine ihtiyacımız var ? Neden deletetüm silmeleri işlemek için tek bir form kullanılamaz?

Bunun cevabı, C ++ 'ın köklerine C uyumlu bir dil olarak geri dönüyor (artık gerçekten olmaya çalışmıyor.) Stroustrup'un felsefesi, programcının kullanmadığı herhangi bir özellik için ödeme yapmak zorunda olmamasıydı. Dizileri kullanmıyorlarsa, ayrılan her bellek yığını için nesne dizilerinin maliyetini taşımak zorunda kalmamalıdırlar.

Yani, kodunuz basitçe

Foo* foo = new Foo;

bunun için ayrılan bellek alanı, foodizileri desteklemek için gereken ek yükü içermemelidir Foo.

Ek dizi boyutu bilgilerini taşımak için yalnızca dizi ayırmaları ayarlandığından, çalışma zamanı kitaplıklarına nesneleri sildiğinizde bu bilgileri aramasını söylemeniz gerekir. Bu yüzden kullanmalıyız

delete[] bar;

sadece yerine

delete bar;

bar, bir diziye işaretçi ise.

Çoğumuz için (kendim dahil), birkaç ekstra bayt bellek hakkındaki bu karışıklık bu günlerde tuhaf görünüyor. Ancak hala birkaç bayt tasarruf etmenin (çok fazla sayıda bellek bloğu olandan) önemli olabileceği bazı durumlar vardır.


20
"Birkaç ekstra bayt bellek hakkındaki karışıklık bu günlerde tuhaf görünüyor". Neyse ki, bu tür insanlara çıplak diziler de ilginç görünmeye başlıyor, bu yüzden sadece bir vektör veya boost :: dizisi kullanabilir ve [] sonsuza kadar silmeyi unutabilirler :-)
Steve Jessop 2

28

Evet, işletim sistemi bazı şeyleri 'arka planda' tutar. Örneğin,

int* num = new int[5];

İşletim Sistemi 4 ekstra bayt tahsis edebilir, tahsisat boyutunu tahsis edilen belleğin ilk 4 baytında depolayabilir ve bir ofset işaretçisi döndürebilir (yani, 1000 ile 1024 arasındaki bellek alanlarını tahsis eder, ancak işaretçi 1004'e, noktaları 1000- 1003 tahsisin büyüklüğünü depolamak). Ardından, silme çağrıldığında, ayırmanın boyutunu bulmak için işaretçinin kendisine geçmesinden önce 4 bayta bakabilir.

Bir tahsisatın boyutunu izlemenin başka yolları olduğundan eminim, ancak bu bir seçenek.


26
+1 - genelde geçerli olan nokta, dil çalışma zamanının işletim sistemini değil, bu meta verileri depolamaktan sorumlu olmasıdır.
sharptooth

Dizi boyutuna veya dizi tanımlı bir nesnenin boyutuna ne olur? Bu nesnede bir sizeof yaptığınızda ek 4 bayt gösteriyor mu?
Shree

3
Hayır, sizeof sadece dizinin boyutunu gösterir. Çalışma zamanı açıkladığım yöntemle uygulamayı seçerse, bu kesinlikle bir uygulama ayrıntısıdır ve kullanıcının bakış açısından maskelenmelidir. İşaretçiden önceki bellek kullanıcıya 'ait değildir' ve sayılmaz.
bsdfish

2
Daha da önemlisi, sizeof her durumda dinamik olarak ayrılmış bir dizinin gerçek boyutunu döndürmez. Yalnızca derleme zamanında bilinen boyutları döndürebilir.
bdonlan

Dizinin üzerinde doğru bir döngü oluşturmak için bu meta verileri for for döngüsünde kullanmak mümkün müdür? ör. for(int i = 0; i < *(arrayPointer - 1); i++){ }
Sam

13

Bu, bu soruya çok benzer ve aradığınız ayrıntıların çoğuna sahiptir.

Ancak, bunların hiçbirini izlemek OS'nin işi değildir. Aslında, dizinin boyutunu izleyecek olan çalışma zamanı kitaplıkları veya temeldeki bellek yöneticisidir. Bu genellikle önden fazla bellek ayırarak ve dizinin boyutunu o konumda depolayarak yapılır (çoğu kafa düğümü kullanır).

Bu, aşağıdaki kod çalıştırılarak bazı uygulamalarda görüntülenebilir

int* pArray = new int[5];
int size = *(pArray-1);

Bu işe yarayacak mı? Windows & linux biz bu çalışma alamadım.
arkadaş

1
size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)bunun yerine deneyin

9

deleteveya delete[]muhtemelen her ikisi de ayrılan belleği serbest bırakır (bellek işaretlenir), ancak büyük fark, deletebir dizide dizinin her öğesinin yıkıcısını çağırmamasıdır.

Her neyse, karıştırma new/new[]ve delete/delete[]muhtemelen UB.


1
Açık, kısa ve en faydalı cevap!
GntS

6

Bunun bir dizi olduğunu bilmiyor, bu yüzden delete[]normal eski yerine tedarik etmeniz gerekiyor delete.


5

Buna benzer bir sorum vardı. C'de, malloc () (veya başka bir benzer işlev) ile bellek ayırır ve free () ile silersiniz. Sadece belirli bir bayt ayıran tek bir malloc () vardır. Yalnızca bir işaretçi parametresi olarak alan bir free () vardır.

Öyleyse neden C'de sadece işaretçiyi serbest bırakmak için teslim edebilirsiniz, ancak C ++ 'da bir dizi mi yoksa tek bir değişken mi olduğunu söylemelisiniz?

Öğrenmiş olduğum cevap, sınıf yıkıcılarıyla ilgili.

Sınıf MyClass örneğini ayırırsanız ...

classes = new MyClass[3];

Ve delete ile silerseniz, yıkıcıyı yalnızca MyClass'ın ilk örneği için alabilirsiniz. Delete [] kullanırsanız, yıkıcıyı dizideki tüm örnekler için çağıracağınızdan emin olabilirsiniz.

BU önemli fark. Yalnızca standart türlerle (örn. İnt) çalışıyorsanız, bu sorunu gerçekten görmezsiniz. Ayrıca, new [] üzerinde delete ve new üzerinde [] silmek için kullanılan davranışın tanımsız olduğunu unutmayın - her derleyici / sistemde aynı şekilde çalışmayabilir.


3

Bellek tahsisinden sorumlu olan çalışma zamanına bağlıdır, aynı şekilde standart C'de malloc ile oluşturulan bir diziyi ücretsiz kullanarak silebilirsiniz. Her derleyicinin farklı uyguladığını düşünüyorum. Yaygın bir yol, dizi boyutu için fazladan bir hücre ayırmaktır.

Ancak, çalışma zamanı bir dizi veya işaretçi olup olmadığını algılayacak kadar akıllı değildir, bunu bilgilendirmeniz gerekir ve yanılıyorsanız, doğru bir şekilde silmezsiniz (Örn, dizi yerine ptr) veya Sonunda boyut için alakasız bir değer alırsınız ve önemli hasara neden olursunuz.


3

Derleyiciler için yaklaşımlardan biri, biraz daha fazla bellek tahsis etmek ve başlık elemanında eleman sayısını saklamaktır.

Nasıl yapılacağına örnek: İşte

int* i = new int[4];

derleyici sizeof (int) * 5 bayt ayırır.

int *temp = malloc(sizeof(int)*5)

4İlk sizeof(int)baytta depolanacak

*temp = 4;

ve ayarla i

i = temp + 1;

Yani i4 öğeden oluşan diziyi gösterir, 5 değil.

Ve

delete[] i;

aşağıdaki şekilde işlenecek

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)

1

Anlamsal olarak, C ++ 'da delete operatörünün her iki versiyonu da herhangi bir işaretçiyi "yiyebilir"; ancak, tek bir nesneye işaretçi verilirse delete[], UB sonuçlanır, yani sistem çökmesi veya hiç bir şey dahil herhangi bir şey olabilir.

C ++, programcının deallocation: array veya single object konusuna bağlı olarak delete operatörünün uygun sürümünü seçmesini gerektirir.

Derleyici, silme operatörüne iletilen bir işaretçinin bir işaretçi dizisi olup olmadığını otomatik olarak belirleyebiliyorsa, her iki durumda da yeterli olacak şekilde C ++ 'da yalnızca bir silme operatörü olacaktır.


1

Derleyicinin bir dizi olup olmadığını bilmediğini kabul edin. Programcıya kalmış.

Derleyici bazen dizi boyutunu saklamak için yeterince ayırmakla birlikte kaç nesnenin silinmesi gerektiğini takip eder, ancak her zaman gerekli değildir.

Fazladan depolama alanı tahsis edildiğinde, lütfen C ++ ABI'ye (derleyicilerin nasıl uygulandığı) bakın: Itanium C ++ ABI: Array Operator new Cookies


Ben sadece her derleyici gözlenen isteyen bazı C belgelenmiş ABI ++. Daha önce ziyaret ettiğim bağlantı için +1. Teşekkürler.
Don Wakefield

0

Sen kullanamaz silme bir dizi için ve kullanmak olamaz [] silme olmayan bir dizi için.


8
Ortalama derleyiciniz kötüye kullanımı tespit edemeyeceğinden, demek istememelisiniz diye düşünüyorum .
Don Wakefield

0

"tanımlanmamış davranış" basitçe, spec özelliğinin ne olacağı konusunda hiçbir garanti vermediği anlamına gelir. Nessacerally kötü bir şey olacağı anlamına gelmez.

Peki bunları hatırlamak kimin işi? İşletim sistemi arka planda bir tür kayıt tutuyor mu? (Yani, bu göreve ne olduğunu tanımsız olarak söyleyerek başladığımı anlıyorum, ama gerçek şu ki, 'öldürme çılgınlığı' senaryosu gerçekleşmedi, bu nedenle birisinin hatırladığı pratik dünyada.)

Burada tipik olarak iki katman vardır. Temel bellek yöneticisi ve C ++ uygulaması.

Genel olarak bellek yöneticisi (diğer şeylerin yanı sıra) ayrılan bellek bloğunun boyutunu hatırlayacaktır. Bu, C ++ uygulamasının istediği bloktan daha büyük olabilir. Genellikle bellek yöneticisi, meta verilerini ayrılmış bellek bloğundan önce saklar.

C ++ uygulaması genellikle dizinin büyüklüğünü yalnızca kendi amaçları için yapması gerekiyorsa hatırlar, tipik olarak tür üç değerlikli olmayan bir yıkıcıya sahiptir.

Bu nedenle, önemsiz yıkıcıya sahip türler için "sil" ve "sil []" öğelerinin uygulanması genellikle aynıdır. C ++ uygulaması, işaretçiyi temeldeki bellek yöneticisine iletir. Gibi bir şey

free(p)

Öte yandan önemsiz bir yıkıcı "silme" ve "silme []" olan türler için farklı olabilir. "delete" şöyle bir şey olabilir (burada T, işaretçinin işaret ettiği türdür)

p->~T();
free(p);

Iken "delete []" gibi bir şey olurdu.

size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
  p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);

-1

nesne dizisi üzerinden yineleme ve her biri için yıkıcı çağrı. Bu basit kod cadı yeni [] aşırı yükler ve [] ifadeleri silmek ve gerektiğinde her nesne için bellek ve çağrı yıkıcı dağıtmak için bir şablon işlevi sağlar:

// overloaded new expression 
void* operator new[]( size_t size )
{
    // allocate 4 bytes more see comment below 
    int* ptr = (int*)malloc( size + 4 );

    // set value stored at address to 0 
    // and shift pointer by 4 bytes to avoid situation that
    // might arise where two memory blocks 
    // are adjacent and non-zero
    *ptr = 0;
    ++ptr; 

    return ptr;
}
//////////////////////////////////////////

// overloaded delete expression 
void static operator delete[]( void* ptr )
{
    // decrement value of pointer to get the
    // "Real Pointer Value"
    int* realPtr = (int*)ptr;
    --realPtr;

    free( realPtr );
}
//////////////////////////////////////////

// Template used to call destructor if needed 
// and call appropriate delete 
template<class T>
void Deallocate( T* ptr )
{
    int* instanceCount = (int*)ptr;
    --instanceCount;

    if(*instanceCount > 0) // if larger than 0 array is being deleted
    {
        // call destructor for each object
        for(int i = 0; i < *instanceCount; i++)
        {
            ptr[i].~T();
        }
        // call delete passing instance count witch points
        // to begin of array memory 
        ::operator delete[]( instanceCount );
    }
    else
    {
        // single instance deleted call destructor
        // and delete passing ptr
        ptr->~T();
        ::operator delete[]( ptr );
    }
}

// Replace calls to new and delete
#define MyNew ::new
#define MyDelete(ptr) Deallocate(ptr)

// structure with constructor/ destructor
struct StructureOne
{
    StructureOne():
    someInt(0)
    {}
    ~StructureOne() 
    {
        someInt = 0;
    }

    int someInt;
};
//////////////////////////////

// structure without constructor/ destructor
struct StructureTwo
{
    int someInt;
};
//////////////////////////////


void main(void)
{
    const unsigned int numElements = 30;

    StructureOne* structOne = nullptr;
    StructureTwo* structTwo = nullptr;
    int* basicType = nullptr;
    size_t ArraySize = 0;

/**********************************************************************/
    // basic type array 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( int ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor. value assigned to basicType pointer
    // will be the same as value of "++ptr" in new expression
    basicType = MyNew int[numElements];

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( int ) * numElements"
    MyDelete( basicType );

/**********************************************************************/
    // structure without constructor and destructor array 

    // behavior will be the same as with basic type 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( StructureTwo ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor value assigned to structTwo pointer
    // will be the same as value of "++ptr" in new expression
    structTwo = MyNew StructureTwo[numElements]; 

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( StructureTwo ) * numElements"
    MyDelete( structTwo );

/**********************************************************************/
    // structure with constructor and destructor array 

    // place break point check size and compare it with size passed in
    // new expression size in expression will be larger by 4 bytes
    ArraySize = sizeof( StructureOne ) * numElements;

    // value assigned to "structOne pointer" will be different 
    // of "++ptr" in new expression  "shifted by another 4 bytes"
    structOne = MyNew StructureOne[numElements];

    // Place break point in template function to see the behavior
    // destructors will be called for each array object 
    MyDelete( structOne );
}
///////////////////////////////////////////

-2

bir sınıf içinde bir yıkıcı tanımlayın ve her iki sözdizimiyle kodunuzu yürütün

delete pointer

delete [] pointer

çıkışa göre çözümler bulabilirsiniz


dizi türünü yeni oluşturduğunuzda delete [] kullanın. örneğin int * a = yeni int; int * b = yeni int [5]; silmek; sil [] b;
Lineesh K Mohan

-3

Cevap:

int * pArray = yeni int [5];

int boyutu = * (pArray-1);

Yukarıda yayınlananlar doğru değil ve geçersiz değer üretiyor. "-1" öğeleri sayar 64 bit Windows işletim sisteminde doğru arabellek boyutu Ptr - 4 bayt adresinde bulunur

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.