【问题标题】:p is a pointer to a structure, what do all these code snippets do?p 是指向结构的指针,所有这些代码片段有什么作用?
【发布时间】:2019-05-13 19:20:55
【问题描述】:
++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);

【问题讨论】:

  • 有错误的4个表达式没有意义,因此它们什么都不做。
  • 提示:++p-&gt;i++(p-&gt;i) 相同,p++-&gt;i(p++)-&gt;i 相同。
  • 语法错误,语法错误的表达式毫无意义。
  • 仔细阅读我的第二条评论。我从来没有提到(p++-&gt;i)
  • @RajatGupta 语句无论出于何种原因都不能编译,根据定义是无用的。

标签: c function struct structure function-pointers


【解决方案1】:

首先,一些提醒:

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

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

++ 的后缀形式和-&gt; 运算符具有相同的优先级,并且它们的优先级高于++* 的一元(前缀)形式。因此,像++p-&gt;i 这样的表达式被解析为++(p-&gt;i)p-&gt;i++ 被解析为(p-&gt;i)++*p-&gt;i 被解析为*(p-&gt;i),等等。

不碍事...

表达式

++p->i

被解析为

++(p->i)

并计算为 p-&gt;i 的当前值加 1,作为 副作用 更新 p-&gt;i

表达式

p++->i

被解析为

(p++)->i

并计算为当前的p-&gt;i,然后更新p 以指向序列中的下一个struct 对象。

表达式

*p->i

被解析为

*(p->i)

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

表达式

*p->i++

被解析为

*((p->i)++)

再一次,编译器会在这个表达式上发怒,因为* 的操作数不是指针类型。

表达式

(*p->i)++

被解析为

(*(p->i))++

同样,p-&gt;i 没有指针类型,所以编译器会 yak。

表达式

*p++->i

被解析为

*((p++)->i)

还有更多的 yakkage。

【讨论】:

    【解决方案2】:

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

    #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-&gt;i 指向字符串"Winter"

    • ++p-&gt;i: inter — 将指针 p-&gt;i 预递增,使其指向 Winteri
    • p++-&gt;i: inter — 后递增指针p(指向"Bash"),但结果与之前相同,因为递增在使用p-&gt;i 后生效。
    • *p-&gt;i++: 66 — 后递增指针 p-&gt;i(因此它指向 Bash 中的 a)并报告在递增之前指向的值,即 B(ASCII 中的 66)。
    • *p-&gt;i++: 97 — 相同的表达式,但指针在增量之前指向 a (97),在增量之后指向 s
    • (*p-&gt;i)++: 115 — 增加p-&gt;i 指向的字母,报告s,但将其更改为t
    • *p++-&gt;i: 116 — 后增量 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]
    

    使用此方案的变体(例如,额外的括号)以确保您了解发生了什么。

    【讨论】:

      【解决方案3】:

      让我们按运算符分解。

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

      ++x:预增量x。这会将x 的值增加1,然后返回x 的新值。它的优先级低于-&gt; 运算符,所以++p-&gt;i 会像上面那样获取i,然后递增它。

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

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

      把所有这些放在一起,我们来到最后一个,它会按顺序:

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

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

      【讨论】:

        【解决方案4】:

        在线查看 cmets:

        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 上运行生成的可执行文件很可能会产生不同的结果。

        【讨论】:

        • 当我在我的机器上运行此代码时,它会打印 NAME: 21 和 'NUMBER: 21'。
        • @RajatGupta - 未定义的行为可能导致第二条语句出现意外。
        • @Jabberwocky - 如果我解释正确,它不会改变指针的地址吗?在这种情况下,结构变量无效。打印该位置会产生一些不确定的值。
        • @ryyker hm,是的,它指向声明中未定义的位置,但由于我们没有取消引用它,我不确定它是 UB。这是一个角落案例。也许它本身就值得一个 SO 问题。
        • 对于未定义行为的问题感兴趣的人,here is a tertiary question 就该主题,具体到这个。
        猜你喜欢
        • 1970-01-01
        • 2018-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-03
        • 1970-01-01
        • 2021-01-25
        • 1970-01-01
        相关资源
        最近更新 更多