Why is this an error:
typedef int H[4];
H * h = new H; // error: cannot convert 'int*' to 'int (*)[4]' in initialization
?
Furthermore, why is this not an error:
H * h = new H[1];
?
Why does the compiler consider that new H
returns an int *
, whereas new H[1]
returns an H *
as expected ?
To put it another way: why is it that T * t = new T;
is correct for a general type T, but is not correct when T
is an array type ?
What would be the canonical way to allocate a simple array type such as this via new
?
Note that this is a simplified example, so e.g. new int[4]
is not an acceptable workaround - I need to use the actual type from the preceding typedef
.
Note also that I'm aware that using std::vector
, std::array
, et al is generally preferable over C-style arrays, but I have a "real world" use case where I need to work with types such as the above.
Pointers are variables which stores the address of another variable. When we allocate memory to a variable, pointer points to the address of the variable. Unary operator ( * ) is used to declare a variable and it returns the address of the allocated memory.
In C++, Pointers are variables that hold addresses of other variables. Not only can a pointer store the address of a single variable, it can also store the address of cells of an array.
As we know an array is essentially a collection of elements of the same data types. All elements must be the same and store at the contiguous memory location. So we can create an array of pointers, it is basically an array of the pointer variables. It is also known as pointer arrays.
The C++ rule for the return type and value of new T
is:
T
is not an array type, the return type is T *
, and the returned value is a pointer to the dynamically allocated object of type T
.T
is an array of type U
, the return type is U *
, and the returned value is a pointer to the first element (whose type is U
) of the dynamically allocated array of type T
.Therefore, since your H
is an array of int
, the return type of new H
is int *
, not H *
.
By the same rule, new H[1]
returns H *
, but note that you have technicaly allocated a two-dimensional array of int
s, sized 1 x 4.
The best way to get get around this in generic code is indeed to use auto
:
auto h = new H;
Or, if you prefer to highlight the pointer fact:
auto *h = new H;
As for the rationale of this seeming inconsistency in the rules: pointers to arrays are quite "dangerous" in C++ since they behave rather unexpectedly (i.e. you have to be very careful with them not to produce unwanted effects). Let's look at this code:
typedef int H[4];
H *h = obtain_pointer_to_H_somehow();
h[2] = h[1] + 6;
At first (and maybe even second) glance, the code above seems to add 6 to the second int
in the array and store it in the third int
. But that's not what it does.
Just like for int *p
, p[1]
is an int
(at the address sizeof(int)
bytes offset from p
), so for H *h
, h[1]
is a H
, at the address 4 * sizeof(int)
bytes offset from h
. So the code is interpreted as: take the address in h
, add 4 * sizeof(int)
bytes to it, then add 6, and then store the resulting address at offset 8 * sizeof(int)
from h
. Of course, that will fail, since h[2]
decays to an rvalue.
OK then, you fix it like this:
*h[2] = *h[1] + 6;
Even worse now. []
binds tighter than *
, so this will reach into the 5th int
object after h
(note there are only 4 of them there!), add 6, and write that into the 9th int
after h
. Writing into random memory FTW.
To actually do what the code was probably intended to, it would have to be spelled like this:
(*h)[2] = (*h)[1] + 6;
In light of the above, and since what you usually do with a dynamically allocated array is access its elements, it makes more sense for new T[]
to return T *
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With