C语言中的指针操作和运算符优先级

18

背景

今天我和一个C语言专家聊天,我们对以下问题持不同意见:

int intgA[2] = { 1, 2 };
int intgB[2] = { 3, 5 };

int *intAPtr = intgA;
int *intBPtr = intgB;

因此,当我们执行以下操作时:

*intAPtr++ = *intBPtr++;

我的分析

首先:

intBPtr增加了一,现在指向地址为5的位置。然后对其进行解引用,得到值5。

intAPtr也增加了一,现在指向地址为2的位置。接着对其进行解引用,得到值2。

最后:

2被5替换。

因此它们分别是:5和5。

他的分析

*intBPtr的值首先被赋给*intAPtr

因此,它们变为:3和3。

然后,*intAPtr*intBPtr都增加了一个单位。

所以,它们分别变为:4和4。

我的假设

我认为++运算符优先级高于 *=,因此得出了我的结论。

例如,如果我们有:

*intAPtr++; 

结果应该是2,对吧?因为我们先递增了指针,然后再解引用。

那么为什么在上述情况下,正如他所声称的那样,我们首先将intBPtr的值赋给intAPtr的值,并最后递增值呢?

在采纳了这里所有的建议之后,我在IDE中运行了代码,结果证实了@sujin的说法:

虽然它至少在优先级上证实了我的正确性:

即:*intAPtr++ = *intBPtr++;

intAPtr++具有较高的优先级,这导致intAPtr将其地址递增1。

现在指向:地址2。

同样地:

intBPtr++ 也递增了1(地址)。

现在指向:地址5。

然后轮到*

因此,两者都被解引用(*),分别为2和5。

但是问题仍然存在,因为上面的赋值(=)似乎没有发生。

如果这样做了,两个都将变为5。

期待进一步的启示。


"intAPtr++" 是后增量运算符。高度可能的是,在赋值之后,增量操作会最后执行。您尝试将此代码放入IDE并运行它,看看它的作用了吗? - Robert Harvey
1
指针的增量操作与赋值操作之间的时间先后关系并不相关。后置增量运算符的返回值是一个新的临时对象,与被增量的指针是分离的。 - Cubbi
@Cubbi:只需运行代码,就可以在5分钟内验证其行为。 - Robert Harvey
6个回答

9
该语句
*intAPtr++ = *intBPtr++;

被解析为

*(intAPtr++) = *(intBPtr++);

并且可以分解如下:

  • intBPtr指向的当前值(3)分配给intAPtr指向的位置(intgA [0]);
  • 指针intAPtrintBPtr被递增。

这些事情发生的确切顺序是未指定的; 您不能指望在intAPtr或反之亦然之后递增intBPtr,也不能指望赋值在递增之前发生,等等。

因此,当所有这些都完成时,intgA [0] == 3intAPtr == &intgA [1]intBPtr == &intgB [1]

表达式a ++计算为增量之前a的值。


2
+1 表示“这些事情发生的确切顺序是未指定的”。 - haccks

5

你和另一个人都错了!

要么,
1. 两个指针先增加,然后进行赋值或
2. 其中一个指针增加,然后进行赋值,然后另一个指针增加或
3. 首先进行赋值,然后指针增加。

但规则是,一个语句的所有副作用必须在下一条语句开始之前完成。请记住必须使用原始值。只要使用原始值,增量可以在任何时候发生。

C-faq: 3.2

不能保证在放弃先前值并在评估表达式的任何其他部分之前立即执行增量或减量。 仅保证更新将在表达式被视为“完成”之前的某个时间执行

阅读Eric Lippert提供的答案,以获得详细解释。


1
谢谢你的提醒。我注意到你在第二个语句中漏掉了一个大小写字母。"...或者一个指针增加,然后进行赋值,接着另一个指针增加"。 - Eric Lippert
@EricLippert;感谢您指出。已将该情况添加到答案中。 :) - haccks

4
是的,++的优先级比*高,但是你误解了它的工作方式。 var ++会增加var的值,但它的计算结果是在增加之前的var值。如果你愿意,可以将其视为 (var += 1, var-1)的语法糖。
换句话说,假如按照你想象的方式运行,那么var ++++ var之间就没有区别了,其中一个根本不会被包含在语言中。C几乎没有冗余。
(绑定优先级确实很重要;例如,这意味着* var ++递增变量var的值,而(* var) ++递增内存地址* var中的值。)

1
同事的说法也不正确,因为 ++ 与指针有关,而不是他所声称的值... - glglgl
@glglgl 哦,我看错了问题。我想我会删除那个语句,而不是试图更详细地解释。 - zwol
@haccks 不是所有的 var 都必须是内存位置(它可能只存在于寄存器中),但 *var 绝对是一个内存位置。我看到我写的可能不够清晰。 - zwol
var 被声明为一个指针。在 *var++ 的情况下,我认为 var++ 先执行(优先级),它会增加内存位置,然后 * 执行,将其解除引用到其值。如果 var 被声明为一个变量,var++ 当然意味着增加 var 的值,正如你所说的那样,但这在这里不是这种情况,因为 var 被声明为一个指针。 - Unheilig
@Unheilig 不,你还在坚持你最初的误解。*var++ 中的 ++ 递增 var(而不是内存位置 *var),这是由于优先级决定的,但它求值为在增量发生之前的 var 的值,这意味着整个表达式的值是由 var 最初指向的内存位置的值。再看看我关于语法糖的解释。 - zwol
@Unheilig 同时,“var”作为指针并不意味着“var”不是变量。在您的原始代码中,“intAPtr”和“intBPtr”绝对都是变量和指针。 - zwol

3
++运算符的主要作用是产生其操作数的值(未进行递增操作)。副作用是递增操作数。

即使在主效果产生的值被用于另一个操作之前执行副作用时,主效果产生的值仍然是操作数的原始值,而不是更新后的值。

*x++中,该表达式被解析为*(x++)*操作符应用于主效果产生的值,因此*操作的结果与*x的结果相同。递增操作发生在主表达式求值之外,因此它不参与确定*x++的值。


2

示例代码(在Linux中):

#include <stdio.h>
#include <stdlib.h>
int main() {
    int intgA[2] = { 1, 2 };
    int intgB[2] = { 3, 5 };
    int *intAPtr = intgA; //1
    int *intBPtr = intgB; //3
    *intAPtr++ = *intBPtr++;
    // *intAPtr = *intBPtr;
    // *intAPtr++ = 25; 
    printf("op: %d %d\n", *intAPtr, *intBPtr);

   return 0;
}

输出:

op: 2 5

首先将intBPtr赋值给intAPtr,然后进行递增,因为这是后缀递增。


1
首先,它将intBPtr分配给intAPtr,然后进行递增。你怎么能这么说呢? - haccks

1

我同意John Bode的答案,为了简化一下,我将直接写成代码:

   *intAptr = * intBPtr;

等同于:

   *intAptr =*intBPtr 
   intAPtr+=1;
   inrBPtr+=1;

所以我们得到的是: intAPtr 指向 2 intBPtr 指向 5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接