C中的序列点和副作用
在这个C-FAQ中给出了序列点 ;
标准规定:
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。 此外,只能访问先前值以确定要存储的值。
在例子中
i = i++; a[i] = i++;
从声明的第一句可以清楚地看出,这些例子是未定义行为的结果。
在解释声明的第二句时,据说;
第二句话说: 如果一个对象被写入一个完整的表达式,那么在同一个表达式中对它的任何和所有访问都必须直接参与计算要写入的值。 此规则有效地将法律表达式约束为在修改之前明显存在访问的表达式。 例如,旧备用
i = i + 1
是允许的,因为i的访问用于确定i的最终值。 这个例子
a[i] = i++
是不允许的,因为i的一个访问(a [i]中的一个)与最终存储在i中的值无关(在i ++中发生),因此没有好的方法来定义。
我的问题是;
1.它是什么意思, 如果一个对象被写入一个完整的表达式,那么在同一个表达式中对它的任何和所有访问必须直接参与计算要写入的值。 ?
2.它是什么意思,例子a[i] = i++
是不被允许的,因为i的一个访问(a [i]中的一个)与最终存储在i中的值无关(其中发生在i ++)
有人可以用一些简单的方法解释它吗?
我的问题是; 1.它是什么意思,如果一个对象被写入一个完整的表达式,那么在同一个表达式中对它的任何和所有访问都必须直接参与计算要写入的值。
使用像i++
这样的子表达式, i
会写入。 而且,赋值是一个表达式,所以在i = 2
, i
被写入。 a = b
是表达式可能不是很明显,但确实如此。 这就是为什么你可以做一些事情,比如a = b = c
,这是好的, if (a = b)
哪个不太好。
所以它所说的是,如果你用i
,或者前后增量写入i
,那么对i的任何访问必须作为i的新值的计算的一部分。 然而,这很重要,计算前后增量所涉及的唯一事情是在语句开头的 i
的值。
2.它是什么意思,例子a [i] = i ++是不被允许的,因为i的一个访问(a [i]中的一个)与最终存储在i中的值无关(其中发生在i ++)
正是它所说的。 当你在a[i]
访问i
,它不是i++
新值的计算的一部分。
有人可以用一些简单的方法解释它吗?
简单方法:不要在表达式中使用前置或后置增量。 总是在声明中自己使用它们。 如果你真的必须,请不要在整个语句中的任何其他位置使用相同的变量。
这种解释很奇怪,我无法理解它。
真正的解释是表达式i++
具有副作用(递增i
),可以在评估此特定i
之后的任何时间应用。
由于在序列点之外,C语言不保证评估顺序或应用后递增的时间(可能出于性能原因),在第二个示例中可能会发生三件事(让我们假设在行之前i = 5
) :
- 首先评估最左边的
i
(a[i]
中的a[i]
),以计算用于存储的[i]的地址。 然后评估最右边的i
,然后应用后增量:该行执行a[5] = 5; i=6;
a[5] = 5; i=6;
- 首先评估最右边的
i
,然后评估最左边的i
,然后在完成所有操作时应用后增量:在这种情况下,效果与上述情况相同。 - 首先评估最右边的
i
,立即应用后增量,然后评估最左边的i
以计算用于存储的[i]的地址。 这次效果是a[6] = 5; i=6
a[6] = 5; i=6
。
结果的选择不仅取决于编译器的选择,还取决于编译器的设置:Microsoft Visual C ++可以根据您是在Debug还是Release中编译而给出不同的结果。
最后我得到了关于这一点的解释 。 阅读完之后, 我得出结论 :
最后一句话
此外,只能访问先前值以确定要存储的值
会是这样的;
此外,只能访问对象的先前值以确定要存储的( 同一对象的 ) 修改/新值。
通过这个例子很清楚
int i = 1, j, a[5]; i = i + 1; j = i + 1; a[i] = i;
在表达式i = i + 1
情况下, i
(在RHS中)的先前值(在此为1
)被访问以确定要存储的i
的值,这就是语句
如果一个对象被写入一个完整的表达式,那么在同一个表达式中对它的任何和所有访问都必须直接参与计算要写入的值。
说。
在j = i + 1
和a[i] = i
, a[i] = i
的访问值只是值 而不是 先前值,因为在这些语句中i
被修改。
第二个问题可以解释为;
在表达式a[i] = i++
或a[i++] = i
,上述语句的第一句
在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。
因为i
在两个连续的序列点之间仅被修改一次而 失败 。 这就是为什么我们需要第二句话。
在C中不允许这两个例子,因为i
的先前值被访问了两次,即i++
本身在表达式中访问i++
先前值以修改它,因此i
的先前值/值的其他访问是不必要的,因为它不被访问确定要存储的修改值。