Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting size of dynamic C-style array vs. use of delete[]. Contradiction? [duplicate]

Tags:

c++

arrays

I read everywhere that in C++ it is not possible to get the size of a dynamic array just from the pointer pointing to that chunk of memory.

How is it possible that there is no way of getting the size of a dynamic array just from the pointer, and at the same time it is possible to free all the memory allocated by using delete [] just on the pointer, without the need of specifying the array size?

delete [] must know the size of the array, right? Therefore this information must exist somewhere. Shouldn't it?

What is wrong in my reasoning?

like image 409
Michele Piccolini Avatar asked Feb 20 '19 10:02

Michele Piccolini


People also ask

How to allocate size of an array dynamically in c++?

Dynamic arrays in C++ are declared using the new keyword. We use square brackets to specify the number of items to be stored in the dynamic array. Once done with the array, we can free up the memory using the delete operator. Use the delete operator with [] to free the memory of all array elements.

How to dynamically allocate an array?

dynamically allocated arrays To dynamically allocate space, use calls to malloc passing in the total number of bytes to allocate (always use the sizeof to get the size of a specific type). A single call to malloc allocates a contiguous chunk of heap space of the passed size.


2 Answers

TL;DR The operator delete[] destructs the objects and deallocates the memory. The information N ("number of elements") is required for destructing. The information S ("size of allocated memory") is required for deallocating. S is always stored and can be queried by compiler extensions. N is only stored if destructing objects requires calling destructors. If N is stored, where it is stored is implementation-dependent.


The operator delete [] has to do two things:

a) destructing the objects (calling destructors, if necessary) and

b) deallocating the memory.

Let's first discuss (de)allocation, which is delegated to the C functions malloc and free by many compilers (like GCC). The function malloc takes the number of bytes to be allocated as a parameter and returns a pointer. The function free takes only a pointer; the number of bytes is not necessary. This means that the memory allocating functions have to keep track how many bytes have been allocated. There could be a function to query how many bytes have been allocated (in Linux this can be done with malloc_usable_size, in Windows with _msize). This is not what you want because this does not tell you the size of an array but the amount of memory allocated. Since malloc is not necessarily giving you exactly as much memory as you have asked for, you cannot compute the array size from the result of malloc_usable_size:

#include <iostream> #include <malloc.h>  int main() {     std::cout << malloc_usable_size(malloc(42)) << std::endl; } 

This example gives you 56, not 42: http://cpp.sh/2wdm4

Note that applying malloc_usable_size (or _msize) to the result of new is undefined behavior.

So, let's now discuss construction and destruction of objects. Here, you have two ways of delete: delete (for single objects) and delete[] (for arrays). In very old versions of C++, you had to pass the size of the array to the delete[]-operator. As you mentioned, nowadays, this is not the case. The compiler tracks this information. GCC adds a small field prior the beginning of the array, where the size of the array is stored such that it knows how often the destructor has to be called. You might query that:

#include <iostream>  struct foo {     char a;     ~foo() {} };  int main() {     foo * ptr = new foo[42];     std::cout << *(((std::size_t*)ptr)-1) << std::endl; } 

This code gives you 42: http://cpp.sh/7mbqq

Just for the protocol: This is undefined behavior, but with the current version of GCC it works.

So, you might ask yourself why there is no function to query this information. The answer is that GCC doesn't always store this information. There might be cases where destruction of the objects is a no-operation (and the compiler is able to figure that out). Consider the following example:

#include <iostream>  struct foo {     char a;     //~foo() {} };  int main() {     foo * ptr = new foo[42];     std::cout << *(((std::size_t*)ptr)-1) << std::endl; } 

Here, the answer is not 42 any more: http://cpp.sh/2rzfb

The answer is just garbage - the code was undefined behavior again.

Why? Because the compiler does not need to call a destructor, so it does not need to store the information. And, yes, in this case the compiler does not add code that keeps track how many objects have been created. Only the number of allocated bytes (which might be 56, see above) is known.

like image 164
Handy999 Avatar answered Oct 22 '22 16:10

Handy999


It does - the allocator, or some implementation detail behind it, knows exactly what the size of the block is.

But that information is not provided to you or to the "code layer" of your program.

Could the language have been designed to do this? Sure! It's probably a case of "don't pay for what you don't use" — it's your responsibility to remember this information. After all, you know how much memory you asked for! Often times people will not want the cost of a number being passed up the call stack when, most of the time, they won't need it to be.

There are some platform-specific "extensions" that may get you what you want, like malloc_usable_size on Linux and _msize on Windows, though these assume that your allocator used malloc and didn't do any other magic that may extend the size of the allocated block at the lowest level. I'd say you're still better off tracking this yourself if you really need it… or using a vector.

like image 32
Lightness Races in Orbit Avatar answered Oct 22 '22 15:10

Lightness Races in Orbit