When do you need to delete C-style arrays? - c++

I was reading an excellent answer to another question which gave these specific examples:
char[] array = {'a', 'b', 'c', '\0'};
Thing* t = new Thing[size];
t[someindex].dosomething();
I commented that, as good practice, you should add delete[] to the second example, as this is the bit people forget leading to memory leaks.
But I can't remember if you need to delete[] in the first instance. I don't think you do, because there's no new so it's on the stack, but I'm not 100% sure that's right.

I don't think you do, because there's no new so it's on the stack, but I'm not 100% sure that's right.
This reasoning is correct when the array is defined inside a function, and is therefore on the stack. You must never use delete on any objects that have been created on the stack without the use of new. Using delete on stack allocated objects will lead to undefined behavior.
If the definition char[] array = {'a', 'b', 'c', '\0'}; appeared at file scope, however, it will have static storage duration and will not be on the stack, but it still won't have been dynamically allocated and still must not be delete[]d.
So, either way, don't delete[] the array....

You always and only use delete when you have used new and delete[] when you have used new[]. Never mix them. And never mix with (2).
You always and only use free when you have made a classic C allocation like malloc or calloc. Never mix with (1). But don't free memory allocated with alloca ;)
Note that in some cases, the allocation or deallocation can be hidden inside a function or something. For instance, there is a non standard C-function that copies a string like this: char *str = strdup("Hello");. In this case, you should use free(str) even though you did not manually invoke malloc.
So if you do a declaration on the form T t[10] or T[] t={a,b,c} you do NOT deallocate this.
int main()
{
int x;
std::cin << x;
T *a = new T;
delete a;
T *b = new T[x];
delete[] b;
T *c = (T*)malloc(x*sizeof(*c)); // Casting is needed in C++ but not in C
free(c);
// No free or delete for these
T d;
T e[10];
// Assuming a, b and c are instances of T or that you have overloaded the
// operator so that you can write T t=a;
// In either case, you should not free nor delete this
T f[] = {a, b, c};
}
In C, you can use VLA:s like int arr[x] where x is not known at compile time. That's not possible in C++. You don't use free on these arrays either.
I don't think you do, because there's no new so it's on the stack, but I'm not 100% sure that's right.
This is not correct. The standard does not dictate if the objects should be on the stack or the heap. It's up to the compiler, so what happens in reality is a question of how compilers usually solves the task.
Furthermore, the "fact" that statically allocated memory ends up on the stack and dynamically allocated memory ends up on the heap is only true most of the time. Granted, this is usually what happens, but there are some rare cases when it's not. And these occurrences are not so rare or obscure that they can be ignored. Global variables typically does not end up on the stack.
So if memory should be freed/deleted is not a question of if it lives in the stack or the heap. It's about how it has been allocated.
And in modern C++ you very rarely use new, new[], delete or delete[] at all. Instead, you use smart pointers and containers. Look at this link:
What is a smart pointer and when should I use one? and also https://en.cppreference.com/w/cpp/container/vector If you are dealing with old legacy C++, it might be a good idea to refactor it to modern memory management. If you change a new-delete pair to a smart pointer, then you have saved a line. If you change a new without a corresponding delete, then you have solved a bug.
TL;DR
When do you need to delete C-style arrays?
For the case T a[n] (with or without initialization) you should never do it. For the case T *a = new T[n] you should always do it. Whenever you have used new you should delete it afterwards, and never otherwise. (There may be some rare exceptions to this. See the rest of the post.)
Also, AFIK, there is no established definition of "C-style arrays". Personally, I would not consider T *a = new T[n] an array at all. I would not consider T *a = malloc(n*sizeof(*a)) an array either. The reason is simple. Pointers and arrays are NOT the same thing, and this goes for both C and C++. This question is about C, but is applicable for C++ too: Is an array name a pointer?

I commented that, as good practice, you should add delete[] to the
second example, as this is the bit people forget leading to memory
leaks.
You also should add delete[] in order to get destructors called. In addition to releasing some internally allocated memory, destructors may have other side effects.
Semantically, new and delete (and new[] and delete[]) come in pairs. new starts the object lifetime, and delete ends the object lifetime started by new. However, usually it is advisable to make deletion hidden inside a smart pointer.
The idea is to bind object lifetimes to the notion of "object ownership". Static objects are owned by the whole program. Automatic objects are owned by the code block they are defined in. Dynamic objects have no clearly defined ownership in C++ language itself, although the language gives the tools to implement the notion of object ownership by the writers of the code.
Smart pointers are one of these tools. std::unique_ptr is used when at every moment of time the object has only one owner, but you may want to pass ownership between blocks of code. std::shared_ptr is used when there can be multiple owners, and the object lifetime should end when the last owner stops to be interested in the object.

Related

A pointer that can point to anywhere, how to determine if "delete" can be safely called on it?

Is there any way to distinguish two following situations at run time:
double ptr * = new double(3.14159);
double variable = 3.14159
double * testPtr_1 = ptr;
double * testPtr_2 = &variable;
delete testPtr_1 // fine...
delete testPtr_2 // BIG RUN TIME ERROR !!!
I have find myself in situation in with I need to call delete operator for some unknown pointer. The pointer can point to anywhere (to a "local" variable or to dynamically allocated variable).
How can I find out where my "unknown" pointer points, and therefore choose when to and when not to call operator delete on it
EDIT:
Ok I see that everyone is pointing me to the smart pointers, but what if I am trying to write my own set of smart pointers (that is The reason behind my question)?
There is no way to test if a pointer is pointing to a memory area that would be valid to delete. Moreover,
There is no way to tell between pointers that must be freed with delete vs. delete[],
There is no way to tell between the pointers that have been freed and pointers that have not been freed,
There is no way to tell among pointers to an automatic variable, pointers to static variable, and pointers to dynamically allocated blocks.
The approach that you should take is tracking allocations/deallocations by some other means, such as storing flags along with your pointers. However, this is rather tedious: a much better practice is to switch to smart pointers, which would track resources for you.
You need to set some better coding practices for yourself (or for your project).
Especially since most platforms have, at the very least, a C++11-compliant compiler, there's no reason not to be using the following paradigm:
Raw Pointers (T*) should ONLY be used as non-owning pointers. If you receive a T* as the input for a function or constructor, you should assume you have no responsibility for deleting it. If you have an instance or local variable that is a T*, you should assume you have no responsibility for deleting it.
Unique Pointers (std::unique_ptr<T>) should be used as single-ownership pointers, and in general, these should be your default go-to choice for any situation where you need to dynamically allocate memory. std::make_unique<T>() should be preferred for creating any kind of Unique Pointer, as this prevents you from ever seeing the raw pointer in use, and it prevents issues like you described in your original post.
Shared Pointers (std::shared_ptr<T> and std::weak_ptr<T>) should ONLY be used in situations where it is logically correct to have multiple owners of an object. These situations occur less often than you think, by the way! std::make_shared<T>() is the preferred method of creating Shared Pointers, for the same reasons as std::make_unique, and also because std::make_shared can perform some optimizations on the allocations, improving performance.
Vectors (std::vector<T>) should be used in situations where you need to allocate multiple objects into heap space, the same as if you called new T[size]. There's no reason to use pointers at all except in very exotic situations.
It should go without saying that you need to take my rules of "ONLY do 'x'" with a grain of salt: Occasionally, you will have to break those rules, and you might be in a situation where you need a different set of rules. But for 99% of use-cases, those rules are correct and will best convey the semantics you need to prevent memory leaks and properly reason about the behavior of your code.
You cannot.
Avoid raw pointers and use smart pointers, particularly std::unique_ptr. It conveys clearly who is responsible for deleting the object, and the object will be deleted when the std::unique_ptr goes out of scope.
When creating objects, avoid using new. Wrap them in a smart pointer directly and do not take addresses of anything to wrap it in a smart pointer. This way, all raw pointers will never need freeing and all smart pointers will get cleaned up properly when their time has come.
Okay, some things you can distinguish in a very platform-specific, implementation-defined manner. I won’t go into details here, because it’s essentially insane to do (and, again, depends on the platform and implementation), but you are asking for it.
Distinguish local, global and heap variables. This is actually possible on many modern architectures, simply because those three are different ranges of the address space. Global variables live in the data section (as defined by the linker and run-time loader), local variables on the stack (usually at the end of the address space) and heap variables live in memory obtained during run-time (usually not at the end of the address space and of course not overlapping the data and code sections, a.k.a. "mostly everything else"). The memory allocator knows which range that is and can tell you details about the blocks in there, see below.
Detect already-freed variables: you can ask the memory allocator that, possibly by inspecting its state. You can even find out when a pointer points into a allocated region and then find out the block to which it belongs. This is however probably computationally expensive to do.
Distinguishing heap and stack is a bit tricky. If your stack grows large and your program is running long and some piece of heap has been returned to the OS, it is possible that an address which formerly belonged to the heap now belongs to the stack (and the opposite may be possible too). So as I mentioned, it is insane to do this.
You can't reliably. This is why owning raw pointers are dangerous, they do not couple the lifetime to the pointer but instead leave it up to you the programmers to know all the things that could happen and prepare for them all.
This is why we have smart pointers now. These pointers couple the life time to the pointer which means the pointer is only deleted once it is no longer in use anywhere. This makes dealing with pointer much more manageable.
The cpp core guildlines suggests that a raw pointer should never be deleted as it is just a view. You are just using it like a reference and it's lifetime is managed by something else.
Ok I see that everyone is pointing me to the smart pointers, but what if I am trying to write my own set of smart pointers (that is The reason behind my question)?
In that case do like the standard smart pointers do and take a deleter which you default to just using delete. That way if the user of the class wants to pass in a pointer to a stack object they can specify a do nothing deleter and you smart pointer will use that and, well, do nothing. This puts the onus on the person using the smart pointer to tell the pointer how to delete what it points to. Normally they will never need to use something other than the default but if they happen to use a custom allocator and need to use a custom deallocator they can do so using this method.
Actually you can. But memory overhead occurs.
You overload new and delete operator and then keep track of allocations and store it somewhere(void *)
#include<iostream>
#include<algorithm>
using namespace std;
void** memoryTrack=(void **)malloc(sizeof(void *)*100); //This will store address of newly allocated memory by new operator(really malloc)
int cnt=0;//just to count
//New operator overloaded
void *operator new( size_t stAllocateBlock ) {
cout<<"in new";
void *ptr = malloc(stAllocateBlock); //Allocate memory using malloc
memoryTrack[cnt] = ptr;//Store it in our memoryTrack
cnt++; //Increment counter
return ptr; //return address generated by malloc
}
void display()
{
for(int i=0;i<cnt;i++)
cout<<memoryTrack[i]<<endl;
}
int main()
{
double *ptr = new double(3.14159);
double variable = 3.14159;
double * testPtr_1 = ptr;
double * testPtr_2 = &variable;
delete testPtr_1; // fine...
delete testPtr_2;
return 0;
}
Now the most important function(You will have to work on this because it is not complete)
void operator delete( void *pvMem )
{
//Just printing the address to be searched in our memoryTrack
cout<<pvMem<<endl;
//If found free the memory
if(find(memoryTrack,memoryTrack+cnt,pvMem)!=memoryTrack+cnt)
{
//cout<<*(find(memoryTrack,memoryTrack+cnt,pvMem));
cout<<"Can be deleted\n";
free (pvMem);
//After this make that location of memoryTrack as NULL
//Also keep track of indices that are NULL
//So that you can insert next address there
//Or better yet implement linked list(Sorry was too lazy to do)
}
else
cout<<"Don't delete memory that was not allocated by you\n";
}
Output
in new
0xde1360
0xde1360
Can be deleted
0xde1360
0x7ffe4fa33f08
Dont delete memory that was not allocated by you
0xde1360
Important Node
This is just basics and just code to get you started
Open for others to edit and make necessary changes/optimization
Cannot use STL, they use new operator(if some can implement them please do,it would help to reduce and optimize the code)

Should I apply RAII to all arrays I allocated?

I'm studying C++ now. It is a so complicated language that I'm not sure which feature I should use and when to use.
C++ Primer introduces RAII as a method to ensure exception safety. Does that mean, as a good behavior, when I want to use an array, I should put array into an class to allocate and destroy resources. I know my idea is very simple, or naive.
I'm just curious about what is good C++ coding behavior.
RAII means that a resource is tied to the object lifetime.
Every STL class of C++ follows this principle, this means that if you need an array you can simply use std::vector.
The destructor of the vector class will take care of deleting the resources when your instance goes out of the scope.
This means that in your case, instead of using new like this:
int *array = new int[n];
you should use:
vector<int> array(n);
If you really need to allocate on the heap to have a shared pointer and still be safe by using RAII you can do (requires C++11):
shared_ptr<vector<int>> array(new vector<int>(10));
It depends how you create your array. If you create it in some block of code like this
int arr[5];
it has automatic storage. This array is automatically destroyed when it goes out of scope.
The only time you have to explicitly manage the lifetime of your objects is if you dynamically allocate them, perhaps like so:
int* arr = new int[5];
This allocates an array of 5 ints which need to be manually deleted later like this:
delete[] arr;
RAII is the idea that we can have a class's constructor perform the dynamic allocation and its destructor perform the deallocation. Then if we create objects of this class type, they will internally perform dynamic allocation, but we can be sure that the memory will be correctly deallocated when finished with.
So yes, if you really want a dynamically allocated array, you could encapsulate it in your own class. However, this already exists in a more powerful form: std::vector. The std::vector type internally manages a dynamically allocated array, but also allows you to add elements to and remove elements from it at run-time.
Note that this isn't something specific to arrays. Best practice is to encapsulate all dynamic allocation (even a simple new int) within classes. The standard library provides many classes to help you do this (in particular, smart pointers).
RAII is a recommended idiom for resource management -- resources can be threads, memory... I frequently encounter long-time developers who don't understand RAII or how to use it, so there's no shame in asking. Unfortunately, I think you need a little background and terminology to understand it. In C++ you can create objects in one of two kinds of memory: The stack and the heap.
The Stack:
Consider this code:
class Foo
{
// some implementation
}
void bar()
{
Foo f;
// do something with f.
// maybe it throws an exception, maybe not.
}
Here the object f of type Foo has been created on the stack. When the running program leaves the scope in which f was created (in our example when it leaves the function bar()), f's destructor will called, regardless of how it leaves the scope. It might leave scope because the function executed successfully, or it might leave scope because an exception was thrown.
Heap allocation
Now consider the following:
class Foo
{
// same thing
}
void bar()
{
Foo* f = new f;
// whatever
delete f;
}
In this case we created the object f on the heap. You can tell we did so because we called the new operator. Whenever an object is created with new, we have to call delete in order to free the memory up. This can be error prone, because people forget to call delete, or they return the pointer and it's unclear where the pointer should get deleted, or because an exception is thrown. RAII is a solution to all those, and it should be your first tool of choice.
But that doesn't mean you should put your arrays in a class (well it kind of does in some circumstances, but probably not in the way that you mean). Consider the following:
void foo()
{
// c-style array, but fixed size.
int bar_stack[5];
// c-style array, dynamically allocated!
unsigned int array_size = get_array_size();
int* bar_heap = new int[array_size]
// c++ way:
std::vector bar_vector(array_size);
}
In the first example we have a fixed c-style array, it's allocated on the stack, so it's nothing to worry about. In the second case, it's dynamically allocated (the compiler doesn't know the size, its available at run time), so we would need a delete, meaning we'd need to protect against exceptions and make sure there's a clear owner. In the third case we just use a vector, which is a standard container.
Now I provided all that because I was afraid you wouldn't understand the short answer which is: Yes you should generally use RAII to manage your resources, but you should use std containers, not arrays. Why? Because std containers provide RAII for you -- unless you make a container of pointers, or allocate your vector on the heap.
I hope that helps.

How best to allocate dynamic memory in c++

which is better for memory allocation in c++ for classes and template. malloc() or new; can malloc() be used to allocate memory for classes or template?
It is possible to do this, although not the way you do it, and in any case it's not really beginner stuff. The reason your approach does not work is that an uninitialized slab of memory is not the same as a valid object. But before I continue:
IMPORTANT STYLE NOTE: I am going to leave aside for the sake of brevity the usual memory management mechanics (smart pointers et al) that you should stick to religiously as a beginner (and, in fact, as an expert, but they don't need to be told). Do not use code like what I'm about to show here in production (not without all the necessary bookkeeping around it), or things will break in ways that are horrible to debug. I'm only doing it because doing it properly would obscure the mechanics I'll try to show.
The difference between a slab of uninitialized memory and a valid object are, in a broad sense, class invariants. What this means is that a class defines certain properties for its objects that these always fulfill. For example, a string class will generally guarantee that the pointer to its data will not be a dangling pointer, and that its size will not reported to be larger than the block of memory that pointer points to. An uninitialized slab of memory cannot guarantee either of those things, and that is why we have constructors. A constructor is a function that constructs an object into an uninitialized slab of memory, fixing these invariants, and most of the time it is called for you automatically. If you write
Foo f;
memory is obtained from the stack (simplified terminology; please bear with me, language lawyers), and the default constructor Foo::Foo() is called on that piece of memory. Similarly, if you write
Foo *p = new Foo();
operator new obtains memory from the heap and calls the constructor on that piece of memory. Possibly it does more than that, but let's leave that possibility aside for now.
So what goes wrong in your code? Well, when you write
*obj = A();
this is an assignment. It assumes that there is a valid object *obj that a newly constructed object can be copied into. This is not the case, because obj points to an uninitialized slab of memory.
So, before you can use your uninitialized slab of memory like an object, you have to construct one into it. This is possible, and it is called placement new. The syntax looks like this:
new(obj) A();
And later you have to call the destructor (the constructor's counterpart; its purpose is to undo what the constructor did) manually like so:
obj->~A();
...after which obj will again point to uninitialized memory, and (unless the class is buggy) all necessary cleanup has been done. Except giving back the slab of memory, because that was allocated outside the constructor/destructor duo. Normally it would happen automatically at the end of the scope (for automatic variables like f above) or delete would do it for you (for dynamic storage objects like *p above), but since you did the allocation yourself, you have to do the deallocation yourself.
The use of malloc() for allocatiog C++ objects shall be avoided.
Rewrite your main() using C++ practice:
A *obj = new A(); // get memory and initialize the object
B<int> *objb = new B<int>(); // same
This works better and is easier to read !
Remark: Statements like *obj=A(); may call a class specific assignment operator, which would assume that *obj was already initialized by a constructor and is in a valid state. If memory was simply aquired by malloc(), this assumption is not fulfilled.

Malloc function in C++

I am transitioning to C++ from C. In C++, is there any use for the malloc function? Or can I just declare it with the "new" keyword. For example:
class Node
{
...
}
...
Node *node1 = malloc(sizeof(Node)); //malloc
Node *node2 = new Node; //new
Which one should I use?
Use new. You shouldn't need to use malloc in a C++ program, unless it is interacting with some C code or you have some reason to manage memory in a special way.
Your example of node = malloc(sizeof(Node)) is a bad idea, because the constructor of Node (if any exists) would not be called, and a subsequent delete node; would have undefined results.
If you need a buffer of bytes, rather than an object, you'll generally want to do something like this:
char *buffer = new char[1024];
or, preferably, something like this:
std::vector<char> buffer(1024);
Note that for the second example (using std::vector<>), there is no need to delete the object; its memory will automatically be freed when it goes out of scope. You should strive to avoid both new and malloc in C++ programs, instead using objects that automatically manage their own memory.
The direct equivalent of malloc() in C++ is operator new() which also allocates raw memory, however in most cases a new expression is what you want. A new expression both allocates an appropriate amount of raw memory and initializes an object in that memory location, returning a correctly typed pointer to the new object.
In your case , new Node is correct as it allocates memory and initializes a new Node object. Simply calling malloc and casting result to a pointer to Node won't correctly construct the Node object. This is critical if Node is not a POD-struct (e.g. when it or one of its sub-objects has a constructor that should be called).
You should avoid dynamic allocation where it is not needed; where it is needed, it is often best to initialize some sort of smart pointer with the address of the dynamically allocated object so that it's not possible to 'forget' to delete the obejct.
One of the primary differences between new and malloc is that new will call the object's constructor.
Another difference is that new will throw an exception (may be circumvented by a compiler pragma) if the memory cannot be succesfully allocated. The malloc may cause a system signal to be generated. Although some C++ libraries implement new by calling malloc.
There may be a few instances where objects need to be dynamically allocated without invoking their constructors. In over 20 years, I haven't come across any (not even in the embedded systems arena).
Well, the one thing I can think of, if you're using new you're going to miss realloc if you end up needing it.
My habit is to use malloc() for primitive types and C compatible structs, and new for everything else.
In the old days this gave better interop with C library code. On Linux, this is still true. On Windows, you need to keep the habit of the library that allocates it frees it.
They key situation in which you must use malloc is if the original code ever calls realloc. Of course you can reimplement everything needed, but there isn't that much advantage in doing so.
In general, use new except when interfacing with C code.
The key point on this is that what is allocated with new must be freed with delete, and what is allocated with malloc must be freed with free. You cannot allocate with new and free with free() or vice-versa. So, about the only time you need malloc is when you need to pass data off to some C code that might free() or realloc() it.

Why is there a special new and delete for arrays?

What is wrong with using delete instead of delete[]?
Is there something special happening under the covers for allocating and freeing arrays?
Why would it be different from malloc and free?
Objects created with new[] must use delete[]. Using delete is undefined on arrays.
With malloc and free you have a more simple situation. There is only 1 function that frees the data you allocate, there is no concept of a destructor being called either. The confusion just comes in because delete[] and delete look similar. Actually they are 2 completely different functions.
Using delete won't call the correct function to delete the memory. It should call delete[](void*) but instead it calls delete(void*). For this reason you can't rely on using delete for memory allocated with new[]
See this C++ FAQ
[16.13] Can I drop the [] when
deleteing array of some built-in type
(char, int, etc)?
No!
Sometimes programmers think that the
[] in the delete[] p only exists so
the compiler will call the appropriate
destructors for all elements in the
array. Because of this reasoning, they
assume that an array of some built-in
type such as char or int can be
deleted without the []. E.g., they
assume the following is valid code:
void userCode(int n) {
char* p = new char[n];
...
delete p; // ← ERROR! Should be delete[] p !
}
But the above code is wrong, and it
can cause a disaster at runtime. In
particular, the code that's called for
delete p is operator delete(void*),
but the code that's called for
delete[] p is operator
delete[](void*). The default behavior
for the latter is to call the former,
but users are allowed to replace the
latter with a different behavior (in
which case they would normally also
replace the corresponding new code in
operator new[](size_t)). If they
replaced the delete[] code so it
wasn't compatible with the delete
code, and you called the wrong one
(i.e., if you said delete p rather
than delete[] p), you could end up
with a disaster at runtime.
Why does delete[] exist in the first place?
Whether you do x or y:
char * x = new char[100];
char * y = new char;
Both are stored in char * typed variables.
I think the reason for the decision of delete, and delete[] goes along with a long list of decisions that are in favor of efficiency in C++. It is so that there is no enforced price to do a lookup of how much needs to be deleted for a normal delete operation.
Having 2 new and new[] seems only logical to have delete and delete[] anyway for symmetry.
The difference is that delete will only delete the entire memory range, but will only call the destructor for 1 object. delete[] will both delete the memory and call the destructor for every single object. If you do not use delete[] for arrays, it's only a matter of time before you introduce a resource leak into your application.
EDIT Update
According to the standard, passing an object allocated with new[] to delete is undefined. The likely behavior is that it will act as I described.
Stroustrup talks about the reasons for separate new/new[] and delete/delete[]` operators in "The Design and Evolution of C++" in sections 10.3 through 10.5.1:
10.3 Array Allocation - discusses that they wanted a way to allow arrays of objects to be allocated using a separate scheme from allocation single objects (ie., allocating arrays from a separate store). Adding the array versions of new and delete was a solution for this;
10.5.1 Deallocating Arrays - discusses how a problem with deallocating arrays using just a single delete operator is that there needs to be more information than just the pointer in order to determine if the pointer points to the first element of an array or if it just points to a single object. Instead of "complicating the common case of allocating and deallocating individual objects", the delete[] operator is used to handle arrays. This fits in with the general C++ design philiosophy of "don't pay for what you don't use".
Whether this decision was a mistake or not is debatable - either way has good arguments, but we have what we have.
The reason for this requirement is historical and because new type and new type [size] return different things that need to be cleaned up differently.
Consider this code
Foo* oneEntry = new Foo;
Foo* tenEntries = new Foo[10];
These both return a Foo* pointer, the difference is the second call will result in the Foo constructor being called 10x, and there being roughly 10x as much memory.
So now you want to free your objects.
For a single object you would call delete - e.g. delete oneEntry. This calls the objects destructor and and deallocates the memory.
But here's the problem - oneEntry and tenEntries are both just Foo pointers. The compiler has no idea whether they point to one, ten, or a thousand elements.
When you use the special syntax of delete []. This tells the compiler "this is an array of objects, figure out the count and then destruct them all".
What really happens is that for new type [size] the compiler secretly stores 'size' somewhere else. When you call delete[] it knows that this secret value exists so it can find out how many objects are in that block of memory and destruct them.
The question you could then ask is "why doesn't the compiler always store the size?"
That's a great question and it dates back to the early days of C++. There was a desire that for built-in types (char, int, float, etc) the following would be valid for C++;
int* ptr = new int;
free(ptr);
int* ptr = (int*)malloc(sizeof(int) * someSize);
delete ptr;
The reasoning behind this was an expectation that people would provide libraries that returned dynamically allocated memory, and users of these libraries would have no way of knowing whether to use free/delete.
This desire for compatibility meant that the size of an array could not be stored as part of the array itself and had to be kept elsewhere. Because of this overhead (and remember, this was back in the early 80's) it was decided to do this book keeping only for arrays and not single-elements. Thus arrays need a special delete syntax that looks up this value.
The reason malloc/free do not have this problem is that they simply deal with blocks of memory and do not have to worry about calling constructors/destructors.
As to the "why" in the title: one of the design goals of C++ was that there wouldn't be any hidden costs. C++ was also developed at a time when every byte of memory still mattered a whole lot more than it does today. Language designers also like orthogonality: if you allocate the memory with new[] (instead of new), you should free it with delete[].
I don't think there's any technical reason that new[] couldn't stick an "I'm an array" flag in the header of the memory block for delete (no more delete[]) to look at later.
new and delete are different from malloc and free in that malloc and free only allocate and free memory; they don't call ctors or dtors.
When you use new[] to allocate an array, you are actually telling C++ the size of the array. When you use malloc, you are instead telling it how much memory is allocated. In the former case, freeing based on the size of the array would not make sense. In this case, it does. But since there is no difference between a pointer for an array vs. for a single object, a separate function is needed.