As the code below, function pointer y, z, b behave the same. Even *x would be understand as data of pointer x and &x would understand as pointer to x.
typedef void (*func)(void);
void x(void){
    printf("asdsag\n");
    return;
}
int main(int argc, char const *argv[])
{
    func y = *x;
    func z = x;
    func b = &x;
    y();
    z();
    b();
    return 0;
}
Now look at this code.
void test(int **p){
    printf("%p\n",p);
    return;
}
int main(int argc, char const *argv[])
{
    int * p[5];
    test(p);
    test(&p);
    return 0;
}
This code print same pointer for all 3 case. Here is asembly after compile above code
main:
    @ args = 0, pretend = 0, frame = 32
    @ frame_needed = 1, uses_anonymous_args = 0
    push    {r7, lr}
    sub sp, sp, #32
    add r7, sp, #0
    str r0, [r7, #4]
    str r1, [r7]
    ldr r3, [r7, #12]
    mov r0, r3
    bl  test
    add r3, r7, #12
    mov r0, r3
    bl  test
    add r3, r7, #12
    mov r0, r3
    bl  test
    movs    r3, #0
    mov r0, r3
    adds    r7, r7, #32
    mov sp, r7
    @ sp needed
    pop {r7, pc}
    .size   main, .-main
It is seem that they take same r7+#12 for all three case.
[Question] For all given information above, what should I understand p,*p,&p in C99?
[Info] This is build with gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabihf
[Update] Update for @tadman about imcompatible pointer
#include <stdio.h>
void test1(void **p){
    printf("%p\n",p);
    return;
}
void test2(void *p){
    printf("%p\n",p);
    return;
}
int main(int argc, char const *argv[])
{
    void * p[5] = {0};
    test1(p);
    test2(&p);
    return 0;
}
There is no warning even with -Wall
Here is the output:
0x7fff5615bb20
0x7fff5615bb20
P/S I was wrong in the first case, so I delete it.
 
     
    