表达式的定义行为

时间:2012-01-29 19:30:20

标签: c undefined-behavior standards-compliance language-lawyer

C99标准以6.5.2美元计算。

  

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

(我强调)

接下来请注意,以下示例有效(最初看起来很明显)

a[i] = i;

虽然它没有明确说明ai是什么。

虽然我不相信,但我想知道这个例子是否包含以下案例:

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

这将更改i的值,但访问i的值以确定放置值的地址。或者,我们将i的值分配给已存储在i中的值是否无关紧要?请详细说明。


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

2 个答案:

答案 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的值。