They are not the same. The first (int a[100][100]) is a single variable that is a compound object. The other (int **p) is a collection of arrays that you are using as a data structure for matrices.
If you want to have an actual 2D array in dynamic storage, this is how you do it:
#include <stdlib.h>
int main()
{
int (*m)[100][100];
m = malloc(sizeof *m);
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++)
(*m)[i][j] = 0;
}
Of course, the syntax is a bit weird, and you would rather have a dynamic matrix with variadic number of columns and rows, which is why you would prefer to declare a matrix pointed by int **p.
The reason why a and *a give the same output is because both decay to a pointer to the first element of a, which is a[0][0]. On the other hand, p is a pointer itself, and *p is the contents of the variable pointed by p. They are just as different as they would be if you did this:
int d = 0;
int *p = &d;
printf("%p\n", p);
printf("%d\n", *p);
Now back to your int **p.
Yes, you can access both int a[][100] and int **p with double indexing. However, there is a fundamental difference in the way the compiler treats a[i][j] and p[i][j].
In a[i][j], each a[i] is an array of 100 integer objects. So in order to access the i-th element, and then the j-th element, the compiler has to acess the i*100+j-th element from a[0][0]. This access can be performed in a single step with some index arithmetic.
In v[i][j], each v[i] is a pointer which may point to objects far from each other in memory. In order to acess the element v[i][j], the compiler must first follow p to the array *p, then find the i-th element in this array, which is a pointer to the array p[i]. And then with some pointer arithmetic it will find the j-th element of this array.