Ok,
So getting back into C++ programming, I’ve been allocating plenty of dynamic memory (using new and new[] ) . However, I’ve only been using delete , never delete[] . If fact, I’ll be honest, I forgot about delete[] , but that’s OK, I didn’t need the [] form anyway. Why you ask?
Take this example:
1 2 3 |
char * buf = new char[100]; ... delete buf; |
Now, almost every example you see online will actually have delete[] buf; , this is correct, but unnecessary. Why? The only difference between delete and delete[] is item deconstruction.
When you call delete, all the memory you allocated with new[] is still erased. The problem is, only the first element (the one being directly pointed to) is deconstructed. None of the other members of your array are deconstructed. In my example this is OK, because the array was an array of “char”s, you can’t deconstruct a char in C++, it’s not an object.
When is delete[] useful?
When you allocate an array of objects that themselves allocate more memory. delete[] makes sure that each entry in the allocated array is properly deconstructed. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
class class1 { public: int var1; int var2; }; class class2 { public: class1 * theclass; class2() { theclass = new class1(); } ~class2() { delete theclass; } }; class1 * ptr_class1a = new class1[5]; class2 * ptr_class2a = new class2[5]; class1 * ptr_class1b = new class1(); class2 * ptr_class2b = new class2(); delete ptr_class1a; // This is OK! All allocated memory will be freed. delete[] ptr_class1a; // This is Best. // It is safest to deconstruct all items, because we don't really // know what is going on beneath the hood of every class we instatiate, // and calling delete[] is just safer all around. delete ptr_class2a; // This is NOT ok. // This will only delete the memory reserved for the 5 instances of class2. // It will NOT free the instances of class1 that each class2 instantiated!. delete[] ptr_class2a; // Good. This deconstructs each class2 instance, freeing // each one's class1 instance. Then all the memory reserved for ptr_class2 // is free. No memory leaks! delete ptr_class1b; delete ptr_class2b; // Good // delete always deconstructs the first item pointed to (assuming the pointer //type matches the class type (pointer casting is dangerous!!)). All memory is //freed. delete[] ptr_class1b; delete[] ptr_class2b; // Acceptable, but not necessary. // You can delete[] any pointer, as long as you have not recast the pointer // type. |
Can I just call delete[] all the time?
Sure, well, almost. delete[] works on a specific type of pointer. When you call myclass * myptr = new myclass[5] , you are telling the system to reserve you sizeof(myclass)*5 bytes of memory. This size is all that is stored in the memory allocator. When you call delete[] myptr; , the memory allocator goes through the following process:
- How much memory was reserved starting at the address stored in myptr (answer: sizeof(myclass) * 5);
- Start at offset = 0.
- Call the myclass.deconstructor at the address myptr + offset.
- Increase offset by sizeof(myclass)
- If offset < total allocated memory, goto 3. Else, continue;
- Delete/Free all memory reserved (sizeof(myclasS) * 5 bytes) starting at address storef in myptr;.
However, if you tried to call delete[] (void *) myptr; all calls to sizeof(myclass) above will be replaced with sizeof(void), which returns no size (no object can be of type void). Therefore, none of the objects in the array will be deconstructed. Calling delete[] (void *)ptr; is the same as calling delete (void *)ptr; .
Similarly, if you recast the pointer to a different type, delete[] will not know the proper size of each object, and any attempts to deconstruct those “objects” will most likely result in errors/exceptions. Example:
1 2 3 4 5 6 7 8 9 10 11 |
// Using class1 and class2 from above. class2 *ptr2= new class2[5]; class1 *ptr1 = (class1 *)ptr2; delete[] ptr1; // ERROR // ptr1 points to memory of sizeof(class2) * 5, but will be interpreted // as (sizeof(class2) * 5) / sizeof(class1) objects! // // Also, delete[] will call class1.deconstructor() instead of class2.deconstructor, // and the BEST CASE scenario is leaked memory (worst case in an error/exception). |
Of course, casting pointers in C++ is always dangerous. The compiler and system do not keep track of allocated memory by type, only size. Therefore, you should always be careful when having to cast a pointer.
Note: There are acceptable times to cast a pointer (when reading in bytes that are actually multi-byte values, say casting an int* as a char*), but often times, this can also be handled with Unions.
Anyway, that’s my “quick” overview of the delete and delete[] operations in C++.