【发布时间】:2017-10-25 21:25:05
【问题描述】:
表达式 1:*p++; 其中p 是一个指向整数的指针。
p 将首先递增,然后由于关联性(从右到左)而采用它指向的值。对吗?
表达式 2:a=*p++; 其中p 是一个指向整数的指针。
首先获取p 的值,然后首先分配给a,然后p 由于后递增而递增。对吗?
【问题讨论】:
标签: c pointers operator-precedence post-increment
表达式 1:*p++; 其中p 是一个指向整数的指针。
p 将首先递增,然后由于关联性(从右到左)而采用它指向的值。对吗?
表达式 2:a=*p++; 其中p 是一个指向整数的指针。
首先获取p 的值,然后首先分配给a,然后p 由于后递增而递增。对吗?
【问题讨论】:
标签: c pointers operator-precedence post-increment
首先,让我告诉你,没有关联性也没有评估顺序实际上是相关的这里。这都是关于operator precedence。让我们先看看定义。 (强调我的)
Precedence :在数学和计算机编程中,运算顺序(或运算符优先级)是反映关于 首先执行哪些程序来评估给定的数学表达式。
Associativity:在编程语言中,运算符的关联性(或固定性)是一种属性,它决定了相同的运算符如何优先级在没有括号的情况下分组。
Order of evaluation:任何 C 运算符的操作数的求值顺序,包括函数调用表达式中函数参数的求值顺序,并且任何表达式中子表达式的求值顺序是未指定的,除了少数情况。主要有两种类型的评估:a)价值计算 b)副作用。
后自增的优先级较高,所以会先求值。
现在,值增量恰好是在“值计算”之后排序的操作的副作用。因此,值计算结果将是操作数 p 的未更改值(这里再次由于使用 * 运算符而被取消引用),然后发生增量。
引用C11,第 §6.5.2.4 章,
后缀
++运算符的结果是操作数的值。作为一个副作用, 操作数对象的值递增(即相应类型的值 1 为 添加到它)。参见关于加法运算符和复合赋值的讨论 有关约束、类型和转换以及操作对 指针。 结果的值计算在副作用之前排序 更新操作数的存储值。 [.....]
两种情况的求值顺序相同,唯一不同的是,第一种情况下,最终的值被丢弃。
如果您“按原样”使用第一个表达式,您的编译器应该会生成有关未使用值的警告。
【讨论】:
后缀运算符的优先级高于一元运算符。
所以这个表达式
*p++
等价于表达式
*( p++ )
根据 C 标准(6.5.2.4 后缀递增和递减运算符)
2 后缀++运算符的结果是 操作数。作为副作用,操作数对象的值是 递增(即,将适当类型的值 1 添加到 它)。查看加法运算符和复合赋值的讨论 有关约束、类型和转换以及效果的信息 对指针的操作。结果的值计算为 在更新存储值的副作用之前排序 操作数。
所以p++ 产生指针p 的原始值作为操作的结果,并且还具有递增操作数本身的副作用。
至于一元运算符 then(6.5.3.2 地址和间接运算符)
4 一元 * 运算符表示间接。如果操作数指向一个 函数,结果是一个函数指示符;如果它指向一个 对象,结果是一个指定对象的左值。如果操作数 类型为“类型指针”,结果类型为“类型”。如果 无效的值已分配给指针,的行为 一元 * 运算符未定义
所以表达式的最终结果
*( p++ )
是指针p 指向的对象的值,由于副作用,该值也会增加。这个值被赋值给语句中的变量a
a=*p++;
例如如果有以下声明
char s[] = "Hello";
char *p = s;
char a;
然后在这句话之后
a = *p++;
对象a 将具有字符'H',而指针p 将指向数组s 的第二个字符,即字符'e'。
【讨论】:
关联性在此无关紧要。仅当您有具有相同优先级的相邻运算符时,关联性才重要。但在这种情况下,++ 的优先级高于*,因此只有优先级很重要。由于优先级,表达式等价于:
*(p++)
由于它使用后增量,p++ 会增加指针,但表达式返回指针的值之前它被增加。然后间接使用该原始指针来获取值。它实际上相当于:
int *temp = p;
p = p + 1;
*temp;
第二个表达式是一样的,只是它把值赋给另一个变量,所以最后一条语句变成:
a = *temp;
【讨论】:
表达式
*p++
等价于
*(p++)
这是由于优先级(即:后缀自增运算符的优先级高于间接运算符)
和表达式
a=*p++
出于同样的原因,等同于
a=*(p++)
在这两种情况下,表达式 p++ 的计算结果为 p。
【讨论】:
v = i++;:i返回到相等运算,然后赋值给v。随后,i 递增(编辑:从技术上讲,它不一定按此顺序执行)。因此v 具有i 的旧值。我记得是这样的:++ 最后写,因此最后发生。v = ++i;:i递增,然后返回分配给v。 v 和 i 具有相同的值。for(int i=0; i<n; i++) 与 for(int i=0; i<n; ++i) 相同。后者有时会自动成为首选,因为它往往对某些对象更快。* 的优先级低于++,因此*p++ 与*(p++) 相同。因此,在这种情况下,p 被返回给 *,后者取消了对它的引用。然后p 中的地址增加一个元素。 *++p 首先增加 p 的地址,然后取消引用它。v = (*p)++; 设置 v 等于 p 指向的旧值然后递增它,而 v = ++(*p); 递增 p 指向的值然后设置 v 等于它。 p中的地址不变。例子:如果,
int a[] = {1,2};
然后
int v = *a++;
和
int v = *++a;
两者都会使 a 递增,但在第一种情况下,v 将为 1,在后者中为 2。
【讨论】:
*p++;其中p是一个指向整数的指针。
p将首先递增,然后由于关联性(从右到左)而采用它指向的值。对吗?
没有。在后增量中,该值被复制到一个临时值(一个右值),然后左值作为副作用增加。
a=*p++;其中p是一个指向整数的指针。首先取
p的值,然后首先分配给a,然后p由于后递增而递增。对吗?
不,这也不正确。 p 的增量可能发生在写入a 之前。重要的是存储在a 中的值是使用p 先前值的临时副本加载的。
未指定内存读取是否发生在新值p 的内存写入之前,并且任何依赖该顺序的代码都是未定义的行为。
这些序列中的任何一个都是允许的:
p 复制到临时THEN 增量p,然后在临时地址指示的地址处加载值,然后将加载的值存储到a
p 复制到临时THEN 加载值在临时指定的地址(此值本身被放置在临时) THEN 递增p THEN 将加载的值存储到a
p复制到临时THEN加载值在临时THEN存储加载值到a地址处的临时THEN加载值THEN增量p
以下是两个未定义行为的代码示例,因为它们依赖于副作用的顺序:
int a = 7;
int *p = &a;
a = (*p)++; // undefined behavior, do not do this!!
void *pv;
pv = &pv;
void *pv2;
pv2 = *(pv++); // undefined behavior, do not do this!!!
括号不创建序列点(或sequenced before关系,在新的措辞中)。带括号的代码版本与不带括号的代码版本一样未定义。
【讨论】: