如何测试序列点的未定义行为规则?

时间:2014-03-25 05:40:27

标签: c undefined-behavior

我从2天开始阅读有关UB的内容,并且在我的脑海中对于以下示例感到困惑

int a=5;
a++ & printf("%d",a);//i know that `&` introduced here is not a sequence point.

现在,它是U中的c,但我正在从另一个天使看下面

现在c标准说

Between the previous and next sequence point an object shall have its stored value modified at most once by the evaluation of an expression.

因此,由于我们仅通过编写a++一次来修改a的值,因此该规则无法将行为声明为UB。
但进一步说明了

Furthermore, the prior value shall be accessed only to determine the value to be stored.

由于a(此处为5)的先前值是通过a++访问,以在a

语句中写出printf("%d",a) .But的值 在表达式运行之前,

("%d",a)是一个完整的表达式。all the dust of side effect should be settled down 按照标准

when a function call is made there is sequence point before the actual call and after the evaluation of arguments.

所以a应该在函数调用中进行修改,更准确地说,a的值会根据标准更新。
那么为什么它仍然是UB ("%d",a)本身就是一个表达式,它在;之前被评估了?

4 个答案:

答案 0 :(得分:2)

("%d",a)不是完整表达式。在这种情况下,它甚至不是表达。它是函数的参数列表。代码中的完整表达式是第二行中的所有内容,;除外。

  

完整表达式是不属于另一个表达式或声明符的表达式。

a在参数列表中的访问在它与a++之间没有插入序列点,并且它不是为了执行a++,所以行为未定义。

答案 1 :(得分:1)

So,as we are modifying the value of a by writing一个++ only once,this rule fails in declaring the behavior as UB.

此处a++ & printf("%d",a); - 调用时输入;之前的唯一序列点为printf

您可以清楚地看到a正在修改并在单个序列点;内同时读取。因此重点说明:

序列点是尘埃落定的时间点,到目前为止所见的所有副作用都保证完整。 C标准中列出的序列点是:

  

在完整表达式的评估结束时(完整的   expression是一个表达式语句,或任何其他表达式   不是任何更大表达式中的子表达式);       在||,&&,?:和逗号运算符;和       在函数调用(在评估所有参数之后,以及在实际调用之前)。

标准规定

  

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

答案 2 :(得分:1)

你困惑的是副作用是什么。根据:

printf("%d",a)a++都有副作用
  

C11 5.1.2.3程序执行

     

访问易失性对象,修改对象,修改文件或调用函数   任何这些操作都是副作用,12)是状态的变化   执行环境。

printf("%d",a)正在修改执行环境的状态。没有副作用的函数是除了返回仅由其参数确定的值之外什么都不做的函数。

答案 3 :(得分:1)

表达式a++ & printf("%d",a)包含五个有趣的步骤:

  • A)阅读
  • B)增加一个
  • C)阅读
  • D)致电printf
  • E)bitwise-and

您可能希望C ++从上到下执行这些步骤(A-> B-> C-> D-> E),因为人们倾向于从左到右阅读。但实际上,C ++只能为您提供以下保证:

  • A在B(A-> B)之前执行
  • C在D(C-> D)之前执行,因为函数需要知道它们的参数
  • A和D在E之前执行(A-> E,D-> E),因为运营商需要知道他们的操作数
  • 如果在D(A-> D)之前执行A,则在D(B-> D)之前也执行B,因为a++具有副作用,函数调用是序列点,并且到那时,保证可以看到副作用。

注意,不保证形式(A-> C)或(B-> C)。这是大多数初学者都不会得到的。

C是否读取B发生的增量之前或之后的值?我们不知道!

  • 运算符&不对其操作数的求值顺序施加任何限制,因此可以在第一个操作数printf("%d",a)之前计算第二个操作数a++。 (操作数的评估也可以交错,只要它们遵守上述保证。)
  • 即使如果在第二个操作数之前计算第一个操作数,B执行时发生的副作用也不能保证在C执行时可见。 (然后 会在D处可见,因为函数调用是序列点,但到那时已经太晚了。参数已经被评估了。)

这些限制使我们得到以下可能的执行命令:

  • A-> B-> C-> D-&GT,E
  • A-> C-> B-> D-&GT,E
  • C-> A-> B-> D-&GT,E
  • C-> D-> A-> B-&GT,E
  • C-> A-> B-> D-&GT,E

我花了好几次尝试写下这个列表。我并非100%确定它是正确和完整的。