我一直在愚弄一些代码,看到一些我不理解“为什么”的东西。
int i = 6;
int j;
int *ptr = &i;
int *ptr1 = &j
j = i++;
//now j == 6 and i == 7. Straightforward.
如果将操作符放在等号的左侧怎么办?
++ptr = ptr1;
相当于
(ptr = ptr + 1) = ptr1;
,而
ptr++ = ptr1;
相当于
ptr = ptr + 1 = ptr1;
后缀运行编译错误,我明白了。你在赋值运算符的左侧有一个常量“ptr + 1”。很公平。
前缀一个编译和WORKS在C ++中。是的,我理解它很乱,你正在处理未分配的内存,但它可以工作和编译。在C中,这不会编译,返回与后缀“左值作为赋值的左操作数所需的左值”相同的错误。无论如何编写,使用两个“=”运算符或“++ ptr”语法进行扩展,都会发生这种情况。
C处理此类赋值的方式与C ++如何处理它有什么区别?
答案 0 :(得分:73)
在C和C ++中,x++
的结果都是右值,因此您无法分配它。
在C中,++x
相当于x += 1
(C标准§6.5.3.1/ p2;所有C标准引用均为WG14 N1570)。在C ++中,如果++x
不是x += 1
,则x
等同于bool
(C ++标准§5.3.2[expr.pre.incr] / p1;所有C ++标准引用的是WG21 N3936)。
在C中,赋值表达式的结果是rvalue(C标准§6.5.16/ p3):
赋值运算符将值存储在由指定的对象中 左操作数。赋值表达式的值为left 赋值后的操作数,但不是左值。
因为它不是左值,你不能分配它:( C标准§6.5.16/ p2 - 注意这是一个约束)
赋值运算符左边应有一个可修改的左值 操作数。
在C ++中,赋值表达式的结果是左值(C ++标准§5.17[expr.ass] / p1):
赋值运算符(=)和复合赋值运算符all 小组从右到左。所有都需要左侧可修改的左值 操作数并返回一个引用左操作数的左值。
因此++ptr = ptr1;
是C中的可诊断约束违规,但不违反C ++中的任何可诊断规则。
但是,在C ++ 11之前,++ptr = ptr1;
具有未定义的行为,因为它在两个相邻序列点之间修改ptr
两次。
在C ++ 11中,++ptr = ptr1
的行为变得很明确。如果我们将其重写为
(ptr += 1) = ptr1;
从C ++ 11开始,C ++标准提供了(§5.17[expr.ass] / p1)
在所有情况下,在计算值之后对赋值进行排序 左右操作数,以及值的计算之前 赋值表达式。关于 不确定顺序的函数调用,化合物的操作 任务是一次评估。
因此,在=
和ptr += 1
的值计算之后,ptr1
执行的分配将被排序。由+=
执行的赋值在ptr += 1
的值计算之前排序,并且+=
所需的所有值计算必须在该赋值之前排序。因此,这里的排序是明确定义的,并且没有未定义的行为。
答案 1 :(得分:17)
在C中,前后增量的结果是rvalues,我们无法分配到 rvalue ,我们需要左值( also see: Understanding lvalues and rvalues in C and C++ )。我们可以通过转到draft C11 standard部分6.5.2.4
Postfix递增和递减运算符来看到(强调我的前进):
后缀++运算符的结果是的值 操作数。 [...]参见添加剂操作者和化合物的讨论 分配有关约束,类型和转换的信息 操作对指针的影响。 [...]
因此,后递增的结果是值,它与 rvalue 同义,我们可以通过转到6.5.16
部分来确认操作符以上段落指示我们进一步理解约束和结果,它说:
[...]赋值表达式具有左后操作数的值 分配,但不是左值。[...]
进一步确认后增量的结果不是左值。
对于预增量,我们可以从6.5.3.1
前缀增量和减量运算符中看到:
[...]参见附加算子和复合赋值的讨论 有关约束,类型,副作用和转换的信息 操作对指针的影响。
也指向6.5.16
,就像后增量一样,因此C中的预增量结果也不是左值。
在C ++中,后递增也是 rvalue ,更具体地说是 prvalue 我们可以通过转到5.2.6
部分来确认这一点。递增和递减说:
[...] 结果是prvalue。结果的类型是cv-unqualified 操作数类型的版本[...]
关于预增量C和C ++的不同。在C中,结果是 rvalue ,而在C ++中,结果是 lvalue ,这解释了为什么++ptr = ptr1;
在C ++中工作而不是C。
对于C ++,这将在5.3.2
增量和减量部分中介绍:
[...]结果是更新的操作数; 这是一个左值,它是一个 如果操作数是位字段,则为位字段。[...]
了解是否:
++ptr = ptr1;
在C ++中是否定义良好我们需要两种不同的方法,一种用于预C ++ 11,另一种用于C ++ 11。
Pre C ++ 11此表达式调用undefined behavior,因为它在同一序列点中多次修改对象。我们可以通过转到Pre C ++ 11草案标准部分5
表达式来看到这一点,其中说:
除非另有说明,否则评估个人操作数的顺序 单个表达式的运算符和子表达式以及顺序 在哪些副作用发生,是未指明的.57)之间 上一个和下一个序列点标量对象应该存储它 通过表达式的评估最多修改一次值。 此外,只能访问先前值以确定 要存储的值。应满足本款的要求 对于每个允许的完整子表达式的排序 表达;否则行为未定义。 [例如:
i = v[i ++]; / / the behavior is undefined i = 7 , i++ , i ++; / / i becomes 9 i = ++ i + 1; / / the behavior is undefined i = i + 1; / / the value of i is incremented
-end example]
我们正在递增ptr
然后再分配给它,这是两次修改,在这种情况下,序列点出现在;
之后的表达式的末尾。
对于C + 11,我们应该转到defect report 637: Sequencing rules and example disagree ,这是导致以下内容的缺陷报告:
i = ++i + 1;
在C ++ 11中成为明确定义的行为,而在C ++ 11之前,这是undefined behavior。本报告中的解释是我见过的最好的解释之一,多次读它是有启发性的,并帮助我以新的眼光理解了许多概念。
导致此表达式变为明确定义的行为的逻辑如下:
在LHS和RHS的值计算(5.17 [expr.ass]第1段)之后,需要对分配副作用进行排序。
LHS(i)是左值,因此其值计算涉及计算i的地址。
为了计算RHS(++ i + 1)的值,有必要首先对左值表达式++ i进行值计算,然后对结果进行左值到右值的转换。这保证了在计算加法运算之前对递增副作用进行排序,加法运算又在赋值副作用之前进行排序。换句话说,它为此表达式生成一个明确定义的顺序和最终值。
逻辑有点相似:
++ptr = ptr1;
在分配副作用之前对LHS和RHS的值计算进行排序。
RHS是左值,因此其值计算涉及计算ptr1的地址。
为了计算LHS(++ ptr)的值,有必要首先对左值表达式++ ptr进行值计算,然后对结果进行左值到右值的转换。这保证了在赋值副作用之前对递增副作用进行排序。换句话说,它为此表达式生成一个明确定义的顺序和最终值。
注意
OP说:是的,我理解它很乱,你处理的是未分配的 记忆,但它可以工作和编译。
非数组对象的指针被认为是加法运算符的大小为1的数组,我将引用C ++标准草案,但C11几乎完全相同的文本。来自5.7
添加剂运算符:
出于这些运算符的目的,指向非阵列对象的指针 行为与指向数组的第一个元素的指针相同 长度为1,对象的类型为元素类型。
并进一步告诉我们,只要你没有取消引用指针,指向一个数组末尾的结果是有效的:
[...]如果指针操作数和结果都指向元素 相同的数组对象,或超过数组的最后一个元素 对象,评估不得产生溢出;否则, 行为未定义。
这样:
++ptr ;
仍然是一个有效的指针。