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)
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)
BIB*_*IBD 35
好,
*our_var_ptr++;
Run Code Online (Sandbox Code Playgroud)
它的工作原理如下:
our_var_ptr(包含63).our_var_ptr然后在评估之后递增.它正在改变指针所指向的位置,而不是它所指向的位置.它实际上与执行此操作相同:
*our_var_ptr;
our_var_ptr = our_var_ptr + 1;
Run Code Online (Sandbox Code Playgroud)
合理?Mark Ransom的答案有一个很好的例子,除了他实际上使用了结果.
这里多的混乱,所以这里是一个修改的测试程序,使发生什么明显的(或至少明确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)
有四点需要注意:
add_one_v2被不递增,并且既不是下面的值,但指针是add_one_v2发生++绑定比*(取消引用或乘法)更紧密所以增量add_one_v2适用于指针,而不是它指向的指针.正如其他人所指出的那样,运算符优先级会导致v2函数中的表达式被视为*(our_var_ptr++).
但是,由于这是一个后增量运算符,所以说它增加指针然后取消引用它并不完全正确.如果这是真的,我认为你不会得到63作为输出,因为它将返回下一个内存位置的值.实际上,我认为操作的逻辑顺序是:
正如htw所解释的那样,你没有看到指针值的变化,因为它是通过值传递给函数的.
如果不使用括号来指定运算顺序,则前缀和后缀增量都优先于引用和取消引用。然而,前缀增量和后缀增量是不同的操作。在 ++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)