表达式的定义行为

C99标准以6.5.2美元计价。

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。 此外, 先前的值应该是只读的,以确定要存储的值

(我强调)

它继续指出,以下示例是有效的(一开始看似很明显)

a[i] = i; 

虽然它没有明确说明我和i是什么。

虽然我相信它没有,但我想知道这个例子是否涵盖以下情况:

 int i = 0, *a = &i; a[i] = i; 

不会更改i的值,而是访问i的值以确定放置值的地址。 或者我们为i分配一个已经存储在i的值是无关紧要的? 请说清楚。


奖金问题; 那a[i]++a[i] = 1怎么样?

第一句话:

在前一个和下一个序列点之间,对象的存储值最多只能通过表达式的计算修改一次。

很清楚。 除非在它们之间存在序列点,并且不需要一些未指定的评估顺序,否则该语言不对子表达式强加评估顺序,它表示修改对象两次会产生未定义的行为。 这允许积极优化,同时仍然可以编写遵循规则的代码。

下一句话:

此外,先前的值应该是只读的,以确定要存储的值

在第一次(和第二次)一眼看上去确实看起来不直观; 为什么读取值的目的是否会影响表达式是否已定义行为?

但它反映的是,如果子表达式B取决于子表达式A的结果,则必须在评估B 之前评估A. 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]指的是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的值本身永远不会改变。 i的值不是“先前值”,它只是值。

C11标准有一种新的模型用于这种表达式评估 – 或者更确切地说,它用不同的词语表达相同的模型。 它不是“序列点”,而是讨论在彼此之前或之后进行排序的副作用,或相对于彼此不顺序的副作用。 它明确表示如果子表达式B取决于子表达式A的结果,则必须在评估B 之前评估A.

在N1570草案中 ,第6.5节说:

1 表达式是一系列运算符和操作数,用于指定值的计算,或指定对象或函数,或生成副作用或执行其组合的操作和操作数。 在运算符的结果的值计算之前,对运算符的操作数的值计算进行排序。

2如果相对于同一标量对象的不同副作用或使用相同标量对象的值进行值计算,标量对象的副作用未被排序,则行为未定义。 如果表达式的子表达式有多个允许的排序,则如果在任何排序中发生这种未测序的副作用,则行为是不确定的。

3语法指示运算符和操作数的分组。 除非后面指出,否则子表达的副作用和值计算是不确定的。

读取对象的值以确定存储它的位置不算作“确定要存储的值” 。 这意味着唯一的争论点可能是我们是否正在“修改”对象i :如果我们是,那么它是未定义的; 如果我们不是,那就没关系。

将值0存储到已包含值0的对象中是否计为“修改存储的值”? 通过简单的英文定义“修改”,我不得不说; 保持不变是与修改它相反的。

但是,很明显,这将是未定义的行为:

 int i = 0, *a = &i; a[i] = 1; 

这里毫无疑问,除了确定要存储的值(要存储的值是常数)之外,读取存储的值,并且修改i的值。