我应该如何理解这些 C 语法?

How should I understand theese C syntax?

如下代码,函数指针yzb的行为相同。即使 *x 也会被理解为指针 x 的数据,而 &x 也会被理解为指向 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;
}

现在看这段代码。

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;
}

此代码为所有 3 种情况打印相同的指针。 这是编译以上代码后的汇编

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

这三种情况似乎都采用相同的 r7+#12

[问题] 对于上面给出的所有信息,我应该如何理解 C99 中的 p*p&p

[信息] 这是用 gcc-linaro-7.2.1-2017 构建的。11-x86_64_arm-linux-gnueabihf

[更新] @tadman 关于不兼容指针的更新

#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;
}

即使 -Wall 也没有警告 这是输出:

0x7fff5615bb20
0x7fff5615bb20

P/S第一种情况我错了,删了

在第一种情况下,您有一个函数,如下所示:

typedef void (*f)(void);

void p(void) {
}

int main(int argc, char const *argv[]) {
    func a = p;
    func b = *p;
    func c = &p;
    a();
    b();
    c();

    return 0;
}

所有这三个都起作用,因为 p&p 是同一件事,函数上的 & 运算符是可选的,基本上是一个“什么都不做”的运算符。在 clang 上至少 * 也是一个空操作,没有记录但似乎是这种情况。因此,您可以执行 &p*p&*p****&&**&*p 并且您会得到相同的结果。

在第二种情况下你有实际数据:

void test(int **p) {
}

int main(int argc, char const *argv[]) {
    int *p[5];

    test(p);  // Valid: int** -> int**
    test(&p); // Invalid: Incompatible pointer int*** -> int**
    test(*p); // Invalid: incompatible pointer int* -> int**

    return 0;
}

第一个是已定义的行为,您正在传递一个相同的指针类型,因此它可以工作。后两个只是垃圾代码。也许他们会编译,但行为是未定义的。无论他们做什么都是完全随意的。

不要混淆函数指针和常规指针的间接级别。这是关于 C 标准的一个特别古怪的事情。

§ 6.3.2.1

Lvalues, arrays, and function designators

...

  1. A function designator is an expression that has function type. Except when it is the operand of the sizeof operator,67) or the unary & operator, a function designator with type "function returning type" is converted to an expression that has type "pointer to function returning type".

强调-

function designator with type "function returning type" is converted to an expression that has type "pointer to function returning type".

(提到标准的正确部分,感谢@John Bollinger)

这基本上意味着abc在这里都是一样的

func a = p;
func b = *p;
func c = &p;

所有这些都可以像常规函数一样处理,并用 (...) 调用。

虽然常规指针的行为非常非常不同 - 正如您的下一个示例。

int * p[5];
test(*p);
test(p);
test(&p);

所以int* p[5]声明了一个包含5个指针的数组。所以p的类型是int*[5]p中每个元素的类型(比如p[0])是int*.

因此,对于 test(*p),您只是传递了 p[0](因为 *(p + 0) == p[0] == *p)- 这是输入 int*。从 int*int** 的转换是未定义的,因此这是一个很大的禁忌。

使用 test(&p),您传递的是 int*(*)[5] 类型的值。再一次,不同级别的间接寻址——而不是函数类型——真正的规则在这里适用。行为未定义。

test(p) 很好,p,因为类型 int*[5] 会衰减到 int**,这是 test[=42 的有效参数=]

(经过编辑以反映指针类型的非衰减版本,感谢 @John Bollinger

此外,由于我们经常谈论未定义的行为,以防您感到困惑。它基本上意味着该行为没有明确定义的结果 - 您可能会在您的 PC 和您的环境中观察到某些 明显定义的 - 但在全球范围内不能保证相同的行为。

3.4.3

undefined behavior

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements