对解除引用的指针进行后递增?

时间:2009-05-13 19:01:51

标签: c pointers operator-precedence

尝试理解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明显不同?

13 个答案:

答案 0 :(得分:60)

这是让C和C ++变得如此有趣的小问题之一。如果你想弯曲大脑,请弄清楚这一点:

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

这是一个字符串副本。指针不断增加,直到复制了一个值为零的字符。一旦你知道为什么这个技巧有效,你就永远不会忘记++如何再次对指针起作用。

P.S。您始终可以使用括号覆盖运算符顺序。以下将增加指向的值,而不是指针本身:

(*our_var_ptr)++;

答案 1 :(得分:39)

由于运算符优先级规则以及++是后缀运算符的事实,add_one_v2()会取消引用指针,但++实际上是将应用于指针本身。但是,请记住C始终使用pass-by-value:add_one_v2()正在递增指针的本地副本,这对存储在该地址的值没有任何影响。

作为测试,用这些代码替换add_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.
}

答案 2 :(得分:32)

行,

*our_var_ptr++;

它的工作原理如下:

  1. 首先取消引用,为您提供由our_var_ptr(包含63)指示的内存位置。
  2. 然后评估表达式,63的结果仍然是63。
  3. 结果被丢弃(你没有做任何事情)。
  4. 然后在评估后递增
  5. our_var_ptr。它正在改变指针所指向的位置,而不是它所指向的位置。
  6. 实际上与此相同:

    *our_var_ptr;
    our_var_ptr = our_var_ptr + 1; 
    

    有意义吗? Mark Ransom的答案有一个很好的例子,除了他实际上使用了结果。

答案 3 :(得分:7)

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

#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. 对本地指针的目标的更改会影响调用指针的目标(至少在目标指针更新之前)
  3. add_one_v2中指向的值递增,且以下值都不是,但指针是
  4. add_one_v2中指针的增量在取消引用后发生
  5. 为什么?

    • 因为++*更紧密地绑定(作为取消引用或乘法),所以add_one_v2中的增量适用于指针,而不是它指向的位置。
    • post 增量在评估术语后发生,因此取消引用获取数组中的第一个值(元素0)。

答案 4 :(得分:6)

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

但是,由于这是一个后增量运算符,所以说它增加指针然后解引它就不完全正确。如果这是真的,我认为你不会得到63作为你的输出,因为它将返回下一个内存位置的值。实际上,我认为操作的逻辑顺序是:

  1. 保存指针的当前值
  2. 增加指针
  3. 取消引用步骤1中保存的指针值
  4. 正如htw所解释的那样,你没有看到指针值的变化,因为它是通过值传递给函数的。

答案 5 :(得分:3)

our_var_ptr是指向某个内存的指针。即它是存储数据的存储器单元。 (在这种情况下,int的二进制格式为4个字节)。

* our_var_ptr是解除引用的指针 - 它指向指针'指向'的位置。

++递增一个值。

如此。 *our_var_ptr = *our_var_ptr+1取消引用指针,并在该位置添加一个值。

现在添加运算符优先级 - 将其读作(*our_var_ptr) = (*our_var_ptr)+1,您会看到取消引用首先发生,因此您可以获取值并将其递增。

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

我的建议是,在使用解除引用(或其他任何内容)时,使用括号来明确您的决定。不要试图记住优先级规则,因为你最终会在某一天使用另一种语言稍微不同而且你会感到困惑。或旧,最终忘记哪个具有更高的优先级(就像我使用*和 - &gt;)。

答案 6 :(得分:3)

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

//prefix increment (++x)
auto operator++()
{
    (*this) = (*this) + 1;
    return (*this);
}

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

(请注意,后缀增量中有一个副本,使其效率较低。这就是为什么你应该在循环中使用++ i而不是i ++的原因,即使大多数编译器会自动为你做这些天。)

正如您所看到的,首先处理后缀增量,但是,由于它的行为方式,您将取消引用指针的先前值。

以下是一个例子:

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将被解除引用,并且它指向的('a')将递增(到'b')。由于后缀增量返回旧值,y仍将使用'a'初始化。由于指针没有改变,z将用新值'b'初始化。

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

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”。在 strcpy 函数中(在其他答案中提到),增量也是先完成的:

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 :(得分:1)

'++'运算符优先于'*'运算符,这意味着指针地址在被解除引用之前会递增。

然而,'+'运算符的优先级低于'*'。

答案 8 :(得分:1)

我会尝试从一个不同的角度回答这个问题...... 步骤1 让我们看一下运算符和操作数: 在这种情况下,它是操作数,并且您有两个运算符,在这种情况下*用于解除引用,++用于增量。 第2步 它具有更高的优先权 ++优先于* 第3步 ++是哪里,右边是 POST 增量 在这种情况下,编译器会记录一个&#39;心理记录&#39;执行增量 AFTER 它与所有其他运营商完成... 请注意,如果它是* ++ p那么它将在之前完成 所以在这种情况下,它等同于取两个处理器的寄存器,一个将保持解除引用的* p的值,另一个将保持递增的p ++的值,在这种情况下有的原因是二,是POST活动...... 在这种情况下,这是棘手的,它看起来像一个矛盾。 人们会期望++优先于*,它只会使POST意味着它将仅在所有其他操作数之后应用,在下一个操作数之前;&#39;令牌...

答案 9 :(得分:1)

    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)++;

这将仅将地址增加1个字节;)

答案 10 :(得分:0)

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

答案 11 :(得分:0)

图片大约值一千个字(给定或花费一百万左右)...符号可以是图片(反之亦然)。

因此,对于那些寻求tl;dr (优化的数据消耗)但仍然希望“(主要)无损”编码的人来说,矢量图像/图片/插图/演示非常重要。

换句话说,只需忽略我的最后2条语句,然后参阅下文。

Valid forms:


*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 'value' of `a` is unchanged)
≡ val=*a,++*a,val

Notes

/* Indirection/deference operator must pr̲e̲cede the target identifier: */
a++*;
a*++;
++a*;

答案 12 :(得分:-1)

因为指针是按值传入的,所以本地副本会增加。如果你真的想要增加指针,你必须通过引用传递它,如下所示:

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