I came across this construction inside a function (e is a parameter passed to the function):
short (*tt)[][2] = (short (*)[][2])(heater_ttbl_map[e]);
and its use (where i is a counter inside a for loop):
(*tt)[i][0]
I think I got the first part of the assignment:
short (*tt)[][2]
for what I understand tt is declared as a pointer to an array of arrays of shorts.
The second part is confusing me though, looks like some sort of cast but I'm not sure I understand what it does, expecially this: (*). How does it work?
heater_ttbl_map is declared like this (where pointer1 and pointer2 are both bidimensional arrays of shorts):
static void *heater_ttbl_map[2] = {(void*)pointer1, (void*)pointer2};
as for its use I understand that what is pointed at by tt is dereferenced (and its the content of the third index of the i index of the array, which is a short) but why writing it like this:
(*tt)[i][0]
and not like this:
*tt[i][0]
is it because tt is not an array itself but a pointer to an array?
Due to operator precedence ([] has precedence over * operator), there is difference in two statements -
(*tt)[i][0]
In this you access the element at index [i][0] of array to which pointer tt points to .
Whereas, in this -
*tt[i][0]
First the element at index [i][0](may be 2-d array of pointers) is accessed and then dereferenced.
Using them interchangeably can cause access or dereferencing unauthorized memory location and lead to undefined behaviour.
As ameyCU explained, the [] subscript operator has higher precedence than the unary * operator, so the expression *a[i] will be parsed as *(a[i]); IOW, you're indexing into a and dereferencing the result.
This works if a is an array of T (or a pointer to T; more on that below). However, if a is a pointer to an array of T, that won't do what you want. This is probably best explained visually.
Assume the declarations:
int arr[3] = { 0, 1, 2 };
int (*parr)[3] = &arr; // type of &arr is int (*)[3], not int **
Here's what things look like in memory (sort of; addresses are pulled out of thin air):Address Item Memory cell
------- ---- -----------
+---+
0x8000 arr: | 0 | <--------+
+---+ |
0x8004 | 1 | |
+---+ |
0x8008 | 2 | |
+---+ |
... |
+---+ |
0x8080 parr: | | ----------+
+---+
...
So you see the array arr with its three elements, and the pointer parr pointing to arr. We want to access the second element of arr (value 1 at address 0x8004) through the pointer parr. What happens if we write *parr[1]?
First of all, remember that the expression a[i] is defined as *(a + i); that is, given a pointer value a1, offset i elements (not bytes) from a and dereference the result. But what does it mean to offset i elements from a?
Pointer arithmetic is based on the size of the pointed-to type; if p is a pointer to T, then p+1 will give me the location of the next object of type T. So, if p points to an int object at address 0x1000, then p+1 will give me the address of the int object following p - 0x1000 + sizeof (int).
So, if we write parr[1], what does that give us? Since parr points to a 3-element array if int, parr + 1 will give us the address of the next 3-element array of int - 0x8000 + sizeof (int [3]), or 0x800c (assuming 4-byte int type).
Remember from above that [] has higher precedence than unary *, so the expression *parr[1] will be parsed as *(parr[1]), which evaluates to *(0x800c).
That's not what we want. To access arr[1] through parr, we must make sure parr has been dereferenced before the subscript operation is applied by explicitly grouping the * operator with parentheses: (*parr)[1]. *parr evaluates to 0x8000 which has type "3-element array of int"; we then access the second element of that array (0x8000 + sizeof (int), or 0x8004) to get the desired value.
Now, let's look at something - if a[i] is equivalent to *(a+i), then it follows that a[0] is equivalent to *a. That means we can write (*parr)[1] as (parr[0])[1], or just parr[0][1]. Now, you don't want to do that for this case since parr is just a pointer to a 1D array, not a 2D array. But this is how 2D array indexing works. Given a declaration like T a[M][N];, the expression a will "decay" to type T (*)[N] in most circumstances. If I wrote something like
int arr[3][2] = {{1,2},{3,4},{5,6}};
int (*parr)[2] = arr; // don't need the & this time, since arr "decays" to type
// int (*)[2]
then to access an element of arr through parr, all I need to do is write parr[i][j]; parr[i] implicitly dereferences the parr pointer.
This is where things get confusing; arrays are not pointers, and they don't store any pointers internally. Instead, of an array expression is not the operand of the sizeof or unary * operators, its type is converted from "N-element array of T" to "pointer to T", and the value of the expression is the address of the first element of the array. This is why you can use the [] operator on both array and pointer objects.
This is also why we used the & operator to get the address of arr in our code snippet; if it's not the operand of the `&` operator, the expression "decays" from type "3-element array of int" to "pointer to int"
Related
This question already has answers here:
How does *(&arr + 1) - arr give the length in elements of array arr?
(5 answers)
Why are the values different? C++ pointer
(2 answers)
Closed 1 year ago.
The community reviewed whether to reopen this question 9 months ago and left it closed:
Duplicate This question has been answered, is not unique, and doesn’t differentiate itself from another question.
int arr[] = { 3, 5, 9, 2, 8, 10, 11 };
int arrSize = *(&arr + 1) - arr;
std::cout << arrSize;
I am not able to get how this is working. So anyone can help me with this.
If we "draw" the array together with the pointers, it will look something like this:
+--------+--------+-----+--------+-----+
| arr[0] | arr[1] | ... | arr[6] | ... |
+--------+--------+-----+--------+-----+
^ ^ ^
| | |
&arr[0] &arr[1] |
| |
&arr &arr + 1
The type of the expressions &arr and &arr + 1 is int (*)[7]. If we dereference either of those pointers, we get a value of type int[7], and as with all arrays, it will decay to a pointer to its first element.
So what's happening is that we take the difference between a pointer to the first element of &arr + 1 (the dereference really makes this UB, but will still work with any sane compiler) and a pointer to the first element of &arr.
All pointer arithmetic is done in the base-unit of the pointed-to type, which in this case is int, so the result is the number of int elements between the two addresses being pointed at.
It might be useful to know that an array will naturally decay to a pointer to its first element, ie the expression arr will decay to &arr[0], which will have the type int *.
Also, for any pointer (or array) p and index i, the expression *(p + i) is exactly equal to p[i]. So *(&arr + 1) is really the same as (&arr)[1] (which makes the UB much more visible).
That program has undefined behaviour. (&arr + 1) is a valid pointer that points "one beyond" arr, and has type int(*)[7], however it doesn't point to an int [7], so dereferencing it is invalid.
It so happens that your implementation assumes there is a second int [7] after the one you declare, and subtracts the location of the first element of that array that exists from the location of the first element of the fictitious array that the pointer arithmetic invented.
You need to explore what the type of the &arr expression is, and how that affects the + 1 operation on it.
Pointer arithmetic works in 'raw units' of the pointed-to type; &arr is the address of your array, so it points to an object of type, "array of 7 int". Adding 1 to that pointer actually adds the size of the type to the address – so 7 * sizeof(int) is added to the address.
However, in the outer expression (subtraction of arr), the operands are pointers to int objects1 (not arrays), so the 'units' are just sizeof(int) – which is 7 times smaller than in the inner expression. Thus, the subtraction results in the size of the array.
1 This is because, in such expressions, an array variable (such as the second operand, arr) decays to a pointer to its first element; further, your first operand is also an array, as the * operator dereferences the modified value of the array pointer.
Note on Possible UB: Other answers (and comments thereto) have suggested that the dereferencing operation, *(&arr + 1), invokes undefined behaviour. However, looking through this Draft C++17 Standard, there is the vaguest of suggestions that it may not:
6.7.2 Compound Types
...
3 … For purposes of pointer arithmetic (8.5.6) and comparison
(8.5.9, 8.5.10), a pointer past the end of the last element of an
array x of n elements is considered to be equivalent to a pointer to a
hypothetical element x[n].
But I won't claim "Language-Lawyer" status here, as there is no explicit mention in that section about dereferencing such a pointer.
If you have a declaration like this
int arr[] = { 3, 5, 9, 2, 8, 10, 11 };
the the expression &arr + 1 will point to the memory after the last element of the array. The value of the expression is equal to the value of the expression arr + 7 where 7 is the number of elements in the array declared above. The only difference is that the expression &arr + 1 has the type int ( * )[7] while the expression arr + 7 has the type int *.
So due to the integer arithmetic the difference ( arr + 7 ) - arr will yield 7: the number of elements in the array.
On the other hand, dereferencing the expression &att + 1 having the type int ( * )[7] we will get lvalue of the type int[7] that in turn used in the expression *(&arr + 1) - arr is converted to a pointer of the type int * and has the same value as arr + 7 as it was pointed out above. So the expression will yield the number of elements in the array.
The only difference between these two expressions
( arr + 7 ) - arr
and
*( &arr + 1 ) - arr
is that in the first case we will need explicitly to specify the number of elements in the array to get the address of the memory after the last element of the array while in the second case the compiler itself will calculate the address of the memory after the last element of the array knowing the array declaration.
As others have mentioned, *(&arr + 1) triggers undefined behavior because &arr + 1 is a pointer to one-past-the end of an array of type int [7] and that pointer is subsequently dereferenced.
An alternate way of doing this would be to convert the relevant pointers to uintptr_t, subtracting, and dividing the element size.
int arrSize = reinterpret_cast<int>((reinterpret_cast<uintptr_t>(&arr + 1) -
reinterpret_cast<uintptr_t>(arr)) / sizeof *arr);
Or using C-style casts:
int arrSize = (int)(((uintptr_t)(&arr + 1) - (uintptr_t)arr) / sizeof *arr);
This one is simple:
arr is just a pointer to the 0'th element of the array (&arr[0]);
&arr gives a pointer to the previous pointer;
&arr+1 gives a pointer to a pointer to arr[0]+sizeof(arr)*1;
*(&arr + 1) turns the previous value into just &arr[0]+sizeof(arr)*1;
*(&arr + 1) - arr also subtracts the pointer to arr[0] leaving just sizeof(arr)*1.
So the only tricks here are that static arrays in C internally preserve all their static type information including their total sizes and that when you increment a pointer by some integer value, C compilers don't just add the value to it, but for whatever reason standards require to increase the pointers by the value of sizeof() of whatever type the pointer is assigned to times the specified value so *(&p+idx) gives the same result as p[idx].
C language is designed to allow for very simplistic compilers so inside it is full of little tricks like this. I would not recommend using them in production code though. Remember about other developers who may need to read and maintain your code later and use the most simple and obvious stuff available instead (for the example it is obviously just using sizeof() directly).
I am a beginner in C++ and I am having a hard time understanding why we use [ ] dealing with pointers that point to arrays.
As far as I know new int[5] returns a pointer that points to an array of size 5.
So if we were to store this pointer in a variable we would do: int *arr = new int[5].
What I am not understanding is: if I want to access index 0 of that array, I would do arr[0].
Why is this syntax correct? Because in my mind arr is a pointer, so I would have to dereference the pointer in order to access the array.
Why don't we have to dereference the pointer?
In my mind I would do something like (*arr)[0], but that is incorrect.
The array subscript operator [] dereferences a pointer implicitly.
This is spelled out in section 8.2.1p1 of the C++17 standard:
The expression E1[E2] is identical (by definition) to *((E1)+(E2))
And section 6.5.2.1p2 of the C11 standard:
The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2)))
So given your example, arr[0] is exactly the same as *(arr + 0). The value between the brackets is added to the pointer value to point to the desired array element and the resulting pointer is dereferenced to get the object.
Also, it's not quite correct to say your example points to an array, but rather it points to the first element of an array. A pointer to an array would look like this:
int arr[5];
int (*p)[5] = &arr;
One other thing that may be confusing is the fact that an array, in most contexts, decays to a pointer to its first element. This means you can do this:
int arr[5];
int *p = arr;
arr[1] = 5; // sets element 1 of arr
p[1] = 7; // also sets element 1 of arr
I was wondering how *(&array + 1) actually works. I saw this as an easy way to calculate the array length and want to understand it properly before using it. I'm not very experienced with pointer arithmetic, but with my understanding &array gives the address of the first element of the array. (&array + 1) would go to end of the array in terms of address. But shouldn't *(&array + 1) give the value, which is at this address. Instead it prints out the address. I would really appreciate your help to get the pointer stuff clear in my head.
Here is the simple example I'm working on:
int numbers[] = {5,8,9,3,4,6,1};
int length = *(&numbers + 1) - numbers;
(This answer is for C++.)
&numbers is a pointer to the array itself. It has type int (*)[7].
&numbers + 1 is a pointer to the byte right after the array, where another array of 7 ints would be located. It still has type int (*)[7].
*(&numbers + 1) dereferences this pointer, yielding an lvalue of type int[7] referring to the byte right after the array.
*(&numbers + 1) - numbers: Using the - operator forces both operands to undergo the array-to-pointer conversion, so pointers can be subtracted. *(&numbers + 1) is converted to an int* pointing at the byte after the array. numbers is converted to an int* pointing at the first byte of the array. Their difference is the number of ints between the two pointers---which is the number of ints in the array.
Edit: Although there's no valid object pointed to by &numbers + 1, this is what's called a "past the end" pointer. If p is a pointer to T, pointing to a valid object of type T, then it's always valid to compute p + 1, even though *p may be a single object, or the object at the end of an array. In that case, you get a "past the end" pointer, which does not point to a valid object, but is still a valid pointer. You can use this pointer for pointer arithmetic, and even dereference it to yield an lvalue, as long as you do not try to read or write through that lvalue. Note that you can only go one byte past-the-end of an object; attempting to go any further leads to undefined behaviour.
The expression &numbers gives you the address of the array, not the first member (although numerically they are the same). The type of this expression is int (*)[7], i.e. a pointer to an array of size 7.
The expression &numbers + 1 adds sizeof(int[7]) bytes to the address of array. The resulting pointer points right after the array.
The problem however is when you then dereference this pointer with *(&numbers + 1). Dereferencing a pointer that points one element past the end of an array invokes undefined behavior.
The proper way to get the number of elements of an array is sizeof(numbers)/sizeof(numbers[0]). This assumes that the array was defined in the current scope and is not a parameter to a function.
but with my understanding &array gives the address of the first element of the array.
This understanding is misleading. &array gives the address of the array. Sure, the value of that address is the same same as the first element, but the type of the expression is different. The type of the expression &array is "pointer to array of N elements of type T" (where N is the length that you're looking for and T is int).
But shouldn't *(&array + 1) give the value, which is at this address.
Well yes... but it's here that the type of the expression becomes important. Indirecting a pointer to an array (rather than pointer to an element of the array) will result in the array itself.
In the subtraction expression, both array operands decay into pointer to first element. Since the subtraction uses decayed pointers, the unit of the pointer arithmetic is in terms of the element size.
I saw this as an easy way to calculate the array length
There are easier ways:
std::size(numbers)
And in C:
sizeof(numbers)/sizeof(numbers[0])
This question already has answers here:
How to use pointer expressions to access elements of a two-dimensional array in C?
(6 answers)
Closed 9 years ago.
We just started going over pointers in my C/C++ class, and I'm a bit confused at how they work. I am presented with this problem:
Assume you have declared a multi-dimensional array char ma[5][30].
What is the address of the element "ma[0][0]"? Use two (2) different
ways to assign the address to a pointer variable, p. (Do not give a
concrete address for ma[0][0] - this will vary each time the program
is run, based on the memory allocated.)
I know how much this community frowns on providing the "answers" to a homework problem. I don't need the answer, but hopefully someone can explain to me how I can use a pointer to get the address of an element in an array?
The two ways would be
char* p1 = (char*)ma;
char* p2 = &ma[0][0];
The first one works because "ma" is already a pointer to a location in memory where the array is stored.
The second one works using the address-of operator (&). &ma[0][0] translates to "the address of ma, element 0,0"
Lets say you have a pointer p; Grab the memory with the & operator.
p = &ma[row][col];
Or you can have a pointer from the variable name of the array.
p = ma;
Then you use pointer arithmetic to access this.
p = p + (row * num_per_col + col);
There are actually three straightforward ways to obtain a pointer to the element at index (0,0), two of which are cast-free:
char const* const p1 = (char const*)ma;
char const* const p2 = ma[0];
char const* const p3 = &ma[0][0];
The reason why these work is due to the memory layout of ma. Your matrix is just a series of 150 consecutive chars (i.e. 150 bytes). C allows us to decay arrays to pointers to their respective first element whenever we wish to. In fact, this mechanism is so promiscuous, that people are sometimes led to believe there to be no difference between an array and a pointer.
Let's start with the second line (p2). ma[0] is a one-dimensional array of 30 elements of type char and C allows us to have that expression decay into a pointer, pointing at the very first element in the 30 char array. The third line (p3), explicitly fetches the address of element [0][0]. To use this, we don't even have to understand how the memory is laid out.
The first line (p1) is a bit nasty, because it involves a cast. Normally, we could have ma decay to a pointer to a char array, since the first element of ma is an array, not a char. But we know that the very first element of that array is the char we are looking for, so we rely on the array to have no padding before the first element and reinterpret the address of the entire first array as the address of its first char.
You can get it like
&ma[0][0];
If you have a multidimensional array defined as int [][], then x = y[a][b] is equivalent to x = *((int *)y + a * NUMBER_OF_COLUMNS + b); (Reference Answer)
Except when it is the operand of the sizeof or unary & operators, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.
Assuming the declaration
char ma[5][30];
then all of the following are true:
The expression ma has type "5-element array of 30-element array of char". Unless ma is an operand to either sizeof or unary &, it will be converted to an expression of type "pointer to 30-element array of char", or char (*)[30], and its value will be the address of the first element in the array, or &ma[0].
The expression ma[i] has type "30-element array of char". Unless ma[i] is an operand to either sizeof or unary &, it will be converted to an expression of type "pointer to char", or char *, and its value will be the address of the first element in the array, or &ma[i][0].
The expression ma[i][j] has type char.
The expression &ma has type "pointer to 5-element array of 30-element array of char, or char (*)[5][30].
The expression &ma[i] has type char (*)[30].
The expression &ma[i][j] has type char *.
The values of the expressions ma, &ma, ma[0], &ma[0], and &ma[0][0] are all the same; the address of the array is the same as the address of the first element of the array.
Note that the types char (*)[30] and char (*)[5][30] are not compatible with char *, or with each other; if you want to assign a value of those types to a variable of type char *, you will need to use an explicit cast, such as char *p = (char *) ma;.
Edit
Types matter; pointer arithmetic is based on the pointed-to type, so an expression like ptr+1 will give different results based on the type that ptr points to. For example:
#include <stdio.h>
int main( void )
{
char ma[5][30] = {{0}};
char (*p0)[5][30] = &ma;
char (*p1)[30] = &ma[0];
char *p2 = &ma[0][0];
printf("%5s%-15s%-15s\n"," ","ptr","ptr+1");
printf("%5s%-15s%-15s\n"," ","-----","-----");
printf("%-5s%-15p%-15p\n","p0", (void *) p0, (void *) (p0+1));
printf("%-5s%-15p%-15p\n","p1", (void *) p1, (void *) (p1+1));
printf("%-5s%-15p%-15p\n","p2", (void *) p2, (void *) (p2+1));
return 0;
}
I create three pointers of different types; p0 is of type char (*)[5][30] and takes the result of &ma, p1 is of type char (*)[30] and takes the result of &ma[0], and p2 is of type char * and takes the result of &ma[0][0]. I then print the value of each pointer, than the value of each pointer plus 1. Here are the results:
ptr ptr+1
----- -----
p0 0x7fff36670d30 0x7fff36670dc6
p1 0x7fff36670d30 0x7fff36670d4e
p2 0x7fff36670d30 0x7fff36670d31
Each pointer starts out with the same value, but adding 1 to the pointer gives different results based on the type. p0 points to a 5x30-element array of char, so p0 + 1 will point to the beginning of the next 5x30-element array of char. p1 points to a 30-element array of char, so p1 + 1 points to the next 30-element array of char (ma[1]). Finally, p2 points to a single char, so p2 + 1 points to the next char (ma[0][1]).
Or you get the value at (x,y) from the array with addr = &ma[0][0] + sizeof(<type of array>)*columns * x + sizeof(<type of array>) * y
I have been programming c/c++ for many years, but todays accidental discovery made me somewhat curious... Why does both outputs produce the same result in the code below? (arr is of course the address of arr[0], i.e. a pointer to arr[0]. I would have expected &arr to be the adress of that pointer, but it has the same value as arr)
int arr[3];
cout << arr << endl;
cout << &arr << endl;
Remark: This question was closed, but now it is opened again. (Thanks ?)
I know that &arr[0] and arr evaluates to the same number, but that is not my question! The question is why &arr and arr evaluates to the same number. If arr is a literal (not stored anyware), then the compiler should complain and say that arr is not an lvalue. If the address of the arr is stored somewhere then &arr should give me the address of that location. (but this is not the case)
if I write
const int* arr2 = arr;
then arr2[i]==arr[i] for any integer i, but &arr2 != arr.
#include <cassert>
struct foo {
int x;
int y;
};
int main() {
foo f;
void* a = &f.x;
void* b = &f;
assert(a == b);
}
For the same reason the two addresses a and b above are the same. The address of an object is the same as the address of its first member (Their types however, are different).
arr
_______^_______
/ \
| [0] [1] [2] |
--------------------+-----+-----+-----+--------------------------
some memory | | | | more memory
--------------------+-----+-----+-----+--------------------------
^
|
the pointers point here
As you can see in this diagram, the first element of the array is at the same address as the array itself.
They're not the same. They just are at the same memory location. For example, you can write arr+2 to get the address of arr[2], but not (&arr)+2 to do the same.
Also, sizeof arr and sizeof &arr are different.
The two have the same value but different types.
When it's used by itself (not the operand of & or sizeof), arr evaluates to a pointer to int holding the address of the first int in the array.
&arr evaluates to a pointer to array of three ints, holding the address of the array. Since the first int in the array has to be at the very beginning of the array, those addresses must be equal.
The difference between the two becomes apparent if you do some math on the results:
arr+1 will be equal to arr + sizeof(int).
((&arr) + 1) will be equal to arr + sizeof(arr) == arr + sizeof(int) * 3
Edit: As to how/why this happens, the answer is fairly simple: because the standard says so. In particular, it says (§6.3.2.1/3):
Except when it is the operand of the sizeof operator or the unary & operator, or is a
string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue.
[note: this particular quote is from the C99 standard, but I believe there's equivalent language in all versions of both the C and C++ standards].
In the first case (arr by itself), arr is not being used as the operand of sizeof, unary &, etc., so it is converted (not promoted) to the type "pointer to type" (in this case, "pointer to int").
In the second case (&arr), the name obviously is being used as the operand of the unary & operator -- so that conversion does not take place.
The address is the same but both expressions are different. They just start at the same memory location. The types of both expressions are different.
The value of arr is of type int * and the value of &arr is of type int (*)[3].
& is the address operator and the address of an object is a pointer to that object. The pointer to an object of type int [3] is of type int (*)[3]
They are not the same.
A bit more strict explanation:
arr is an lvalue of type int [3]. An attempt to use
arr in some expressions like cout << arr will result in lvalue-to-rvalue conversion which, as there are no rvalues of array type, will convert it to an rvalue of type int * and with the value equal to &arr[0]. This is what you can display.
&arr is an rvalue of type int (*)[3], pointing to the array object itself. No magic here :-) This pointer points to the same address as &arr[0] because the array object and its first member start in the exact same place in the memory. That's why you have the same result when printing them.
An easy way to confirm that they are different is comparing *(arr) and *(&arr): the first is an lvalue of type int and the second is an lvalue of type int[3].
Pointers and arrays can often be treated identically, but there are differences. A pointer does have a memory location, so you can take the address of a pointer. But an array has nothing pointing to it, at runtime. So taking the address of an array is, to the compiler, syntactically defined to be the same as the address of the first element. Which makes sense, reading that sentence aloud.
I found Graham Perks' answer to be very insightful, I even went ahead and tested this in an online compiler:
int main()
{
int arr[3] = {1,2,3};
int *arrPointer = arr; // this is equivalent to: int *arrPointer = &arr;
printf("address of arr: %p\n", &arr);
printf("address of arrPointer: %p\n", &arrPointer);
printf("arr: %p\n", arr);
printf("arrPointer: %p\n", arrPointer);
printf("*arr: %d\n", *arr);
printf("*arrPointer: %d\n", *arrPointer);
return 0;
}
Outputs:
address of arr: 0x7ffed83efbac
address of arrPointer: 0x7ffed83efba0
arr: 0x7ffed83efbac
arrPointer: 0x7ffed83efbac
*arr: 1
*arrPointer: 1
It seems the confusion was that arr and arrPointer are equivalent. However, as Graham Parks detailed in his answer, they are not.
Visually, the memory looks something like this:
[Memory View]
[memory address: value stored]
arrPointer:
0x7ffed83efba0: 0x7ffed83efbac
arr:
0x7ffed83efbac: 1
0x7ffed83efbb0: 2
0x7ffed83efbb4: 3
As you can see, arrPointer is a label for memory address 0x7ffed83efba0 which has 4 bytes of allocated memory which hold the memory address of arr[0].
On the other hand, arr is a label for memory address 0x7ffed83efbac, and as per Jerry Coffin's answer, since the type of variable arr is "array of type", it gets converted to a "pointer of type" (which points to the array's starting address), and thus printing arr yields 0x7ffed83efbac.
The key difference is arrPointer is an actual pointer and has its own memory slot allocated to hold the value of the memory it's pointing to, so &arrPointer != arrPointer. Since arr is not technically a pointer but an array, the memory address we see when printing arr is not stored elsewhere, but rather determined by the conversion mentioned above. So, the values (not types) of &arrPointer and arrPointer are equal.