Bagaimana delete [] tahu itu array?

136

Baiklah, saya pikir kita semua setuju bahwa apa yang terjadi dengan kode berikut tidak ditentukan, tergantung pada apa yang diteruskan,

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

Pointer dapat berupa segala macam hal yang berbeda, sehingga melakukan tanpa syarat delete[]di atasnya tidak ditentukan. Namun, mari kita asumsikan bahwa kita memang mengoper pointer array,

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

Pertanyaan saya adalah, dalam hal ini di mana pointernya adalah sebuah array, siapa yang mengetahui hal ini? Maksud saya, dari sudut pandang bahasa / kompiler, ia tidak tahu apakah arrpointer array versus pointer ke int tunggal. Heck, bahkan tidak tahu apakah arrdiciptakan secara dinamis. Namun, jika saya melakukan hal berikut,

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

OS cukup pintar untuk hanya menghapus satu int dan tidak melakukan beberapa jenis 'pembunuhan foya' dengan menghapus sisa memori di luar titik itu (kontras dengan strlendan \0string yang tidak diakhiri - itu akan terus berjalan sampai itu hits 0).

Jadi tugas siapa mengingat hal-hal ini? Apakah OS menyimpan beberapa jenis catatan di latar belakang? (Maksud saya, saya menyadari bahwa saya memulai posting ini dengan mengatakan bahwa apa yang terjadi tidak ditentukan, tetapi kenyataannya adalah, skenario 'pembunuhan foya' tidak terjadi, jadi oleh karena itu dalam dunia praktis seseorang mengingat.)

GRB
sumber
6
itu tahu dari tanda kurung siku setelah menghapus
JoelFan
"pointernya adalah array". Tidak, pointer tidak pernah merupakan array. Mereka sering menunjuk ke elemen pertama dari array, tetapi itu adalah hal yang berbeda.
Aaron McDaid

Jawaban:

100

Kompilator tidak tahu itu sebuah array, ia mempercayai programmer. Menghapus penunjuk ke satu intdengan delete []akan menghasilkan perilaku yang tidak ditentukan. main()Contoh kedua Anda tidak aman, meskipun tidak langsung mogok.

Kompilator memang harus melacak berapa banyak objek yang perlu dihapus. Ini dapat melakukan ini dengan mengalokasikan cukup banyak untuk menyimpan ukuran array. Untuk lebih jelasnya, lihat C ++ Super FAQ .

Fred Larson
sumber
14
Sebenarnya, menggunakan delete [] untuk menghapus sesuatu yang dibuat dengan yang baru bisa dieksploitasi. taossa.com/index.php/2007/01/03/…
Rodrigo
23
@Rodrigo Tautan di komentar Anda rusak, tapi untungnya mesin wayback memiliki salinannya di replay.web.archive.org/20080703153358/http://taossa.com/…
David Gardner
103

Satu pertanyaan yang jawaban yang diberikan sejauh ini tampaknya tidak menjawab: jika perpustakaan runtime (bukan OS, sungguh) dapat melacak jumlah hal dalam array, lalu mengapa kita membutuhkan delete[]sintaks sama sekali? Mengapa satu deleteformulir tidak dapat digunakan untuk menangani semua penghapusan?

Jawaban untuk ini kembali ke akar C ++ sebagai bahasa yang kompatibel dengan C (yang sebenarnya tidak lagi diperjuangkan.) Filosofi Stroustrup adalah bahwa programmer tidak perlu membayar untuk fitur yang tidak mereka gunakan. Jika mereka tidak menggunakan array, maka mereka tidak harus menanggung biaya array objek untuk setiap potongan memori yang dialokasikan.

Artinya, jika kode Anda begitu saja

Foo* foo = new Foo;

maka ruang memori yang dialokasikan footidak boleh menyertakan overhead tambahan yang diperlukan untuk mendukung array Foo.

Karena hanya alokasi array yang disiapkan untuk membawa informasi ukuran array ekstra, Anda kemudian perlu memberi tahu pustaka runtime untuk mencari informasi tersebut saat Anda menghapus objek. Itulah mengapa kita perlu menggunakan

delete[] bar;

bukan hanya

delete bar;

jika bar adalah penunjuk ke sebuah array.

Bagi kebanyakan dari kita (termasuk saya sendiri), keributan tentang beberapa byte tambahan memori tampaknya aneh belakangan ini. Tetapi masih ada beberapa situasi di mana menyimpan beberapa byte (dari yang bisa menjadi jumlah blok memori yang sangat tinggi) bisa menjadi penting.

Dan Breslau
sumber
20
"keributan tentang beberapa byte tambahan memori tampaknya aneh hari ini". Untungnya, bagi orang-orang seperti itu, array kosong juga mulai terlihat aneh, jadi mereka dapat menggunakan vektor atau boost :: array, dan lupakan tentang delete [] forever :-)
Steve Jessop
28

Ya, OS menyimpan beberapa hal di 'latar belakang'. Misalnya, jika Anda lari

int* num = new int[5];

OS dapat mengalokasikan 4 byte tambahan, menyimpan ukuran alokasi dalam 4 byte pertama dari memori yang dialokasikan dan mengembalikan pointer offset (yaitu, mengalokasikan ruang memori 1000 hingga 1024 tetapi pointer mengembalikan poin ke 1004, dengan lokasi 1000- 1003 menyimpan ukuran alokasi). Kemudian, saat delete dipanggil, ia dapat melihat 4 byte sebelum pointer diteruskan ke sana untuk menemukan ukuran alokasi.

Saya yakin ada cara lain untuk melacak ukuran alokasi, tapi itu salah satu pilihan.

bsdfish.dll
sumber
26
+1 - poin valid secara umum kecuali bahwa biasanya runtime bahasa bertanggung jawab untuk menyimpan metadata ini, bukan OS.
gigi tajam
Apa yang terjadi dengan ukuran larik atau ukuran objek yang telah ditentukan larik? Apakah itu menunjukkan 4 byte tambahan ketika Anda melakukan ukuran pada objek itu?
Shree
3
Tidak, sizeof hanya menunjukkan ukuran array. Jika runtime memilih untuk mengimplementasikannya dengan metode yang saya jelaskan, itu benar-benar merupakan detail implementasi dan dari perspektif pengguna, itu harus disamarkan. Memori sebelum penunjuk bukan 'milik' pengguna, dan tidak dihitung.
bsdfish
2
Lebih penting lagi, sizeof tidak akan mengembalikan ukuran sebenarnya dari array yang dialokasikan secara dinamis dalam kasus apa pun. Itu hanya dapat mengembalikan ukuran yang diketahui pada waktu kompilasi.
bdonlan
Apakah mungkin untuk menggunakan metadata ini dalam perulangan for untuk mengulang larik secara akurat? misalnya for(int i = 0; i < *(arrayPointer - 1); i++){ }
Sam
13

Ini sangat mirip dengan pertanyaan ini dan memiliki banyak detail yang Anda cari.

Tapi cukup untuk mengatakan, bukanlah tugas OS untuk melacak semua ini. Ini sebenarnya adalah pustaka runtime atau manajer memori yang mendasari yang akan melacak ukuran array. Ini biasanya dilakukan dengan mengalokasikan memori ekstra di depan dan menyimpan ukuran array di lokasi itu (kebanyakan menggunakan simpul kepala).

Ini dapat dilihat pada beberapa implementasi dengan menjalankan kode berikut

int* pArray = new int[5];
int size = *(pArray-1);
JaredPar
sumber
apakah ini akan berhasil? Di windows & linux kami tidak mendapatkan ini berfungsi.
sobat
1
coba size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)sebagai gantinya
9

deleteatau delete[]mungkin keduanya akan membebaskan memori yang dialokasikan (memori menunjuk), tetapi perbedaan besar adalah bahwa deletepada larik tidak akan memanggil destruktor dari setiap elemen larik.

Anyway, mixing new/new[]dan delete/delete[]mungkin UB.

Benoît
sumber
1
Jawaban yang jelas, singkat, dan paling berguna!
GntS
6

Itu tidak tahu itu sebuah array, itu sebabnya Anda harus memasok delete[]daripada yang lama biasa delete.

eduffy
sumber
5

Saya punya pertanyaan serupa dengan ini. Di C, Anda mengalokasikan memori dengan malloc () (atau fungsi lain yang serupa), dan menghapusnya dengan free (). Hanya ada satu malloc (), yang hanya mengalokasikan sejumlah byte. Hanya ada satu free (), yang mengambil pointer sebagai parameternya.

Jadi mengapa di C Anda bisa menyerahkan pointer ke free, tetapi di C ++ Anda harus memberi tahu apakah itu array atau variabel tunggal?

Jawabannya, saya telah belajar, berkaitan dengan penghancur kelas.

Jika Anda mengalokasikan instance dari kelas MyClass ...

classes = new MyClass[3];

Dan hapus dengan delete, Anda mungkin hanya mendapatkan destruktor untuk instance pertama MyClass yang dipanggil. Jika Anda menggunakan delete [], Anda dapat yakin bahwa destruktor akan dipanggil untuk semua instance dalam array.

INILAH perbedaan penting. Jika Anda hanya bekerja dengan tipe standar (misalnya int) Anda tidak akan benar-benar melihat masalah ini. Plus, Anda harus ingat bahwa perilaku untuk menggunakan delete pada new [] dan delete [] pada new tidak terdefinisi - ini mungkin tidak bekerja dengan cara yang sama pada setiap compiler / sistem.

ProdigySim
sumber
3

Terserah runtime yang bertanggung jawab atas alokasi memori, dengan cara yang sama Anda dapat menghapus array yang dibuat dengan malloc dalam C standar menggunakan free. Saya pikir setiap kompiler mengimplementasikannya secara berbeda. Salah satu cara yang umum adalah mengalokasikan sel ekstra untuk ukuran array.

Namun, runtime tidak cukup pintar untuk mendeteksi apakah itu array atau pointer, Anda harus memberitahunya, dan jika Anda salah, Anda tidak menghapus dengan benar (Misalnya, ptr daripada array), atau Anda akhirnya mengambil nilai yang tidak terkait untuk ukuran tersebut dan menyebabkan kerusakan yang signifikan.

Uri
sumber
3

SALAH SATU pendekatan untuk kompiler adalah mengalokasikan lebih banyak memori dan menyimpan jumlah elemen di elemen head.

Contoh bagaimana itu bisa dilakukan: Di sini

int* i = new int[4];

kompilator akan mengalokasikan sizeof (int) * 5 byte.

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

Akan disimpan 4dalam sizeof(int)byte pertama

*temp = 4;

dan set i

i = temp + 1;

Jadi imenunjuk ke larik 4 elemen, bukan 5.

Dan

delete[] i;

akan diproses dengan cara berikut

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)
Avt
sumber
1

Secara semantik, kedua versi delete operator di C ++ bisa "memakan" pointer apapun; Namun, jika pointer ke satu objek diberikan delete[], maka UB akan menghasilkan, yang berarti apa pun dapat terjadi, termasuk sistem crash atau tidak ada sama sekali.

C ++ mengharuskan programmer untuk memilih versi yang tepat dari operator delete tergantung pada subjek deallocation: array atau objek tunggal.

Jika compiler dapat secara otomatis menentukan apakah pointer yang diteruskan ke operator delete adalah array pointer, maka hanya akan ada satu operator delete di C ++, yang akan mencukupi untuk kedua kasus tersebut.

mloskot.dll
sumber
1

Setuju bahwa kompilator tidak tahu apakah itu array atau bukan. Terserah programmer.

Kompilator terkadang melacak berapa banyak objek yang perlu dihapus dengan mengalokasikan cukup banyak untuk menyimpan ukuran array, tetapi tidak selalu diperlukan.

Untuk spesifikasi lengkap saat penyimpanan tambahan dialokasikan, lihat C ++ ABI (bagaimana compiler diimplementasikan): Itanium C ++ ABI: Array Operator new Cookies

shibo
sumber
Saya hanya berharap setiap kompilator mengamati beberapa ABI terdokumentasi untuk C ++. +1 untuk tautan, yang pernah saya kunjungi sebelumnya. Terima kasih.
Don Wakefield
0

Anda tidak dapat menggunakan delete untuk sebuah array, dan Anda tidak dapat menggunakan delete [] untuk non-array.

Don Wakefield
sumber
8
Saya pikir maksud Anda tidak boleh , karena kompiler rata-rata Anda tidak akan mendeteksi penyalahgunaan.
Don Wakefield
0

"perilaku tidak terdefinisi" berarti spesifikasi bahasa tidak menjamin apa yang akan terjadi. Ini tidak selalu berarti bahwa sesuatu yang buruk akan terjadi.

Jadi tugas siapa mengingat hal-hal ini? Apakah OS menyimpan beberapa jenis catatan di latar belakang? (Maksud saya, saya menyadari bahwa saya memulai posting ini dengan mengatakan bahwa apa yang terjadi tidak ditentukan, tetapi kenyataannya adalah, skenario 'pembunuhan besar-besaran' tidak terjadi, jadi oleh karena itu dalam dunia praktis seseorang mengingat.)

Biasanya ada dua lapisan di sini. Manajer memori yang mendasari dan implementasi C ++.

Secara umum pengelola memori akan mengingat (antara lain) ukuran blok memori yang dialokasikan. Ini mungkin lebih besar dari blok yang diminta oleh implementasi C ++. Biasanya pengelola memori akan menyimpan metadatanya sebelum blok memori yang dialokasikan.

Implementasi C ++ umumnya hanya akan mengingat ukuran array jika perlu melakukannya untuk tujuannya sendiri, biasanya karena tipe tersebut memiliki destruktor non-trival.

Jadi untuk tipe dengan destruktor sepele, implementasi "delete" dan "delete []" biasanya sama. Implementasi C ++ hanya meneruskan penunjuk ke pengelola memori yang mendasarinya. Sesuatu seperti

free(p)

Di sisi lain untuk tipe dengan destruktor non-trivial "delete" dan "delete []" cenderung berbeda. "hapus" akan seperti itu (di mana T adalah tipe yang ditunjukkan oleh penunjuk)

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

Sedangkan "delete []" akan seperti ini.

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);
plugwash
sumber
-1

iterasi melalui array objek dan panggil destruktor untuk masing-masing objek. Saya telah membuat penyihir kode sederhana ini membebani ekspresi [] dan menghapus [] baru dan menyediakan fungsi template untuk membatalkan alokasi memori dan memanggil destruktor untuk setiap objek jika diperlukan:

// 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 );
}
///////////////////////////////////////////
Rafal Rebisz
sumber
-2

cukup definisikan destruktor di dalam kelas dan jalankan kode Anda dengan kedua sintaks

delete pointer

delete [] pointer

menurut keluaran u dapat menemukan solusinya

bubu
sumber
gunakan delete [] ketika Anda baru tipe array. misalnya int * a = new int; int * b = new int [5]; hapus a; hapus [] b;
Lineesh K Mohan
-3

Jawabannya:

int * pArray = new int [5];

int size = * (pArray-1);

Diposting di atas tidak benar dan menghasilkan nilai yang tidak valid. "-1" menghitung elemen Pada OS Windows 64 bit, ukuran buffer yang benar berada di alamat Ptr - 4 byte

Evgeni Raikhel
sumber