代码之家  ›  专栏  ›  技术社区  ›  ChristopheD

取消引用指针上的后增量?

  •  47
  • ChristopheD  · 技术社区  · 17 年前

    为了理解C中指针的行为,我对下面的代码示例感到有点惊讶:

    #include <stdio.h>
    
    void add_one_v1(int *our_var_ptr)
    {
        *our_var_ptr = *our_var_ptr +1;
    }
    
    void add_one_v2(int *our_var_ptr)
    {
        *our_var_ptr++;
    }
    
    int main()
    {
        int testvar;
    
        testvar = 63;
        add_one_v1(&(testvar));         /* Try first version of the function */
        printf("%d\n", testvar);        /* Prints out 64                     */
        printf("@ %p\n\n", &(testvar));
    
        testvar = 63;
        add_one_v2(&(testvar));         /* Try first version of the function */
        printf("%d\n", testvar);        /* Prints 63 ?                       */
        printf("@ %p\n", &(testvar));   /* Address remains identical         */
    }
    

    输出:

    64
    @ 0xbf84c6b0
    
    63
    @ 0xbf84c6b0
    

    到底是什么 *our_var_ptr++ 第二个函数中的语句( add_one_v2 )因为它显然不同于 *our_var_ptr = *our_var_ptr +1 ?

    12 回复  |  直到 9 年前
        1
  •  79
  •   Mark Ransom    17 年前

    这是让C和C++非常有趣的小把戏之一。如果你想弯曲你的大脑,想一想:

    while (*dst++ = *src++) ;
    

    这是一个字符串副本。指针不断递增,直到复制值为零的字符。一旦你知道了这个技巧的作用,你就永远不会忘记++是如何在指针上工作的。

    请注意,您始终可以用括号覆盖操作员顺序。以下内容将增加指向的值,而不是指针本身:

    (*our_var_ptr)++;
    
        2
  •  49
  •   hbw    17 年前

    ++ 是后缀运算符, add_one_v2() 取消对指针的引用,但 ++ 实际上正在应用 . 但是,请记住,C始终使用传递值: 添加_one_v2() 正在增加其 本地副本

    添加_one_v2()

    void add_one_v2(int *our_var_ptr)
    {
        (*our_var_ptr)++;  // Now stores 64
    }
    
    void add_one_v2(int *our_var_ptr)
    {
        *(our_var_ptr++);  // Increments the pointer, but this is a local
                           // copy of the pointer, so it doesn't do anything.
    }
    
        3
  •  44
  •   BIBD    11 年前

    好啊

    *our_var_ptr++;
    

    1. 解引用首先发生,为您提供由 our_var_ptr (其中包含63个)。
    2. 然后对表达式求值,63的结果仍然是63。
    3. 结果被扔掉了(你没有用它做任何事情)。
    4. 然后在求值后递增。它在改变指针指向的位置,而不是它指向的对象。

    这实际上与执行此操作相同:

    *our_var_ptr;
    our_var_ptr = our_var_ptr + 1; 
    

    有道理?马克·兰森的回答有一个很好的例子,除了他实际使用了结果。

        4
  •  8
  •   dmckee --- ex-moderator kitten    17 年前

    这里有很多混乱,所以这里有一个修改过的测试程序,以使发生的事情变得清楚(或者至少清楚) 呃 ):

    #include <stdio.h>
    
    void add_one_v1(int *p){
      printf("v1: pre:   p = %p\n",p);
      printf("v1: pre:  *p = %d\n",*p);
        *p = *p + 1;
      printf("v1: post:  p = %p\n",p);
      printf("v1: post: *p = %d\n",*p);
    }
    
    void add_one_v2(int *p)
    {
      printf("v2: pre:   p = %p\n",p);
      printf("v2: pre:  *p = %d\n",*p);
        int q = *p++;
      printf("v2: post:   p = %p\n",p);
      printf("v2: post:  *p = %d\n",*p);
      printf("v2: post:   q = %d\n",q);
    }
    
    int main()
    {
      int ary[2] = {63, -63};
      int *ptr = ary;
    
        add_one_v1(ptr);         
        printf("@ %p\n", ptr);
        printf("%d\n", *(ptr));  
        printf("%d\n\n", *(ptr+1)); 
    
        add_one_v2(ptr);
        printf("@ %p\n", ptr);
        printf("%d\n", *ptr);
        printf("%d\n", *(ptr+1)); 
    }
    

    对于结果输出:

    v1: pre:   p = 0xbfffecb4
    v1: pre:  *p = 63
    v1: post:  p = 0xbfffecb4
    v1: post: *p = 64
    @ 0xbfffecb4
    64
    -63
    
    v2: pre:   p = 0xbfffecb4
    v2: pre:  *p = 64
    v2: post:  p = 0xbfffecb8
    v2: post: *p = -63
    v2: post:  q = 64
    
    @ 0xbfffecb4
    64
    -63
    

    1. 对指针本地副本的更改如下: 不 反映在调用指针中。
    2. add_one_v2 是 不 递增,以下两个值都不是,但指针是
    3. 之后

    为什么?

    • 因为 ++ * (作为解引用或乘法)因此 添加一个
    • 邮递 增量发生
        5
  •  7
  •   Dave Costa    17 年前

    正如其他人所指出的,运算符优先级导致v2函数中的表达式被视为 *(our_var_ptr++)

    但是,由于这是一个后增量运算符,所以不能说它先递增指针,然后取消引用它。如果这是真的,我不认为你会得到63作为你的输出,因为它会在下一个内存位置返回值。实际上,我相信操作的逻辑顺序是:

    1. 递增指针

    正如htw所解释的,您没有看到指针值的变化,因为它是按值传递给函数的。

        6
  •  4
  •   marc_s MisterSmith    6 年前

    如果不使用括号指定操作顺序,则前缀和后缀增量都优先于引用和取消引用。但是,前缀增量和后缀增量是不同的操作。在++x中,运算符引用变量,向其中添加一个引用,然后按值返回。在x++中,运算符递增变量,但返回其旧值。它们的行为有点像这样(假设它们在类中声明为方法):

    //prefix increment (++x)
    auto operator++()
    {
        (*this) = (*this) + 1;
        return (*this);
    }
    
    //postfix increment (x++)
    auto operator++(int) //unfortunately, the "int" is how they differentiate
    {
        auto temp = (*this);
        (*this) = (*this) + 1; //same as ++(*this);
        return temp;
    }
    

    (请注意,后缀增量中包含一个副本,因此效率较低。这就是为什么您应该在循环中使用++i而不是i++的原因,尽管现在大多数编译器会自动为您执行此操作。)

    如您所见,postfix increment是首先处理的,但是,由于它的行为方式,您将取消对指针先前值的引用。

    以下是一个例子:

    char * x = {'a', 'c'};
    char   y = *x++; //same as *(x++);
    char   z = *x;
    

    在第二行中,指针x将在取消引用之前递增,但取消引用将发生在旧值x上(这是一个 住址 由后缀增量返回)。因此y将被初始化为“a”,z将被初始化为“c”。但如果你这样做:

    char * x = {'a', 'c'};
    char   y = (*x)++;
    char   z = *x;
    

    这里,x将被取消引用,而 价值

    现在让我们检查前缀情况:

    char * x = {'a', 'c'};
    char   y = *++x; //same as *(++x)
    char   z = *x;
    

    在这里,解引用将发生在x的增量值上(由前缀增量运算符立即返回),因此y和z都将用“c”初始化。要获得不同的行为,可以更改运算符的顺序:

    char * x = {'a', 'c'};
    char   y = ++*x; //same as ++(*x)
    char   z = *x;
    

    在这里,您要确保首先递增x的内容,并且指针的值永远不会改变,因此y和z将被指定为“b”。在 拷贝字符串 函数(在其他答案中提到),增量也首先完成:

    char * strcpy(char * dst, char * src)
    {
        char * aux = dst;
        while(*dst++ = *src++);
        return aux;
    }
    

    在每次迭代中,首先处理src++并作为后缀增量返回src的旧值。然后,src的旧值(它是一个指针)被取消引用,以分配给赋值运算符左侧的任何值。然后dst递增,其旧值被解引用为左值并接收旧src值。这就是为什么dst[0]=src[0],dst[1]=src[1]等等,直到*dst被分配了0,循环就中断了。

    增编:

    这个答案中的所有代码都是用C语言测试的。在C++中,你可能无法列出指针的初始化。所以,如果你想测试C++中的例子,你应该先初始化一个数组,然后将它降级为指针:

    char w[] = {'a', 'c'};
    char * x = w;
    char   y = *x++; //or the other cases
    char   z = *x;
    
        7
  •  3
  •   gbjbaanb    17 年前

    所以 *our_var_ptr = *our_var_ptr+1 取消对指针的引用,并将一个指针添加到该位置的值中。

    现在添加运算符优先级-将其作为 (*our_var_ptr) = (*our_var_ptr)+1

    在另一个示例中,++运算符的优先级低于*,因此它接受传入的指针,向其中添加一个指针(因此它现在指向垃圾),然后返回。(请记住,在C中,值总是通过值传递的,因此当函数返回原始testvar指针时,指针保持不变,您只更改了函数内部的指针)。

    我的建议是,当使用去引用(或任何其他)时,使用括号来明确你的决定。不要试图记住优先规则,因为有一天你只会使用另一种稍微不同的语言,你会感到困惑。或者变老,最终忘记哪个优先级更高(就像我使用*和->)。

        8
  •  2
  •   G. Simsic    9 年前

    我将试着从一个不同的角度来回答这个问题。。。 第一步 让我们看一下运算符和操作数: 步骤2 哪个优先级更高 哪里是++,它在右边,意思是 邮递 在这种情况下,编译器会记下“注意事项”来执行增量 之后 这是与所有其他操作员一起完成的。。。 注意,如果它是*++p,那么它将在之前执行此操作 在这种情况下,这是一个棘手的问题,看起来很矛盾。 人们会期望++优先于*,它确实是这样做的,只是POST意味着它只能在所有其他操作数之后、下一个“;”之前应用代币

        9
  •  1
  •   ItM    8 年前

    来自K&R、 第105页:“*t++的值是t在递增之前指向的字符”。

        10
  •  1
  •   pgibbons    8 年前
        uint32_t* test;
        test = &__STACK_TOP;
    
    
        for (i = 0; i < 10; i++) {
            *test++ = 0x5A5A5A5A;
        }
    
        //same as above
    
        for (i = 0; i < 10; i++) {
            *test = 0x5A5A5A5A;
            test++;
        }
    

    因为test是一个指针,所以test++(这是不取消引用的)将增加指针(它增加test的值,该值恰好是指向的(目标)地址)。因为目标是uint32_t类型,所以test++将增加4个字节,如果目标是例如这种类型的数组,那么test现在将指向下一个元素。

            ((unsigned char*) test)++;
    

        11
  •  0
  •   Martin Cote    17 年前

    “++”运算符的优先级高于“*”运算符,这意味着指针地址将在取消引用之前递增。

    但是,“+”运算符的优先级低于“*”。

        12
  •  0
  •   YenForYang    5 年前

    图片大约值一千个单词(给或拿一百万左右)……符号可以是图片(反之亦然)

    对于我们这些寻求 tl;dr (优化数据消耗)但仍然需要(大部分)无损编码,矢量图像/图片/插图/演示是最重要的。

    换句话说,忽略我的最后两个陈述,看看下面。


    *a++ ≣ *(a++)
         ≣ (a++)[0] ≣ a++[0]
         ≣ 0[a++]  // Don't you dare use this (“educational purposes only”)
         // These produce equivalent (side) effects;
           ≡ val=*a,++a,val
           ≡ ptr=a,++a,*ptr
           ≡ *(ptr=a,++a,ptr)
    
    *++a ≣ *(++a)
         ≣ *(a+=1)  ≣ *(a=a+1)
         ≣ (++a)[0] ≣ (a+=1)[0] ≣ (a=a+1)[0]  // ()'s are necessary
                    ≣ 0[++a]   // 0[a+=1], etc... “educational purposes only”
         // These produce equivalent (side) effects:
           ≡ ++a,*a
           ≡ a+=1,*a
           ≡ a=a+1,*a
    
    ++*a ≣ ++(*a)
         ≣ *a+=1
         ≣ *a=*a+1
         ≣ ++a[0] ≣ ++(a[0])
                  ≣ ++0[a]  // STAY AWAY
    
    (*a)++  // Note that this does NOT return a pointer;
            // Location `a` points to does not change
            // (i.e. the address `a` is unchanged)
            ≡ val=*a,++*a,val
    

    放弃目标标识符:*/
    a++*;

    ++a*;
        13
  •  -1
  •   bongo    14 年前

    因为指针是通过值传入的,所以只有本地副本会递增。如果确实要增加指针,则必须通过引用将其传入,如下所示:

    void inc_value_and_ptr(int **ptr)
    {
       (**ptr)++;
       (*ptr)++;
    }