p 是一个指向结构的指针,所有这些代码片段都做了什么?

p is a pointer to a structure, what do all these code snippets do?

++p->i 
p++->i
*p->i
*p->i++
(*p->i)++
*p++->i

上面这些说法我看不懂,我写了一个小测试程序来理解它们。

#include <stdio.h>

struct my_structure {
    int i;
};

void main() {
    struct my_structure variable = {20};
    struct my_structure *p = &variable;

    printf("NAME: %d\n", ++p->i);
    printf("NUMBER: %d\n", p++->i);
    printf("RANK: %d", *p->i++);
    printf("name: %d\n", *p->i++);
    printf("number: %d\n", (*p->i)++);
    printf("rank: %d", *p++->i);
}

这是我评论最后四个 print 语句后得到的输出:

NAME: 21
NUMBER: 21

取消注释代码并编译后我得到:

test.c: In function 'main':
test.c:14:24: error: invalid type argument of unary '*' (have 'int')
printf("RANK: %d", *p->i++);
                    ^~~~~~~
test.c:15:26: error: invalid type argument of unary '*' (have 'int')
printf("name: %d\n", *p->i++);
                      ^~~~~~~
test.c:16:29: error: invalid type argument of unary '*' (have 'int')
printf("number: %d\n", (*p->i)++);

                         ^~~~~
test.c:17:24: error: invalid type argument of unary '*' (have 'int')
printf("rank: %d", *p++->i);

查看在线评论:

struct my_structure {
    int i;
};

void main() {
    struct my_structure variable = {20};
    struct my_structure *p = &variable;

    printf("NAME: %d\n", ++p->i);     //pre-increments i by 1 (prints 21)
    printf("NUMBER: %d\n", (p++)->i);   //changes location pointed to. (meaningless, ub, printed 3 for me)
    //printf("RANK: %d", *p->i++);      // error, (will not be included in build)
    //printf("name: %d\n", *p->i++);      // error, (will not be included in build)
    //printf("number: %d\n", (*p->i)++);// error, (will not be included in build)
    //printf("rank: %d", *p++->i);    // error, (will not be included in build)

    getchar();
}

虽然第一条和第二条语句在语法上都是正确的(即编译、构建和 运行 没有任何问题的迹象)只有第一条是有意义的,尽管我不知道是为了什么目的。鉴于声明:struct my_structure variable = {20};,只定义了一个内存位置,指针的第二个增量将其位置超出您的定义,并指向一个未知值。在我的 运行 中,它指向 3,但可能包含任何内容。并且,由于它不被拥有,调用 undefined behavior。这就是为什么 运行 在不同的 PC 上,甚至在不同时间在同一台 PC 上运行生成的可执行文件很可能会产生不同的结果。

让我们按运算符分解。

x->y:这会取消引用指向结构的指针 (x),然后访问指示的成员变量 (y)。仅当 x 是指向结构的指针时才有意义。也等同于(*x).y.

++x:前增量x。这会将 x 的值增加 1,然后 returns x 的新值。它的优先级低于 -> 运算符,因此 ++p->i 将像上面那样获取 i,然后递增它。

x++:Post-增量 x。这会将 x 的值增加 1,然后 returns x 的旧值。但是,这一次,运算符具有相同的优先级并且从左到右执行。然后这将递增 p,然后取消引用 p 曾经访问 i 的位置。这将为您提供 i 的值,但现在 p 指向 uninitialized/unknown 内存(除非 p 在数组中,在这种情况下它现在指向下一个该数组的成员)。

*x:取消引用 x 就像我在上面 -> 下提到的,但在这个例子中我们现在做了两次,结果是等效的 ((**p).i)++)。但是,由于 p 指向结构而不是指向结构的指针,因此这是一个编译器错误。这也适用于下一个表达式,因为它只是明确地说明了编译器已经符合的相同操作顺序。

综上所述,我们来到最后一个,顺序为:

  1. 取消引用 p。 (到目前为止还不错)
  2. 增加那个结果。但是没有为结构定义增量,所以编译器错误。
  3. 取消引用该结果。同样,不再是指针,所以我们不能取消引用。编译器错误。
  4. 从该结果中访问成员 i

你可以看到我这里使用的运算符优先级规则:https://en.cppreference.com/w/c/language/operator_precedence

可以使代码正常工作(编译,运行 不会崩溃,并产生连贯、可解释的答案),但您需要与所选结构不同的结构。例如:

#include <stdio.h>

struct my_structure
{
    char *i;
};

#define EXPR(x) #x, x

int main(void)
{
    char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
    struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
    struct my_structure *p = variables;

    printf("%10s: %s\n", EXPR(++p->i));
    printf("%10s: %s\n", EXPR(p++->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %d\n", EXPR((*p->i)++));
    printf("%10s: %d\n", EXPR(*p++->i));
    return 0;
}

生成输出:

    ++p->i: inter
    p++->i: inter
   *p->i++: 66
   *p->i++: 97
 (*p->i)++: 115
   *p++->i: 116

EXPR 只是让我不重复代码中的表达式,而且还可以在调用 printf().

时获取字符串形式和值

当事情开始时,p->i 指向字符串 "Winter"

  • ++p->i: inter — 预先递增指针 p->i,使其指向 Winteri
  • p++->i: inter — post-递增指针p(指向"Bash"),但结果与之前相同,因为递增在之后生效p->i 被使用。
  • *p->i++: 66 — post-增加指针 p->i(因此它指向 Bash 中的 a)并报告之前指向的值增量,即 B(ASCII 中的 66)。
  • *p->i++: 97 — 相同的表达式,但指针指向递增前的 a (97) 和递增后的 s
  • (*p->i)++: 115 - post-增加 p->i 指向的字母,报告 s 但将其更改为 t.
  • *p++->i: 116 — post-增量 p 因此它指向字符串 "In",同时报告 t (116).

这是一个具有更多工具的替代方案:

#include <stdio.h>

struct my_structure
{
    char *i;
};

#define EXPR(x) #x, x

int main(void)
{
    char strings[][10] = { { "Winter" }, { "Bash" }, { "Is" }, { "Here" } };
    struct my_structure variables[] = { { strings[0] }, { strings[1] }, { strings[2] }, { strings[3] } };
    struct my_structure *p = variables;

    for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
        printf("strings[%zu] = [%s]\n", i, strings[i]);

    for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
        printf("variables[%zu].i = [%s]\n", i, variables[i].i);

    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %s\n", EXPR(++p->i));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %s\n", EXPR(p++->i));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p->i++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR((*p->i)++));
    printf("%10s: %s\n", EXPR(p->i));
    printf("%10s: %d\n", EXPR(*p++->i));
    printf("%10s: %s\n", EXPR(p->i));

    for (size_t i = 0; i < sizeof(strings)/sizeof(strings[0]); i++)
        printf("strings[%zu] = [%s]\n", i, strings[i]);

    for (size_t i = 0; i < sizeof(variables)/sizeof(variables[0]); i++)
        printf("variables[%zu].i = [%s]\n", i, variables[i].i);

    return 0;
}

及其输出:

strings[0] = [Winter]
strings[1] = [Bash]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [Winter]
variables[1].i = [Bash]
variables[2].i = [Is]
variables[3].i = [Here]
      p->i: Winter
    ++p->i: inter
      p->i: inter
    p++->i: inter
      p->i: Bash
   *p->i++: 66
      p->i: ash
   *p->i++: 97
      p->i: sh
 (*p->i)++: 115
      p->i: th
   *p++->i: 116
      p->i: Is
strings[0] = [Winter]
strings[1] = [Bath]
strings[2] = [Is]
strings[3] = [Here]
variables[0].i = [inter]
variables[1].i = [th]
variables[2].i = [Is]
variables[3].i = [Here]

尝试使用此方案的变体(例如,额外的括号)以确保您了解正在发生的事情。

首先,一些提醒:

p++ 的计算结果为 p 的当前值,并且作为 副作用 p 加 1。如果 p 是一个指针,它被设置为指向序列中的下一个对象。

++p 的计算结果为 p + 1 的当前值,并且作为副作用将 p 加 1。同样,如果 p 是一个指针,它被设置为指向序列中的下一个对象。

++的后缀形式和->运算符的优先级相同,比++和[=33=的一元(前缀)形式的优先级高].因此,像 ++p->i 这样的表达式被解析为 ++(p->i)p->i++ 被解析为 (p->i)++*p->i 被解析为 *(p->i),等等

除此之外...

表达式

++p->i

被解析为

++(p->i)

并评估为 p->i 的当前值加 1,并且作为 副作用 更新 p->i.

表达式

p++->i

被解析为

(p++)->i

并评估当前 p->i,然后更新 p 以指向序列中的下一个 struct 对象。

表达式

*p->i

被解析为

*(p->i)

因为 -> 的优先级高于一元 *。一元操作数 * 必须是指针类型,但是 p->i 是一个整数,所以编译器会在这个表达式上 yak。

表达式

*p->i++

被解析为

*((p->i)++)

同样,由于 * 的操作数不是指针类型,因此编译器将对该表达式进行检查。

表达式

(*p->i)++

被解析为

(*(p->i))++

同样,p->i 没有指针类型,所以编译器会出问题。

表达式

*p++->i

被解析为

*((p++)->i)

而且,再一次,更多的 yakkage。