【问题标题】:How should I understand theese C syntax? [duplicate]我应该如何理解这些 C 语法? [复制]
【发布时间】:2020-11-18 06:16:10
【问题描述】:

如下面的代码,函数指针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 第一种情况我错了,所以我删除它。

【问题讨论】:

  • 第二段代码应该会产生很多警告。试试-Wall。仅仅因为它编译并不意味着它是正确的
  • GCC 和许多编译器一样,会尝试按照您的要求去做,即使它没有任何意义。将一些随机的int 投射到指针中是可以的,只要您从不使用指针,但这也意味着它毫无意义。仅仅因为汇编代码相同并不意味着您已经定义了行为,只是一致行为。这与既定行为之间存在巨大差距。
  • 是的,这很重要,因为只有一种用法是有效的,其余的只是垃圾数据。 “不兼容的指针类型”是个大问题。
  • 使用未初始化的数据也是一件大事。
  • 这取决于p 是什么。如果它是一个函数,那么*p&amp;pp 基本上都是一样的,甚至&amp;**&amp;&amp;*&amp;*p 因为这些运算符什么都不做。如果它是像 int 这样的原语,那么运营商就是一笔巨大的交易,并且会从根本上改变它所代表的内容。

标签: c c99


【解决方案1】:

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

§ 6.3.2.1

左值、数组和函数指示符

...

  1. 函数指示符是具有函数类型的表达式。除非它是 sizeof 运算符,67) 或一元 & 运算符,类型为“函数返回”的函数指示符 type”被转换为具有“指向函数返回类型的指针”类型的表达式。

强调-

具有“函数返回”类型的函数指示符 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(&amp;p),您将传递int*(*)[5] 类型的值。再一次,不同级别的间接 - 而不是函数类型 - 真正的规则适用于此。行为未定义。

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

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

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

3.4.3

未定义的行为

在使用不可移植或错误程序结构或错误数据时的行为 本文件无任何要求

【讨论】:

  • 6.3.2.3/8 根本不是为什么 OP 的第一个示例的 abc 相同。这来自 6.3.2.1/4:“除非它是 [... 或] 一元 & 运算符的操作数,类型为“函数返回类型”的函数指示符被转换为类型为“函数指针”的表达式返回类型'"。
  • 不,OP 的p 的类型不是 int **。它是int *[5]——一个由 5 个指向 int 的指针组成的数组。指针和数组根本不是一回事,尽管数组类型的表达式会自动转换为指针,就像函数类型的表达式(6.3.2.1/3)一样。因此,&amp;p 的类型也不是int ***——它是int *(*)[5],指向由 5 个指向 int 的指针组成的数组的指针。这些差异在 C 代码中是可以区分的。
  • @JohnBollinger 啊,我明白了,进行了相应的编辑
  • 我更新了第三个例子,也请举例。
  • 谢谢,这指出了数组和指针的混淆,C中的左值右值。这个问题指出了我的大部分困惑。我会处理剩下的。
【解决方案2】:

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

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&amp;p 是同一件事,函数上的&amp; 运算符是可选的,基本上是“什么都不做”运算符。在铿锵声中,至少* 也是一个无操作,没有记录,但似乎是这样。因此,您可以执行 &amp;p*p&amp;*p****&amp;&amp;**&amp;*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;
}

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

【讨论】:

  • 冒着让 OP 更加混乱的风险,但是因为什么都不说也会让他们更加困惑,&amp;p 的类型是 int *(*)[5],而不是 int ***(当然也不是 @987654334 @,或者,正如你所说)。
  • 请检查我更新的问题
  • @JohnBollinger 我只是在报告完全铿锵的说法。它还在另一条消息中讨论了该版本,但“无效”备注与所示完全相同。
  • Clang 的诊断在这方面非常具有误导性。 FWIW,GCC为示例代码发出的诊断正确地表达了该类型。我不太关心诊断,但人们了解所涉及的类型很重要。 int ***int *(*)[5] 是不一样的,它们在一些重要方面的表现不同(即差异不可忽略)。
猜你喜欢
  • 2015-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-06
  • 2013-10-09
  • 1970-01-01
相关资源
最近更新 更多