Why is ::operator new[] necessary when ::operator new is enough? - c++

As we know, the C++ standard defines two forms of global allocation functions:
void* operator new(size_t);
void* operator new[](size_t);
And also, the draft C++ standard (18.6.1.2 n3797) says:
227) It is not the direct responsibility of operator
new or operator delete to note the repetition
count or element size of the array. Those operations are performed
elsewhere in the array new and delete expressions. The array new
expression, may, however, increase the size argument to operator
new to obtain space to store supplemental information.
What makes me confused is:
What if we remove void* operator new[](size_t); from the standard, and just use void* operator new(size_t) instead? What's the rationale to define a redundant global allocation function?

I think ::operator new[] may have been useful for fairly specialized systems where "big but few" arrays might be allocated by a different allocator than "small but numerous" objects. However, it's currently something of a relic.
operator new can reasonably expect that an object will be constructed at the exact address returned, but operator new[] cannot. The first bytes of the allocation block might be used for a size "cookie", the array might be sparsely initialized, etc. The distinction becomes more meaningful for member operator new, which may be specialized for its particular class.
In any case, ::operator new[] cannot be very essential, because std::vector (via std::allocator), which is currently the most popular way to obtain dynamic arrays, ignores it.
In modern C++, custom allocators are generally a better choice than customized operator new. Actually, new expressions should be avoided entirely in favor of container (or smart-pointer, etc) classes, which provide more exception safety.

::operator new[] and ~delete[] facilitate memory usage debugging, being a central point to audit allocation and deallocation operations; you can then ensure the array form is used for both or neither.
There are also lots of plausible if highly unusual/crude tuning uses:
allocate arrays from a separate pool, perhaps because that crucially improved average cache hits for small single-object dynamically-allocated objects,
different memory access hints (ala madvise) for array/non-array data
All that's a bit weird and outside the day-to-day concerns of 99.999% of programmers, but why prevent it being possible?

The standard (n3936) makes it clear that these two operators serve different but related purposes.
operator new calls the function void* operator new(std::size_t). The first argument must be identical to the argument to the operator. It returns a block of storage suitably aligned, and which may be somewhat larger than required.
operator new[] calls the function void* operator new[](std::size_t). The first argument may be larger than the argument supplied to the operator, to provide extra storage space if required for array indexing. The default implement for both is to simply call malloc().
The purpose of operator new[] is to support specialised array indexing, if available. It has nothing to do with memory pools or anything else. In a conforming implementation that made use of this feature, the implementation would set up specialised tables in the extra space and the compiler would generate code for instructions or calls to library library support routines that made use of those tables. C++ code using arrays and failing to use new[] would fail on those platforms.
I am not personally aware of any such implementation, but it resembles the kind of features required for the support of certain mainframes (CDC, IBM, etc) which have an architecture quite unlike the Intel or RISC chips we know and love.
In my opinion, the accepted answer is incorrect.
Just for completeness, the standard (n3936 mostly in S5.3.4) contains the following.
A distinction between allocating an 'array object' or a 'non-array object'
References to 'array allocation overhead', with the implication that extra storage might be needed and it might (somehow) be used for a repetition count or element size.
There is no reference to memory pools or any hint that this might be a consideration.

I'm sure there are proper use-cases out there that require separate new[] and new, but I haven't encountered one yet that is uniquely possible with this separation and nothing else.
However, I see it like this: since the user calls different versions of operator new, the C++ standard would have been guilty of wantonly and deliberately losing information if they'd defined just one operator new and had both new and new[] forward there. There is (literally) one bit of information here, that might be useful to somebody, and I don't think people on the committee could have thrown it out in good conscience!
Besides, having to implement the extra new[] is a very very minor inconvenience to the rest of us, if at all, so the trade off of preserving a single bit of information wins against having to implement a single simple function in a small fraction of our programs.

The C++ Programming Language: Special Edition p 423 says
_The operator new() and operator delete() functions allow a user to take over allocation and deallocation of individual objects; operator new[]() and operator delete[]() serve exactly the same role for the allocation and deallocation of arrays.
Thanks Tony D for correcting my misunderstanding of this nuance.
Wow, it's not often I'm caught out on something in C++ I'm so certain about - I must have been spending too much time in Objective-C!
original wrong answer
It's simple - the new[] form invokes the constructor on every element of a classic C array.
So it first allocates the space for all the objects, then iterates calling the constructor for each slot.

Related

How to allocate a buffer suitable to hold an object of type T (which may be over-aligned, may have operator new, etc)

In a library I'm working on, I need to allocate space that will eventually be passed to placement-new to construct objects of arbitrary types. Those instances will be returned to the caller code, and need to behave properly with the user just writing delete t; or having whatever unique_ptr analog they're using do it for them.
The naive approach of T *t = new char[sizeof(T)] has at least a few problems that I've run noticed:
If T is declared with an alignas specifier, then I can't manually pad the buffer accordingly, because the resulting address won't be the right one to pass to delete
The expression delete t; will be a mismatch between operator delete() and operator delete[]()
If the code declares a custom T::operator new and T::operator delete (for memory pooling, segregation, tracking, whatever), then we neglect to call the allocator, and mis-match on the call to the de-allocator
Seemingly much better is T *t = std::allocator<T>::allocate(1), which addresses the alignment concern in point 1 and the scalar/array mismatch concern in point 2. However, per cppreference, it always calls the global ::operator new. Thus, it would still fail on point 3.
Is the reference wrong? Is there some slightly higher-level routine in the standard library I'm missing that does the right thing? Is there some trick to implementing the right thing correctly without nasty template code to check whether T::operator new exists?
I think you want to follow the same rules a new-expression uses to determine the correct allocation function to call, but I don't know any way off the top of my head to automatically produce that call. It's fairly straightforward in principle (I think) to write the usual family of templates to detect when the class-specific overloads are/are not present and branch of alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__ but I agree it's tedious.

Program with realloc() works just fine but at the end it returns error -1073741819. Can someone please find my mistake in this code? [duplicate]

From what is written here, new allocates in free store while malloc uses heap and the two terms often mean the same thing.
From what is written here, realloc may move the memory block to a new location. If free store and heap are two different memory spaces, does it mean any problem then?
Specifically I'd like to know if it is safe to use
int* data = new int[3];
// ...
int* mydata = (int*)realloc(data,6*sizeof(int));
If not, is there any other way to realloc memory allocated with new safely? I could allocate new area and memcpy the contents, but from what I understand realloc may use the same area if possible.
You can only realloc that which has been allocated via malloc (or family, like calloc).
That's because the underlying data structures that keep track of free and used areas of memory, can be quite different.
It's likely but by no means guaranteed that C++ new and C malloc use the same underlying allocator, in which case realloc could work for both. But formally that's in UB-land. And in practice it's just needlessly risky.
C++ does not offer functionality corresponding to realloc.
The closest is the automatic reallocation of (the internal buffers of) containers like std::vector.
The C++ containers suffer from being designed in a way that excludes use of realloc.
Instead of the presented code
int* data = new int[3];
//...
int* mydata = (int*)realloc(data,6*sizeof(int));
… do this:
vector<int> data( 3 );
//...
data.resize( 6 );
However, if you absolutely need the general efficiency of realloc, and if you have to accept new for the original allocation, then your only recourse for efficiency is to use compiler-specific means, knowledge that realloc is safe with this compiler.
Otherwise, if you absolutely need the general efficiency of realloc but is not forced to accept new, then you can use malloc and realloc. Using smart pointers then lets you get much of the same safety as with C++ containers.
The only possibly relevant restriction C++ adds to realloc is that C++'s malloc/calloc/realloc must not be implemented in terms of ::operator new, and its free must not be implemented in terms of ::operator delete (per C++14 [c.malloc]p3-4).
This means the guarantee you are looking for does not exist in C++. It also means, however, that you can implement ::operator new in terms of malloc. And if you do that, then in theory, ::operator new's result can be passed to realloc.
In practice, you should be concerned about the possibility that new's result does not match ::operator new's result. C++ compilers may e.g. combine multiple new expressions to use one single ::operator new call. This is something compilers already did when the standard didn't allow it, IIRC, and the standard now does allow it (per C++14 [expr.new]p10). That means that even if you go this route, you still don't have a guarantee that passing your new pointers to realloc does anything meaningful, even if it's no longer undefined behaviour.
In general, don't do that. If you are using user defined types with non-trivial initialization, in case of reallocation-copy-freeing, the destructor of your objects won't get called by realloc. The copy constructor won't be called too, when copying. This may lead to undefined behavior due to an incorrect use of object lifetime (see C++ Standard §3.8 Object lifetime, [basic.life]).
1 The lifetime of an object is a runtime property of the object. An object is said to have non-trivial initialization if it is of a class or aggregate type and it or one of its members is initialized by a constructor other than a trivial default constructor. [ Note: initialization by a trivial copy/move constructor is non-trivial initialization. —end note ]
The lifetime of an object of type T begins when:
— storage with the proper alignment and size for type T is obtained, and
— if the object has non-trivial initialization, its initialization is complete.
The lifetime of an object of type T ends when:
— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
— the storage which the object occupies is reused or released.
And later (emphasis mine):
3 The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime.
So, you really don't want to use an object out of its lifetime.
It is not safe, and it's not elegant.
It might be possible to override new/delete to support the reallocation, but then you may as well consider to use the containers.
In general, no.
There are a slew of things which must hold to make it safe:
Bitwise copying the type and abandoning the source must be safe.
The destructor must be trivial, or you must in-place-destruct the elements you want to deallocate.
Either the constructor is trivial, or you must in-place-construct the new elements.
Trivial types satisfy the above requirements.
In addition:
The new[]-function must pass the request on to malloc without any change, nor do any bookkeeping on the side. You can force this by replacing global new[] and delete[], or the ones in the respective classes.
The compiler must not ask for more memory in order to save the number of elements allocated, or anything else.
There is no way to force that, though a compiler shouldn't save such information if the type has a trivial destructor as a matter of Quality of Implementation.
Yes - if new actually called malloc in the first place (for example, this is how VC++ new works).
No otherwise. do note that once you decide to reallocate the memory (because new called malloc), your code is compiler specific and not portable between compilers anymore.
(I know this answer may upset many developers, but I answer depends on real facts, not just idiomaticy).
That is not safe. Firstly the pointer you pass to realloc must have been obtained from malloc or realloc: http://en.cppreference.com/w/cpp/memory/c/realloc.
Secondly the result of new int [3] need not be the same as the result of the allocation function - extra space may be allocated to store the count of elements.
(And for more complex types than int, realloc wouldn't be safe since it doesn't call copy or move constructors.)
You may be able to (not in all cases), but you shouldn't. If you need to resize your data table, you should use std::vector instead.
Details on how to use it are listed in an other SO question.
These function is mostly used in C.
memset sets the bytes in a block of memory to a specific value.
malloc allocates a block of memory.
calloc, same as malloc. Only difference is that it initializes the bytes to zero.
In C++ the preferred method to allocate memory is to use new.
C: int intArray = (int*) malloc(10 *sizeof(int));
C++: int intArray = new int[10];
C: int intArray = (int*) calloc(10 *sizeof(int));
C++: int intArray = new int10;

Malloc vs New for Primitives

I understand the benefits of using new against malloc in C++. But for specific cases such as primitive data types (non array) - int, float etc., is it faster to use malloc than new?
Although, it is always advisable to use new even for primitives, if we are allocating an array so that we can use delete[].
But for non-array allocation, I think there wouldn't be any constructor call for int? Since, new operator allocates memory, checks if it's allocated and then calls the constructor. But just for primitives non-array heap allocation, is it better to use malloc than new?
Please advise.
Never use malloc in C++. Never use new unless you are implementing a low-level memory management primitive.
The recommendation is:
Ask yourself: "do I need dynamic memory allocation?". A lot of times you might not need it - prefer values to pointers and try to use the stack.
If you do need dynamic memory allocation, ask yourself "who will own the allocated memory/object?".
If you only need a single owner (which is very likely), you should
use std::unique_ptr. It is a zero cost abstraction over
new/delete. (A different deallocator can be specified.)
If you need shared ownership, you should use std::shared_ptr. This is not a zero cost abstraction, as it uses atomic operations and an extra "control block" to keep track of all the owners.
If you are dealing with arrays in particular, the Standard Library provides two powerful and safe abstractions that do not require any manual memory management:
std::array<T, N>: a fixed array of N elements of type T.
std::vector<T>: a resizable array of elements of type T.
std::array and std::vector should cover 99% of your "array needs".
One more important thing: the Standard Library provides the std::make_unique and std::make_shared which should always be used to create smart pointer instances. There are a few good reasons:
Shorter - no need to repeat the T (e.g. std::unique_ptr<T>{new T}), no need to use new.
More exception safe. They prevent a potential memory leak caused by the lack of a well-defined order of evaluation in function calls. E.g.
f(std::shared_ptr<int>(new int(42)), g())
Could be evaluated in this order:
new int(42)
g()
...
If g() throws, the int is leaked.
More efficient (in terms of run-time speed). This only applies to std::make_shared - using it instead of std::shared_ptr directly allows the implementation to perform a single allocation both for the object and for the control block.
You can find more information in this question.
It can still be necessary to use malloc and free in C++ when you are interacting with APIs specified using plain C, because it is not guaranteed to be safe to use free to deallocate memory allocated with operator new (which is ultimately what all of the managed memory classes use), nor to use operator delete to deallocate memory allocated with malloc.
A typical example is POSIX getline (not to be confused with std::getline): it takes a pointer to a char * variable; that variable must point to a block of memory allocated with malloc (or it can be NULL, in which case getline will call malloc for you); when you are done calling getline you are expected to call free on that variable.
Similarly, if you are writing a library, it can make sense to use C++ internally but define an extern "C" API for your external callers, because that gives you better binary interface stability and cross-language interoperability. And if you return heap-allocated POD objects to your callers, you might want to let them deallocate those objects with free; they can't necessarily use delete, and making them call YourLibraryFree when there are no destructor-type operations needed is unergonomic.
It can also still be necessary to use malloc when implementing resizable container objects, because there is no equivalent of realloc for operator new.
But as the other answers say, when you don't have this kind of interface constraint tying your hands, use one of the managed memory classes instead.
It's always better to use new. If you use malloc you still have to check manually if space is allocated.
In modern c++ you can use smart pointers. With make_unique and make_shared you never call new explicitly. std::unique_ptr is not bigger than the underlying pointer and the overhead of using it is minimal.
The answer to "should I use new or malloc" is single responsibillity rule.
Resource management should be done by a type that has that as its sole purpose.
Those classes already exists, such as unique_ptr, vector etc.
Directly using either malloc or new is a cardinal sin.
zwol's answer already gives the correct correctness answer: Use malloc()/free() when interacting with C interfaces only.
I'm not going to repeat those details, I'm going to answer the performance question.
The truth is, that the performance of malloc() and new can, and does differ. When you perform an allocation with new, the memory will generally be allocated via call to the global operator new() function, which is distinct from malloc(). It is trivial to implement operator new() by calling through to malloc(), but this is not necessarily done.
As a matter of fact, I've seen a system where an operator new() that calls through to malloc() would outperform the standard implementation of operator new() by roughly 100 CPU cycles per call. That's definitely a measurable difference, and a clear indication that the standard implementation does something very different from malloc().
So, if you are worried about performance, there is three things to do:
Measure your performance.
Write replacement implementations for the global operator new() function and its friends.
Measure your performance and compare.
The gains/losses may or may not be significant.

What to do in class specific version of placement new?

Class-specific version of placement new can be provided even though you can't replace the global one. What scenarios exist where a class should provide its own placement new operator?
Even if my class don't implement placement new the following code works (assuming for abc no operator new is overloaded).
char arr[100];
abc *pt = new(&arr)abc;
So i interpret, there is some default placement new but for class we can provide our own version of operator new, my question is what is the use case for that?
What one is supposed to do other then returning the same pointer that is passed? Is there any useful example/scenario that you encountered?
Sounds like a quiz question...
Faster and Leaner Allocation
The most common reason is a lot of small objects that need to be allocated dynamically. A custom allocator for fixed-size objects has much less allocation overhead than a generic allocator, does not suffer from fragmentation, and is typically faster. (Also, when these allocations are removed from the main heap, they don't contribute to main heap fragmentation anymore).
Similary, a non-freeing allocator (where you can allocate multiple objects, but can't free them together, only in conjunction) is the fastest allocation scheme possible, and does not have any overhead (except alignment in a few rare cases). It makes sense if you are building a data structure that you never modify, only delete as a whole.
Other base allocator
Another application is allocating from a different heap than the C++ heap.
Maybe the data in the objects needs to be allocated in shared memory for exchange with other processes, or it needs to be passed to a system function that takes ownership and requries the use of a certain allocator. (Note that this requires to implement the same mechanism for all sub-objects, too, there is no generic way to achieve that).
Similary (where I use it) is when you create code on the fly. Nowadays, you need to tell the OS that data on this memory page is allowed to run, but you get this memory in rather large chunks (e.g. 4K). So again, request a page (4K) from the OS with execution rights, then allocate many small objects on top of it - using placement new.
Unfortunately, AFAIK, you cannot do a class specific overload of the standard placement new operator, only of custom placement new operators. So a use-case for it is a bit academic, but I wanted to use it to forbid placement new on the class by using = delete of C++11. This works great with standard operator new but not for placement new.
Straight from the horse's mouth wiki. The section titled 'Use' highlights the need for placement new.
This SO thread here might also help
UPDATE:
To specifically answer you question; You might use the standard placement new provided by header <new> if you have a pool of memory you want to use for constructing some objects of a class, but don't want to overload operator new for the whole class. In the latter case all the class objects are placed as per the overloaded placement new as defined in the class
i'm not sure that its possible to overload the placement new, only the regular new. i can't think of even a single use for that, since the only possible implementation is just creating a temp object and memcp'ing it to the given memory address - since you're not supposed to allocate any other memory in there, but use the given one.

Why [] is used in delete ( delete [] ) to free dynamically allocated array ?

I know that when delete [] will cause destruction for all array elements and then releases the memory.
I initially thought that compiler wants it just to call destructor for all elements in the array, but I have also a counter - argument for that which is:
Heap memory allocator must know the size of bytes allocated and using sizeof(Type) its possible to find no of elements and to call appropriate no of destructors for an array to prevent resource leaks.
So my assumption is correct or not and please clear my doubt on it.
So I am not getting the usage of [] in delete [] ?
Scott Meyers says in his Effective C++ book: Item 5: Use the same form in corresponding uses of new and delete.
The big question for delete is this: how many objects reside in the memory being deleted? The answer to that determines how many destructors must be called.
Does the pointer being deleted point to a single object or to an array of objects? The only way for delete to know is for you to tell it. If you don't use brackets in your use of delete, delete assumes a single object is pointed to.
Also, the memory allocator might allocate more space that required to store your objects and in this case dividing the size of the memory block returned by the size of each object won't work.
Depending on the platform, the _msize (windows), malloc_usable_size (linux) or malloc_size (osx) functions will tell you the real length of the block that was allocated. This information can be exploited when designing growing containers.
Another reason why it won't work is that Foo* foo = new Foo[10] calls operator new[] to allocate the memory. Then delete [] foo; calls operator delete[] to deallocate the memory. As those operators can be overloaded, you have to adhere to the convention otherwise delete foo; calls operator delete which may have an incompatible implementation with operator delete []. It's a matter of semantics, not just keeping track of the number of allocated object to later issue the right number of destructor calls.
See also:
[16.14] After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[] p?
Short answer: Magic.
Long answer: The run-time system stores the number of objects, n, somewhere where it can be retrieved if you only know the pointer, p. There are two popular techniques that do this. Both these techniques are in use by commercial-grade compilers, both have tradeoffs, and neither is perfect. These techniques are:
Over-allocate the array and put n just to the left of the first Fred object.
Use an associative array with p as the key and n as the value.
EDIT: after having read #AndreyT comments, I dug into my copy of Stroustrup's "The Design and Evolution of C++" and excerpted the following:
How do we ensure that an array is correctly deleted? In particular, how do we ensure that the destructor is called for all elements of an array?
...
Plain delete isn't required to handle both individual objects an arrays. This avoids complicating the common case of allocating and deallocating individual objects. It also avoids encumbering individual objects with information necessary for array deallocation.
An intermediate version of delete[] required the programmer to specify the number of elements of the array.
...
That proved too error prone, so the burden of keeping track of the number of elements was placed on the implementation instead.
As #Marcus mentioned, the rational may have been "you don't pay for what you don't use".
EDIT2:
In "The C++ Programming Language, 3rd edition", §10.4.7, Bjarne Stroustrup writes:
Exactly how arrays and individual objects are allocated is implementation-dependent. Therefore, different implementations will react differently to incorrect uses of the delete and delete[] operators. In simple and uninteresting cases like the previous one, a compiler can detect the problem, but generally something nasty will happen at run time.
The special destruction operator for arrays, delete[], isn’t logically necessary. However, suppose the implementation of the free store had been required to hold sufficient information for every object to tell if it was an individual or an array. The user could have been relieved of a burden, but that obligation would have imposed significant time and space overheads on some C++ implementations.
The main reason why it was decided to keep separate delete and delete[] is that these two entities are not as similar as it might seem at the first sight. For a naive observer they might seem to be almost the same: just destruct and deallocate, with the only difference in the potential number of objects to process. In reality, the difference is much more significant.
The most important difference between the two is that delete might perform polymorphic deletion of objects, i.e. the static type of the object in question might be different from its dynamic type. delete[] on the other hand must deal with strictly non-polymorphic deletion of arrays. So, internally these two entities implement logic that is significantly different and non-intersecting between the two. Because of the possibility of polymorphic deletion, the functionality of delete is not even remotely the same as the functionality of delete[] on an array of 1 element, as a naive observer might incorrectly assume initially.
Contrary to the strange claims made in some other answers, it is, of course, perfectly possible to replace delete and delete[] with just a single construct that would branch at the very early stage, i.e. it would determine the type of the memory block (array or not) using the household information that would be stored by new/new[], and then jump to the appropriate functionality, equivalent to either delete or delete[]. However, this would be a rather poor design decision, since, once again, the functionality of the two is too different. Forcing both into a single construct would be akin to creating a Swiss Army Knife of a deallocation function. Also, in order to be able to tell an array from a non-array we'd have to introduce an additional piece of household information even into a single-object memory allocations done with plain new. This might easily result in notable memory overhead in single object allocations.
But, once again, the main reason here is the functional difference between delete and delete[]. These language entities possess only apparent skin-deep similarity that exists only at the level of naive specification ("destruct and free memory"), but once one gets to understand in detail what these entities really have to do one realizes that they are too different to be merged into one.
P.S. This is BTW one of the problems with the suggestion about sizeof(type) you made in the question. Because of the potentially polymorphic nature of delete, you don't know the type in delete, which is why you can't obtain any sizeof(type). There are more problems with this idea, but that one is already enough to explain why it won't fly.
The heap itself knows the size of allocated block - you only need the address. Look like free() works - you only pass the address and it frees memory.
The difference between delete (delete[]) and free() is that the former two first call the destructors, then free memory (possibly using free()). The problem is that delete[] also has only one argument - the address and having only that address it need to know the number of objects to run destructors on. So new[] uses som implementation-defined way of writing somewhere the number of elements - usually it prepends the array with the number of elements. Now delete[] will rely on that implementation-specific data to run destructors and then free memory (again, only using the block address).
delete[] just calls a different implementation (function);
There's no reason an allocator couldn't track it (in fact, it would be easy enough to write your own).
I don't know the reason they did not manage it, or the history of the implementation, if I were to guess: Many of these 'well, why wasn't this slightly simpler?' questions (in C++) came down to one or more of:
compatibility with C
performance
In this case, performance. Using delete vs delete[] is easy enough, I believe it could all be abstracted from the programmer and be reasonably fast (for general use). delete[] only requires only a few additional function calls and operations (omitting destructor calls), but that is per call to delete, and unnecessary because the programmer generally knows the type he/she is dealing with (if not, there's likely a bigger problem at hand). So it just avoids calling through the allocator. Additionally, these single allocations may not need to be tracked by the allocator in as much detail; Treating every allocation as an array would require additional entries for count for trivial allocations, so it is multiple levels of simple allocator implementation simplifications which are actually important for many people, considering it is a very low level domain.
This is more complicated.
The keyword and the convention to use it to delete an array was invented for the convenience of implementations, and some implementations do use it (I don't know which though. MS VC++ does not).
The convenience is this:
In all other cases, you know the exact size to be freed by other means. When you delete a single object, you can have the size from compile-time sizeof(). When you delete a polymorphic object by base pointer and you have a virtual destructor, you can have the size as a separate entry in vtbl. If you delete an array, how would you know the size of memory to be freed, unless you track it separately?
The special syntax would allow tracking such size only for an array - for instance, by putting it before the address that is returned to the user. This takes up additional resources and is not needed for non-arrays.