67

试图理解 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

4

13 回答 13

85

这是让 C 和 C++ 如此有趣的小问题之一。如果你想弯曲你的大脑,想出这个:

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

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

PS您总是可以用括号覆盖运算符顺序。以下将增加指向的值,而不是指针本身:

(*our_var_ptr)++;
于 2009-05-13T19:09:12.220 回答
51

由于运算符优先级规则和++作为后缀运算符的事实,add_one_v2()确实取消了对指针的引用,但++实际上是应用于指针本身。但是,请记住 C 始终使用按值传递: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.
}
于 2009-05-13T19:05:39.840 回答
44

好的,

*our_var_ptr++;

它是这样工作的:

  1. our_var_ptr取消引用首先发生,为您提供由(包含 63)指示的内存位置。
  2. 然后对表达式求值,63的结果还是63。
  3. 结果被丢弃(你没有对它做任何事情)。
  4. our_var_ptr然后在评估后递增。它正在改变指针指向的位置,而不是它指向的位置。

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

*our_var_ptr;
our_var_ptr = our_var_ptr + 1; 

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

于 2009-05-13T19:50:30.607 回答
8

这里有很多混乱,所以这是一个修改后的测试程序,以使发生的事情变得清晰(或至少清晰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. in 指向的值add_one_v2递增,后面的值也不递增,但指针是
  4. 指针的增量add_one_v2发生取消引用之后

为什么?

  • 因为++绑定比*(作为取消引用或乘法)更紧密,所以增量add_one_v2适用于指针,而不是它指向的内容。
  • 增量发生对术语的评估之后,因此取消引用获取数组中的第一个值(元素 0)。
于 2009-05-13T20:13:59.727 回答
7

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

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

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

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

于 2009-05-13T19:13:12.480 回答
4

如果您不使用括号来指定操作顺序,则前缀和后缀增量都优先于引用和取消引用。但是,前缀递增和后缀递增是不同的操作。在 ++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++ 的原因,即使现在大多数编译器都会自动为您执行此操作。)

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

这是一个例子:

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;
于 2017-11-04T15:52:51.943 回答
3

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

*our_var_ptr 是取消引用的指针——它指向指针“指向”的位置。

++ 增加一个值。

所以。*our_var_ptr = *our_var_ptr+1取消引用指针并将该位置的值加一。

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

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

我的建议是,在使用取消引用(或其他任何方法)时,请使用括号来明确您的决定。不要试图记住优先规则,因为有一天你只会使用另一种语言,它们会略有不同,你会感到困惑。或者老了,最终忘记了哪个具有更高的优先级(就像我对 * 和 -> 所做的那样)。

于 2009-05-13T19:13:36.190 回答
2

我会尝试从一个不同的角度来回答这个问题... 第 1 步让我们看看运算符和操作数:在这种情况下它是操作数,并且您有两个运算符,在这种情况下 * 用于取消引用和 ++为增量。具有较高优先级的第 2 步 ++ 优先于 * 第 3 步 ++ 在哪里,它在右边,表示POST增量 在这种情况下,编译器会记下“心理笔记”来执行增量AFTER它是由所有其他运算符完成的......注意如果它是 *++p 那么它将在之前完成,所以在这种情况下,它相当于取两个处理器的寄存器,一个将保存取消引用的值*p 和另一个将保存递增的 p++ 的值,在这种情况下有两个的原因是 POST 活动......在这种情况下,这是棘手的地方,看起来很矛盾。人们会期望 ++ 优先于 *,它确实如此,只是 POST 意味着它将仅在所有其他操作数之后应用,在下一个 ';' 之前 令牌...

于 2017-05-02T13:01:52.803 回答
1

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

于 2017-11-02T06:37:45.523 回答
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 个字节;)

于 2018-06-13T12:50:55.020 回答
0

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

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

于 2009-05-13T19:04:09.107 回答
0
于 2019-07-08T01:39:08.450 回答
-1

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

void inc_value_and_ptr(int **ptr)
{
   (**ptr)++;
   (*ptr)++;
}
于 2012-08-17T14:41:55.510 回答