Why am I able to change the array [closed] - c++

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I was studying for my exam then ı saw this on the net.My question is that are not the arrays are basically constant pointers in c (so that there were an error ) ? At first I wanted to get an error about this "b+=2" but there was none.
int func ( int b[])
{
b +=2;
printf("%d",*b);
}
int main()
{
int arr[]={1,2,3,4};
func(arr);
return 0;
}
( output of this program is 3 btw)

are not the arrays are basically constant pointers in c
No, they are not. Arrays are contiguous sequences of objects. Pointers are objects that refer to another object by storing the memory address.
Why am I able to change the array
b +=2;
b is not an array. b is a pointer. Initially, you pass a pointer to the first element of the array arr. Adding 1 to a pointer changes it to point to a successive element of the array. Adding 2 changes it to point to second successive element. Starting from the first element, the second successive element is the one at the index 2, which in this case has the value 3. This pointer arithmetic is why pointers can be used to iterate array elements.
But it's declared using syntax that's normally associated with arrays
Function arguments cannot be arrays. You can declare the argument to be an array, but that declaration is adjusted to be a pointer to an element of the array. These two function declarations are semantically identical:
int func ( int b[]); // the adjusted type is int*
int func ( int *b );
Both of them declare a function whose argument is a pointer to int. This adjustment does not imply that arrays are pointers. This adjustment is complementary to the rule that the array implicitly converts into pointer to first element - this conversion is called decaying.
Note that argument declaration is the only case where this adjustment occurs. For example in variable declaration:
int arr[]={1,2,3,4}; // the type is int[4]; not int*
// the length is deduced from the initialiser
int *ptr; // different type
Also note that the adjustment occurs only at the "top" level of compound types. These declarations are different:
int funcA ( int (*b)[4]); // pointer to array
int funcB ( int **b ); // pointer to pointer
P.S. You've declared the function to return int, but have failed to provide a return statement. Doing so in C++ results in undefined behaviour of the program.

Arrays decay to pointers to their first element when passed to functions. Hence the function is equivalent to
int func ( int* b)
{
b +=2;
printf("%d",*b);
}
The pointer is advanced by 2 and then the element at that position is printed. For the constness consider that parameters are passed by value. Ie b is a copy. You cannot do
int arr[] = {1,2,3,4};
arr+=2;
But you can do
int arr[] = {1,2,3,4};
int* p = arr;
p += 2; // p now points to the third element
For the sake of completeness, main could be written as
int main()
{
int arr[]={1,2,3,4};
func(&arr[0]);
return 0;
}
Actually it is uncommon to write &arr[0] when passing arrays to functions, but this is just to illustrate what happens.

When declaring a parameter, int b[] means int *b. It's not an array. In fact, sizeof b will even return sizeof int *.
From the C spec,
A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.
This leaves you with code passing an array to a function requiring a pointer, and that's perfectly fine. In such circumstances, the array will degrade into a pointer to its first element. It's as if
func(arr)
was
func(&(arr[0]))

Hi Buddy you are very right at your position that arrays are constant pointers but while passing the address to the function the modification is seen only in the pointer variable b , not arr . This is because b is local to the function.
If the print statement would have been in the main() function as:
printf("%d",arr[0]);
or
printf("%d",*arr);
output would have been 1 . Just because the increment has affected the local value but not the real one.
Hope it helps!!

Related

Why is that you can modify an array inside a function without using any reference or pointer [duplicate]

This question already has answers here:
Array changed in a void function, is still changed outside! why? (scope)
(4 answers)
Passing an array as a parameter in C
(3 answers)
Passing Arrays to Function in C++
(5 answers)
Passing an array as an argument to a function in C
(11 answers)
Closed 11 months ago.
I don't get it why you can alter the values inside the array, without using a reference or a pointer (&, *), I'm a freshmen student, and I don't know the reason behind, I hope someone can provide a logical answer, please refer to the code below, Thank You in Advance.
#include <iostream>
using namespace std;
void a(int x[]){
for(int i = 0; i < 5; i++){
x[i] += 2;
}
}
int main(){
int x[5] = {1,2,3,4,5};
a(x);
for(auto b : x){
cout << b << " ";
}
return 0;
}
A function parameter is never an array in C++. When you declare a function parameter as an array, it is automatically adjusted to be a pointer to element of such array. These declarations are effectively identical:
void a(int x[]); // looks like an array of int of unknown bound
void a(int* x); // declaration above is adjusted to this
void a(int x[1234]); // the size is ignored completely
An array implicitly converts to a pointer to the first element of the array (such conversion is called decay). Hence, you can call the function that doesn't accept an array parameter by passing an array argument:
int* ptr1 = x; // implicit conversion
int* ptr2 = &x[0]; // same conversion explicitly
a(x); // same conversion happens here
These two rules (function parameter adjustment and array to pointer decay) make it so that what syntactically looks like passing arrays by value, is actually done using indirection. Within the function, you modify the elements by indirecting through the pointer parameter that points to the array that exists outside of the function.
Important note: The adjustment of array to pointer in function parameter does not apply in other contexts. Arrays and pointers are distinct types with different properties.
Another note: The adjustment does not apply to parts of compound types. For example, a function parameter of type "pointer to array" will not be adjusted to be "pointer to pointer" and "reference to array" will not be adjusted to be "reference to pointer".
The parameter having the array type in this function declaration
void a(int x[]){
is adjusted by the compiler to pointer type to array elements type. That is the above declaration is equivalent to
void a(int *x){
In this call of the function
a(x);
the array designator is implicitly converted to pointer to its first element. That is the call is equivalent to
a( &x[0]);
So within the function you have a pointer to the first element of the array declared in main.
Using the pointer arithmetic you can access elements of the array. That is the elements of the array are passed to the function by reference in the C meaning indirectly through a pointer to them.
Within the function the variable x has the type int *. And this expression statement
x[i] += 2;
is equivalent to
*( x + i ) += 2;
Beacuse
void a(int x[]){
is the same as
void a(int *x){
and so you are using a pointer
Why?
Because an array like
int x[10];
'decays' to a pointer when passed to a function (and in other places). This can be very confusing but at the same time is very flexible
It mens that I can have a function like strlen that can accpet a 'real' array, or a pointer. These 'strings'
char *s1 = malloc(10);
strcpy(s1, "hello");
char s2[] = "hello";
char *s3 = "hello";
store their data in different ways but all can be handled by
size_t strlen(const char *s);
note that this is exactly the same as
size_t strlen(const char s[]);
they are 2 different ways of writing the same thing. Personal preference is for the second type if its really is an 'array' vs a pointer to maybe an array.
One issue with this 'decay' is that inside strlen (or any pointer/array accepting function) it is impossible to 'know' the length just from the parameter. The compiler knows that the size of s2 is 6 but this information is not carried forward to the function.
Regularly SO sees this
void my_func(int *arr){
int len = sizeof(arr)/sizeof(arr[0]);
....
}
int a[10];
my_func(a);
This will give len = 1 or 2 (depending on 32 or 64 bit machine), never 10
The flexibility costs a litle power

What are the exact differences betwenn int **a and int a[][] as function parameters in C and C++?

I have encountered this while writing a program with matrices, I used int** m to declare my matrix - because I needed dynamic allocation and in the function i used int a[][]. I don't remember having any problem with that. But when I used a simple m[6][6] matrix and f(int**m, int** m2, rest params) I had trouble.
It compiled and when I was running the program (code blocks with GCC) it just crashed. I tried debugging by adding printf()s and it was crashing at an if() block where it made no sense to crash. Modified the first function parameter from int a[][] to int* a[6] and it moved on, modified the second param later and my program worked on first try. By a more careful debug what I was saving in int m[i][j] and checking in the if was junk value, not what I was putting in, I was just putting 1 or 0 to mark something.
After all this years, unless I get compiler error by GCC when I do stuff like this, I just write the first way that comes to mind.
What is the logic behind using int** and int [][] to declare variable/get function parameters, in all 4 combinations? Most predefined functions I worked with use int** in the function header.
I know int [][] is not equivalent to int**, it is int* [] correctly, but what are the things that i'm missing? int[][] is an multi dimensional array means array of arrays, all 3 ways of writing it seem the same. And for int[][] it almost always asks to only let the first parameter void, like for int array[][][][] I need to put int array a[][n1][n2][n3] in the function parameter, right? It needs to know the dimension for multi-dimensional arrays except for the first one, because int* and int[] can be used without problems when declaring function arguments?
What are the exact differences betwenn int **a and int a[][] as function parameters in C and C++?
int *a. This is a pointer to an int.
int **a. This is a pointer to pointer to an int.
int a[] This would be an array of unspecified number of ints in all other contexts, but as a function parameter declarator, it is adjusted to be a pointer to int i.e. in that case it is same as if you had written int *a.
int a[][] This would be an array of unspecified number of arrays of unspecified number of ints, but such type is ill-formed because the array element cannot be an array of unspecified size.
int *a[] This would be an array of unspecified number of pointers to int in all other contexts, but as a function parameter declarator, it is adjusted to be a pointer to pointer to int i.e. in that case it is same as if you had written int **a.
int (*a)[N] This is a pointer to an array of N ints.
int a[][N] This would be an array of unspecified number of arrays of N ints in all other contexts, but as a function parameter declarator, it is adjusted to be a pointer to an array of N int i.e. in that case it is same as if you had written int (*a)[N].
Some examples:
void fun_1D(int*); // argument is pointer to int
void fun_1D(int[]); // same as above
void fun_1D(int[10]); // same as above; note that 10 is ignored
int arr_1D[20]; // array of int
fun_1D(arr_1D); // implicit conversion
fun_1D(&arr_1D[0]); // same as above
void fun_2D(int (*)[20]); // note that 20 is not ignored
void fun_2D(int[][20]); // same as above
void fun_2D(int[10][20]); // same as above; note that 10 is ignored
int arr_2D[20][20]; // array of array of int
fun_2D(arr_2D); // implicit conversion
fun_2D(&arr_2D[0]); // same as above
fun_1D(arr_2D[i]); // implicit conversion
fun_1D(&arr_2D[i][0]); // same as above
void fun_ptrs(int**); // argument is pointer to pointer to int
void fun_ptrs(int*[]); // same as above
void fun_ptrs(int*[10]); // same as above; note that 10 is ignored
int *arr_ptr[20]; // array of pointers
fun_ptrs(arr_ptr); // implicit conversion
fun_ptrs(&arr_ptr[0]); // same as above
fun_1D(arr_ptr[i]); // no conversion needed
// broken examples
fun_2D(arr_ptr); // int*[20] is not int(*)[20]
fun_ptrs(arr_2D); // int[20][20] is not int**
Notice how a function parameter declared as an array is adjusted as the same pointer type to which an array will decay to upon lvalue to rvalue conversion.
Some simple rules of thumb to remember:
An array is not a pointer.
A pointer is not an array.
A function argument written as an array is actually not an array. It is actually adjusted to be a pointer to the element of such array. After this adjustement, a function argument is never an array. This does not apply to any other contexts, except for function arguments.
Not every type can be element of an array. Arrays of unspecified length are such types.
There are no objects of "array unspecified length" types. They can only be used in extern variable declarations which refer to an array defined elsewhere, or in a definition where the actual size is deduced from the initialiser of the array, or in a function parameter declaration where the array is adjusted to be a pointer to the element.
If I declare int a[6][6] in main and call a function that expects int** a, will it workd?
No, because int[6][6] is not an int** and neither does it decay to one. int[6][6] decays to int(*)[6] as I explained above. int(*)[6] and int** are not convertible to one another. One is pointer to an array, the other is pointer to a pointer.
And the other way around
No, because int[6][6] argument is adjusted to int(*)[6]. See previous paragraph for reason why these are incompatible.
seems int a[][] is not accepted
Correct. As I explained in the fourth paragraph from the top (not counting the quote).
If I have functions f1(int *a) and f2(int a[]) and f2(int a[6]) what would sizeof (a) return in those cases ?
As I explained above, all of those declare a parameter of type int*. sizeof a would be same as sizeof(int*) because that is the type.
int **a
This is pointer to pointer to int.
int a[][]
This is array of array of int but this is not valid because the dimension of the second array must be known at declaration time, i.e. the second array must be complete, as it cannot be completed afterwards, like that
int a[][DIM]
C++ inherited C's behavior of arrays decaying into a pointer.
This behavior is really helpful, until one hits a sharp edge and realizes something odd is going on, and then one tries to figure out what it does and test out the behavior and realize it is a bit crazy.
But keep calm, once you realize what it means for an array to decay into a pointer, everything makes sense again.
Here is an example to illustrate the differences.
static void func1(int** p) {
(void)p;
}
static void func2(int (&a)[2][2]) {
(void)a;
}
int main() {
// x is a 2-dimensional array of int objects.
int x[2][2] = {{10, 20}, {30, 40}};
// x[0][0] is 10, and is at 0x1000 (say, for example)
// x[0][1] is 20, and is at 0x1004
// x[1][0] is 30, and is at 0x1008
// x[1][1] is 40, and is at 0x100C
// y is a 1-dimensional array of pointers.
int* y[2] = { &x[0][0], &x[1][0] };
// y[0] is 0x1000, and is at 0x1010 (say, for example)
// y[1] is 0x1008, and is at 0x1018
// x cannot decay into an int**, because it is not an array of pointers.
// y can decay into an int**, because it is an array of pointers.
func1(y);
// x can be used for an int[2][2] reference parameter.
// y cannot be used for an int[2][2] reference parameter.
func2(x);
}
When you declare arrays in C++ you are allocating a contigious block of memory that holds the members of the array.
int data[6]; // Block of 6 integers.
This is true even for dimensional arrays.
int threeD[2][3][4]; // Allocates a Block of 24 integers.
// The compiler keeps track of the number of
// of dimensions and does the maths for you
// to calculate the correct offset.
Now when an array is passed to a function the array will decay into a pointer to the first element.
Now if you are dynamically allocating a multi-dimensional array. You tend to do this as arrays of arrays. These arrays are NOT in contiguous memory. But you need to do it this way to allow you to use the square bracket operator [] as you would do normally.
int*** dynamicThreeD = new int**[2];
for(int l1 = 0 ; l1 < 2 ; ++l1) {
dynamicThreeD[l1] = new int*[3];
for(int l2 = 0 ; l2 < 3 ; ++l2) {
dynamicThreeD[l1][l2] = new int[4];
}
}
int threedD[2][3][4];
Though these two types look the same in the way they are accessed:
dynamicThreeD[1][2][3] = 8;
threeD[1][2][3] = 8;
These are not the same. The dynamicThreeD at ache [] is accessing the array and retrieving the next pointer for referencing next. While the threeD object results in an index calculation (3) + (4 * 2) + (4*3 * 1) which is then used as an offset from the first element.
dynamicThreeD[1][2][3] = 8;
// Equivalent to
int** tmp1 = dynamicThreeD[1];
int* tmp2 = tmp1[2];
tmp2[3] = 8;
threeD[1][2][3] = 8;
// Equivalent to
int index = (3) + (4 * 2) + (4*3 * 1)
// OK The compiler does not take the address like this
// But I needed to do some type gymnastics to convert the type
// at the language level.
//
// But you get the same effect like this.
// As the value is just offset by an index from the beginning of
// the array.
(&threed[0][0][0])[index] = 8;
The side affect of this is that multi dimensional arrays can be much more efficient as 1) we only need to do one memory accesses (not 3) get/set the data. 2) Because of data locality and caching you get a much better hit ratio.
On the other hand multi-dimensional arrays in parallel systems can be a pain if you have multiple writers as cache consistency becomes an issue if all the members are all in the same cache line. Here the array of array has an advantage as each line can be in a distinct part of memory and worked on independently (Note: I am oversimplify a very complex issue).
Among the other well answers, I want to focus your last concern.
"And for int[][] it almost always asks to only let the first parameter void, like for int array[][][][] I need to put int array a[][n1][n2][n3] in the function parameter, right?"
Yes, only the first dimension can be left unspecified. But if you want to have the other dimensions to be variable/determined at runtime, at least in C you can use:
int foo (int n1, int n2, int a[][n1][n2]) { ...
With an additional level of indirection, you can pass a fixed size of dimensions, since sizeof(*x), sizeof(x[0][0]) etc. knows the dimensions of the indirect type.
But generally in C, the array [] is just a pointer * and the size, especially when dynamic, needs to be passed or known, as a second parameter, global, MACRO, or in a struct, until classes come along. Of course, there is the null term thing used so much with char* and char**argv. For pointers, you pay 8 bytes overhead.

How to tell if a pointer is the pointer to an array?

Consider this
template <class T>
inline constexpr bool found_to_be_array (T* specimen)
{
if constexpr (std::is_array_v<T>) {
return true;
}
else {
return false;
};
}
The question is in which context is this going to produce the reliable result?
Clarification: I can not change the footprint of this function. I have no answer, so let me post my findings here. Consider this:
int ia[]{ 1,2,3,4,5,6,7,8,9,0 };
int iam[3][3][3][3][3]{};
// returns false
auto is_array_1 = found_to_be_array(ia);
// returns true
auto is_array_2 = found_to_be_array(iam);
I am still researching this, but for multidimensional arrays, found_to_be_array works.
https://godbolt.org/g/ij73Z4
No this approach will not work. The parameter specimen has already decayed to a pointer type (or it might even have been a pointer all along); the metaprogramming technique used by std::is_array does not trace back in some way to the caller.
And besides, you're testing (unintentionally?) T rather than T* - but changing to the latter will not work.
How to tell if a pointer is the pointer to array?
A pointer has type, which indicates what it points to. When it points to array, then the type of the pointer reflects this, for example:
int arr[2][2] = {{1,2},{3,4}};
auto x = &arr[0];
x in this case has the type of int(*)[2]- a pointer to array of 2 ints, in this case {1,2}. If you increase this pointer by 1 it will point to the next array of 2 ints, namely {3,4}. If you pass this pointer to your function, T will be deduced as int[2] and the result will be true;
In case of 1D array it is no different:
int arr[4] = {1,2,3,4};
auto x = &arr;
x type will be int(*)[4] and it will also work and the function will return true.
But if you pass array to your function and not the pointer, thus forcing it to decay to the pointer to its 1st element, the array information is lost, because the pointer is of type int now.
auto x = arr;
x here is of type int *, it is not pointing to array, it points to the first int, which is 1. If you increase it by one it will point to the next int, which is 2 and so on. If this was a pointer to the array, it would point to the next byte after array end if you increase it by 1. Passing this pointer to your function would return false, because int type is not an array.
So to answer to your question, you can tell that the pointer is a pointer to array, because this information would be supplied in the pointer type.
int iam[3][3][3][3][3]{};
// returns true
auto is_array_2 = found_to_be_array(iam);
iam decays to the pointer to its 1st element, basically you are testing if iam[0] is an array, which it is, and so it works.
Whether a non-null data-pointer points to an array-element, is always answered maybe.
Why, you ask?
The language explicitly allows you to treat any object as the single element of an array of one. Which is quite nifty when you need a sequence, want to copy a trivial object, or the like.
Thus, every pointer is one of:
a null-pointer,
a pointer to an element of an array,
a pointer to beyond an element of an array, or
invalid, meaning one of
wild, meaning never initialized, or
dangling, meaning the pointee's lifetime was ended.
And while you can differentiate between case 1 and 2 or 3, the rest would need analysis of potentially the complete history of the current run of the program.
Your requirement that you can not change the function is weird/unreasonable.
If you can not change the function signature you can not detect how argument was produced(from ptr or array) because that information is lost.

difference between pointer to an array and pointer to the first element of an array

int (*arr)[5] means arr is a pointer-to-an-array of 5 integers. Now what exactly is this pointer?
Is it the same if I declare int arr[5] where arr is the pointer to the first element?
Is arr from both the examples are the same? If not, then what exactly is a pointer-to-an-array?
Theory
First off some theory (you can skip to the "Answers" section but I suggest you to read this as well):
int arr[5]
this is an array and "arr" is not the pointer to the first element of the array. Under specific circumstances (i.e. passing them as lvalues to a function) they decay into pointers: you lose the ability of calling sizeof on them.
Under normal circumstances an array is an array and a pointer is a pointer and they're two totally different things.
When dealing with a decayed pointer and the pointer to the array you wrote, they behave exactly the same but there's a caveat: an array of type T can decay into a pointer of type T, but only once (or one level-deep). The newly created decayed type cannot further decay into anything else.
This means that a bidimensional array like
int array1[2][2] = {{0, 1}, {2, 3}};
can't be passed to
void function1(int **a);
because it would imply a two-levels decaying and that's not allowed (you lose how elements of the array are laid out). The followings would instead work:
void function1(int a[][2]);
void function1(int a[2][2]);
In the case of a 1-dimensional array passed as lvalue to a function you can have it decayed into a simple pointer and in that case you can use it as you would with any other pointer.
Answers
Answering your questions:
int (*arr)[5]
this is a pointer to an array and you can think of the "being an array of 5 integers" as being its type, i.e. you can't use it to point to an array of 3 integers.
int arr[5]
this is an array and will always behave as an array except when you pass it as an lvalue
int* ptrToArr = arr;
in that case the array decays (with all the exceptions above I cited) and you get a pointer and you can use it as you want.
And: no, they're not equal otherwise something like this would be allowed
int (*arr)[5]
int* ptrToArr = arr; // NOT ALLOWED
Error cannot convert ‘int (*)[5]’ to ‘int*’ in initialization
they're both pointers but the difference is in their type.
At runtime, a pointer is a "just a pointer" regardless of what it points to, the difference is a semantic one; pointer-to-array conveys a different meaning (to the compiler) compared with pointer-to-element
When dealing with a pointer-to-array, you are pointing to an array of a specified size - and the compiler will ensure that you can only point-to an array of that size.
i.e. this code will compile
int theArray[5];
int (*ptrToArray)[5];
ptrToArray = &theArray; // OK
but this will break:
int anotherArray[10];
int (*ptrToArray)[5];
ptrToArray = &anotherArray; // ERROR!
When dealing with a pointer-to-element, you may point to any object in memory with a matching type. (It doesn't necessarily even need to be in an array; the compiler will not make any assumptions or restrict you in any way)
i.e.
int theArray[5];
int* ptrToElement = &theArray[0]; // OK - Pointer-to element 0
and..
int anotherArray[10];
int* ptrToElement = &anotherArray[0]; // Also OK!
In summary, the data type int* does not imply any knowledge of an array, however the data type int (*)[5] implies an array, which must contain exactly 5 elements.
A pointer to an array is a pointer to an array of a certain type. The type includes the type of the elements, as well as the size. You cannot assign an array of a different type to it:
int (*arr)[5];
int a[5];
arr = &a; // OK
int b[42];
arr = &b; // ERROR: b is not of type int[5].
A pointer to the first element of an array can point to the beginning of any array with the right type of element (in fact, it can point to any element in the array):
int* arr;
int a[5];
arr = &a[0]; // OK
int b[42];
arr = &b[0]; // OK
arr = &b[9]; // OK
Note that in C and C++, arrays decay to pointers to the type of their elements in certain contexts. This is why it is possible to do this:
int* arr;
int a[5];
arr = a; // OK, a decays to int*, points to &a[0]
Here, the type of arr (int*) is not the same as that of a (int[5]), but a decays to an int* pointing to its first element, making the assignment legal.
Pointer to array and pointer to first element of array both are different. In case of int (*arr)[5], arr is pointer to chunk of memory of 5 int. Dereferencing arr will give the entire row. In case of int arr[5], arr decays to pointer to first element. Dereferencing arr will give the first element.
In both cases starting address is same but both the pointers are of different type.
Is it the same if i declare int arr[5] where arr is the pointer to the first element? is arr from both example are same? if not, then what exactly is a pointer to an array?
No. To understand this see the diagram for the function1:
void f(void) {
int matrix[4][2] = { {0,1}, {2,3}, {4,5}, {6,7} };
char s[] = "abc";
int i = 123;
int *p1 = &matrix[0][0];
int (*p2)[2] = &matrix[0];
int (*p3)[4][2] = &matrix;
/* code goes here */
}
All three pointers certainly allow you to locate the 0 in matrix[0][0], and if you convert these pointers to ‘byte addresses’ and print them out with a %p directive in printf(), all three are quite likely to produce the same output (on a typical modern computer). But the int * pointer, p1, points only to a single int, as circled in black. The red pointer, p2, whose type is int (*)[2], points to two ints, and the blue pointer -- the one that points to the entire matrix -- really does point to the entire matrix.
These differences affect the results of both pointer arithmetic and the unary * (indirection) operator. Since p1 points to a single int, p1 + 1 moves forward by a single int. The black circle1 is only as big as one int, and *(p1 + 1) is just the next int, whose value is 1. Likewise, sizeof *p1 is just sizeof(int) (probably 4).
Since p2 points to an entire ‘array 2 of int’, however, p2 + 1 will move forward by one such array. The result would be a pointer pointing to a red circle going around the {2,3} pair. Since the result of an indirection operator is an object, *(p2 + 1) is that entire array object, which may fall under The Rule. If it does fall under The Rule, the object will become instead a pointer to its first element, i.e., the int currently holding 2. If it does not fall under The Rule -- for instance, in sizeof *(p2 + 1), which puts the object in object context -- it will remain the entire array object. This means that sizeof *(p2 + 1) (and sizeof *p2 as well, of course) is sizeof(int[2]) (probably 8).
1 Above content has been taken from More Words about Arrays and Pointers.
The address of the whole array, and the address of the first element, are defined to be the same, since arrays in C++ (and C) have no intrinsic padding besides that of the constituent objects.
However, the types of these pointers are different. Until you perform some kind of typecast, comparing an int * to an int (*)[5] is apples to oranges.
If you declare arr[5], then arr is not a pointer to the first element. It is the array object. You can observe this as sizeof( arr ) will be equal to 5 * sizeof (int). An array object implicitly converts to a pointer to its first element.
A pointer to an array does not implicitly convert to anything, which may be the other cause of your confusion.
If you write int arr[5], you are creating an array of five int on the stack. This takes up size equal to the size of five ints.
If you write int (*arr)[5], you are creating a pointer to an array of five int on the stack. This takes up size equal to the size of a pointer.
If it is not clear from the above, the pointer has separate storage from the array, and can point at anything, but the array name cannot be assigned to point at something else.
See my answer here for more details.

ELI5: What is the data type of `int *p[]`

I don't understand what the datatype of this is. If its a pointer or an array. Please explain in simple terms. To quote what was in the book-
If you want to pass an array of pointers into a function, you can use the same method that you use to pass other arrays—simply call the function with the array name without any indexes. For example, a function that can receive array x looks like this:
void display_array(int *q[])
{
int t;
for(t=0; t<10; t++)
printf("%d ", *q[t]);
}
Remember, q is not a pointer to integers, but rather a pointer to an array of pointers to
integers. Therefore you need to declare the parameter q as an array of integer pointers,
as just shown. You cannot declare q simply as an integer pointer because that is not
what it is.
cite: C++: The Complete Reference, 4th Edition by Herbert Schildt, Page 122-123
This is how it's built up:
int is the type "int".
int* is the type "pointer to int"
int* [] is the type "array (of unknown bound/length) of pointer to int"
int* p[] is the declaration of a variable or parameter named p of the type above.
... pointer to an array of pointers to integers
No it's not. q is the type int *[]. Which is an invalid (or possibly incomplete, depending on context) type in C++, and only valid in some places in C. Arrays must have a size.
The type int *[] is an (unsized) array of pointers to int. It is itself not a pointer.
The confusion probably comes from the fact that an array can decay to a pointer to its first element.
For example, lets say we have this array:
int a[20];
When plain a is used, it decays to a pointer to its first element: a is equal to &a[0].
int *p[]
// ^
p is
int *p[]
// ^^
p is an array of unspecified size (possibly illegal, depends on context)
int *p[]
// ^^^^^
p is an array of unspecified size of pointers to int
Meaning each element of p is a pointer:
int foobar = 42;
p[0] = NULL;
p[1] = &foobar;
I don't understand what the datatype of this is
If it's any comfort, neither does the author of the book you are reading.
Remember, q is not a pointer to integers, but rather a pointer to an array of pointers to integers.
This is bullschildt.
Before adjustment of parameters, q is an array of pointers to integers.
After adjustment of parameters, q is a pointer to the first element of an array of pointers to integers. Equivalent to int** q, a pointer to pointer to an int.
Nowhere is it "a pointer to an array of pointers to integers". That would have been int* (*q)[].
I would advise to stop reading that book.
The key here is that any array that is part of a parameter list of a function, gets adjusted ("decays") into a pointer to the first element. So it doesn't matter if you type int* q[666] or int* q[], either will be silently replaced by the compiler with int** "behind the lines".
This is actually the reason why we can write [] in a parameter list - normally an empty array would be an incomplete type that can't be used before completion elsewhere. But since parameters always get adjusted, they are never of array type, and it doesn't matter that the original type was incomplete.