The array access operator is nothing else than an add and a dereference.
For example the following lines are similar:
printf("%d\n", my_arr[0]);
printf("%d\n", *my_arr);
So are these:
printf("%d\n", my_arr[1]);
printf("%d\n", *(my_arr + 1));
The last line uses pointer arithmetics. That means, my_arr is a pointer to a memory address and if we add something to it, it will point to an other memory address. Imagine your memory is a huge byte array. A memory address is an index to it. And when using the * operator, you are taking the value at this index. But whenever you are using the array access operator, you are first adding to the index/address and than taking the value.
Let's analyze it step by step.
int* my_arr = malloc(sizeof(int) * 5);
my_arr[0] = 0;
my_arr[2] = 5;
my_arr could now be for example 0xff560.
int* my_arr2 = my_arr + 2;
Now my_arr2 is 0xff568 and my_arr is unchanged. 8 is added to the pointer, because in this example one integer is 4 bytes large.
printf("%d %d\n", *my_arr, *my_arr2);
This will write "0 5". And the last two lines are just the complocated way to write:
printf("%d %d\n", my_arr[0], my_arr[2]);
Btw, you can output pointer values with "%p" in printf.