尝试理解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
明显不同?
答案 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++;
它的工作原理如下:
our_var_ptr
(包含63)指示的内存位置。our_var_ptr
。它正在改变指针所指向的位置,而不是它所指向的位置。实际上与此相同:
*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
有四点需要注意:
add_one_v2
中指向的值不递增,且以下值都不是,但指针是add_one_v2
中指针的增量在取消引用后发生 ++
比*
更紧密地绑定(作为取消引用或乘法),所以add_one_v2
中的增量适用于指针,而不是它指向的位置。答案 4 :(得分:6)
正如其他人所指出的那样,运算符优先级会导致v2函数中的表达式被视为*(our_var_ptr++)
。
但是,由于这是一个后增量运算符,所以说它增加指针然后解引它就不完全正确。如果这是真的,我认为你不会得到63作为你的输出,因为它将返回下一个内存位置的值。实际上,我认为操作的逻辑顺序是:
正如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,valNotes
/* Indirection/deference operator must pr̲e̲cede the target identifier: */
❌ a++*; ❌
❌ a*++; ❌
❌ ++a*; ❌
答案 12 :(得分:-1)
因为指针是按值传入的,所以本地副本会增加。如果你真的想要增加指针,你必须通过引用传递它,如下所示:
void inc_value_and_ptr(int **ptr)
{
(**ptr)++;
(*ptr)++;
}