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

Chr*_*heD 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         */
}
Run Code Online (Sandbox Code Playgroud)

输出:

64
@ 0xbf84c6b0

63
@ 0xbf84c6b0
Run Code Online (Sandbox Code Playgroud)

*our_var_ptr++第二个函数(add_one_v2)中的语句究竟是做什么的,因为它显然不一样*our_var_ptr = *our_var_ptr +1

Mar*_*som 62

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

while (*dst++ = *src++) ;
Run Code Online (Sandbox Code Playgroud)

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

PS您始终可以使用括号覆盖操作员顺序.以下将增加指向的值,而不是指针本身:

(*our_var_ptr)++;
Run Code Online (Sandbox Code Playgroud)

  • 这个例子,以及相当多的其他"看马,没有手!"代码片段,出现在C知识的堡垒中,[_ C Programming Language_](https://en.wikipedia.org/wiki/The_C_Programming_Language). (4认同)

hbw*_*hbw 41

由于运算符优先级规则和++作为后缀运算符的事实,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.
}
Run Code Online (Sandbox Code Playgroud)

  • 关于操作的顺序并非如此.在add_one_v2中,++应用于指针,而不是解除引用.但是,由于它是后增量,因此取消引用在增量之前发生. (6认同)
  • 指针本身是一个本地副本,但它指向的值不是. (2认同)

BIB*_*IBD 35

好,

*our_var_ptr++;
Run Code Online (Sandbox Code Playgroud)

它的工作原理如下:

  1. 取消引用首先发生,给你指示的内存位置our_var_ptr(包含63).
  2. 然后评估表达式,63的结果仍然是63.
  3. 结果被扔掉了(你没有做任何事情).
  4. our_var_ptr然后在评估之后递增.它正在改变指针所指向的位置,而不是它所指向的位置.

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

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 
Run Code Online (Sandbox Code Playgroud)

合理?Mark Ransom的答案有一个很好的例子,除了他实际上使用了结果.


dmc*_*kee 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)); 
}
Run Code Online (Sandbox Code Playgroud)

结果输出:

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
Run Code Online (Sandbox Code Playgroud)

有四点需要注意:

  1. 对指针的本地副本的更改不会反映在调用指针中.
  2. 对本地指针的目标的更改会影响调用指针的目标(至少在更新目标指针之前)
  3. 的值指出,在add_one_v2递增,并且既不是下面的值,但指针是
  4. 指针的增量在取消引用add_one_v2发生

为什么?

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


Dav*_*sta 6

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

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

  1. 保存指针的当前值
  2. 增加指针
  3. 取消引用步骤1中保存的指针值

正如htw所解释的那样,你没有看到指针值的变化,因为它是通过值传递给函数的.


Jan*_*ngo 5

如果不使用括号来指定运算顺序,则前缀和后缀增量都优先于引用和取消引用。然而,前缀增量和后缀增量是不同的操作。在 ++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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

这是一个例子:

char * x = {'a', 'c'};
char   y = *x++; //same as *(x++);
char   z = *x;
Run Code Online (Sandbox Code Playgroud)

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

char * x = {'a', 'c'};
char   y = (*x)++;
char   z = *x;
Run Code Online (Sandbox Code Playgroud)

在这里,x 将被取消引用,并且它指向的('a')将增加(到'b')。由于后缀增量返回旧值,因此 y 仍将用“a”初始化。由于指针没有改变,z 将使用新值“b”进行初始化。

现在让我们检查一下前缀大小写:

char * x = {'a', 'c'};
char   y = *++x; //same as *(++x)
char   z = *x;
Run Code Online (Sandbox Code Playgroud)

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

char * x = {'a', 'c'};
char   y = ++*x; //same as ++(*x)
char   z = *x;
Run Code Online (Sandbox Code Playgroud)

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

char * strcpy(char * dst, char * src)
{
    char * aux = dst;
    while(*dst++ = *src++);
    return aux;
}
Run Code Online (Sandbox Code Playgroud)

在每次迭代中,首先处理 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;
Run Code Online (Sandbox Code Playgroud)