C99标准以6.5.2美元计算。
在上一个和下一个序列点之间,对象应具有其存储值 通过表达式的评估最多修改一次。此外,先前的值 应该只读以确定要存储的值。
(我强调)
接下来请注意,以下示例有效(最初看起来很明显)
a[i] = i;
虽然它没有明确说明a
和i
是什么。
虽然我不相信,但我想知道这个例子是否包含以下案例:
int i = 0, *a = &i;
a[i] = i;
这将不更改i
的值,但访问i
的值以确定放置值的地址。或者,我们将i
的值分配给已存储在i
中的值是否无关紧要?请详细说明。
奖金问题;那么a[i]++
或a[i] = 1
呢?
答案 0 :(得分:15)
第一句话:
在前一个和下一个序列点之间,一个对象应该具有它 通过表达式的评估,最多修改一次存储值。
很清楚。该语言不对子表达式施加评估顺序,除非它们之间存在序列点,而不是要求某些未指定的评估顺序,它表示修改对象两次会产生未定义的行为。这允许积极优化,同时仍然可以编写遵循规则的代码。
下一句话:
此外,先前的值应该只读以确定要存储的值
在第一眼(和第二眼)看起来似乎不直观;为什么读取值的目的是否会影响表达式是否已定义行为?
但它反映的是,如果子表达式B取决于子表达式A的结果,则必须在 B可以评估之前评估。 C90和C99标准没有明确说明这一点。
脚注中的一个例子更明确地违反了这句话:
a[i++] = i; /* undefined behavior */
假设a
是声明的数组对象而i
是声明的整数对象(没有指针或宏技巧),则不会多次修改任何对象,因此它不会违反第一个句子。但是对LHS的i++
的评估确定要修改哪个对象,并且对RHS的i
的评估确定要存储在该对象中的值 - 以及读取的相对顺序RHS上的操作和LHS上的写操作没有定义。同样,该语言可能需要以某种未指定的顺序评估子表达式,而是将整个行为保留为未定义,以允许更积极的优化。
在你的例子中:
int i = 0, *a = &i;
a[i] = i; /* undefined behavior (I think) */
读取i
的先前值以确定要存储的值和以确定要存储的对象。由于a[i]
引用{ {1}}(但仅限于i
),修改i==0
的值会更改左值i
所引用的对象。在这种情况下,a[i]
中存储的值与已存储在那里的值(i
)相同,但标准不会对碰巧存储的值存储异常相同的价值。我认为这种行为是不确定的。 (当然,标准中的示例并不打算涵盖这种情况;它隐含地假设0
是与a
无关的声明数组对象。)
至于标准所说的例子是允许的:
i
一个可以解释标准,说它是未定义的。但我认为第二句,指的是“先前值”,仅适用于由表达式修改的对象的值。 int a[10], i = 0; /* implicit, not stated in standard */
a[i] = i;
永远不会被表达式修改,所以没有冲突。 i
的值既用于确定要通过赋值修改的对象,也用于存储在那里的值,但这没关系,因为i
本身的值永远不会改变。 i
的值不是“先前值”,它只是值。
C11标准有一种新的模型用于这种表达式评估 - 或者更确切地说,它用不同的词语表达相同的模型。它不是“序列点”,而是讨论在彼此之前或之后进行排序的副作用,或相对于彼此不顺序的副作用。它明确表示如果子表达式B取决于子表达式A的结果,则必须在 B B之前评估。
在the N1570 draft中,第6.5节说:
1 表达式是一系列运算符和操作数 指定值的计算,或指定对象 或功能,或产生副作用,或执行 它们的组合。操作数的值计算 在计算值的计算之前对运算符进行排序 经营者的结果。
2如果相对于标量对象的副作用未被排序 要么对同一个标量对象产生不同的副作用,要么对a 使用相同标量对象的值进行值计算 行为未定义。如果有多个允许的排序 在表达式的子表达式中,行为是未定义的 如果在任何排序中出现这种无序的副作用。
3运算符和操作数的分组由语法指示。 除非后面指出,否则副作用和值计算 子表达式没有被排除。
答案 1 :(得分:2)
读取对象的值以确定 存储它的位置不计算为“确定要存储的值”。这意味着唯一的争论点可能是我们是否正在“修改”对象i
:如果我们是,则它是未定义的;如果我们不是,那就没关系。
将值0
存储到已包含值0
的对象中是否计为“修改存储值”?按照“修改”的简单英文定义,我不得不说;保持不变是与修改它相反的。
然而,很明显,这将是未定义的行为:
int i = 0, *a = &i;
a[i] = 1;
毫无疑问,除了确定要存储的值(要存储的值是常数)之外,读取存储的值,并且修改i
的值。