这是 C 语言未定义的行为吗? clang 和 GCC 的不同结果

Is this a C language undefined behaviour? Different results in clang and GCC

对于相同的代码,不同的编译器会得到不同的结果。这是未定义的行为吗?

#include <stdio.h>
int a;
int b=10;
int puan_ekle(int puan, int bonus){
    puan=puan+bonus;
    a=puan-5;
    bonus--;
    return bonus;
}
int main(){
    a=23;
    printf("Result1 %d \n", a);
    a=a+puan_ekle(a,b);
    printf("Result2 %d \n", a);
    a=a+puan_ekle(a,b);
    printf("Result3 %d \n", a);
}

未指定加法运算符的操作数求值顺序。

例如在这个声明中

a=a+puan_ekle(a,b);

一个编译器可以首先计算 a 的值,然后调用函数 puan_ekle(a,b),该函数具有更改 a 的副作用。而其他编译器可以首先调用该函数,然后在函数中更改后获取 a 的值。

所以程序有未定义的行为。

如果函数没有副作用,那么行为将被明确定义,与加法运算符的操作数的评估顺序无关。

行为是未指定的,不是未定义的。

C 标准区分了这些。 C 2018 3.4.4 1 说:

unspecified behavior

behavior, that results from the use of an unspecified value, or other behavior upon which this document provides two or more possibilities and imposes no further requirements on which is chosen in any instance

而 3.4.3 1 表示:

undefined behavior

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements

在某些情况下,当一个对象既被用作它的值又被修改时,C 标准中的规则使行为未定义。 6.5 2 说:

If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.

让我们看看这如何适用于 a=a+puan_ekle(a,b);。在这个表达式中:

  1. aa=修饰。
  2. a用于a+.
  3. a 用于参数 (a,b).
  4. 在函数puan_ekle中,a修改为a=puan-5;

修改是副作用——它们是与计算表达式值分开发生的事情。如果修改 1 或 4 中的任何一个相对于任何其他项目没有排序,则行为未定义。

关于 1,6.5.16 3 说:

… The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands…

所以1排在2和3之后。由于4是副作用,不是值计算,所以我们还是要考虑1和4的关系。为了解决这个问题,我们会考虑序列点。根据 5.1.2.3,“在表达式 AB 的求值之间存在序列点意味着每个值计算和与A 在与 B 相关的每个值计算和副作用之前排序。”

接下来我们需要知道什么是完整表达式,以及每个完整表达式后都有一个序列点。 6.8 4 说:

A full expression is an expression that is not part of another expression, nor part of a declarator or abstract declarator… There is a sequence point between the evaluation of a full expression and the evaluation of the next full expression to be evaluated.

这意味着 puan_ekle 中的每个语句都是或包含一个完整表达式:puan=puan+bonus 是一个完整表达式,a=puan-5 是一个完整表达式,bonus-- 是一个完整表达式,return bonus 中的 bonus 是完整表达式。所以,在a=puan-5之后,有一个序列点

因为,对于 a=,修改 a 的副作用在操作数的值计算之后排序。评估这些操作数包括调用包含其序列点的函数。因此效果 4,在 a=puan-5; 中修改 a,必须在继续执行下一条语句之前完成,因此必须在效果 1 之前完成。所以 1 和 4 是有顺序的。

剩下的就是考虑关于 2 和 3 的效果 4。4 在 3 之后排序,因为根据 6.5.2.2 10,函数调用在评估其参数之后排序:

There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call…

现在只剩下2相对于4的排序了,这里并没有具体说明哪个在先。 + 的操作数的评估是无序的,因此,对于 a+puan_ekle(a,b),C 实现可以先执行 a 或先执行 puan_ekle(a,b)。但是,无论哪个先做,2和4之间有一个序列点:

  • 如果a先求值,那么在函数调用之前,有一个序列点(根据6.5.2.2 10,上面引用)。
  • 如果puan_ekle(a,b)先求值,则完整表达式a=puan-5后有一个序列点。

因此,2 和 4 是 不确定序列。 (5.1.2.3 3:“……评估 ABA 被排序时不确定地排序在 B 之前或之后,但未指定是哪个……”)但它们并非未排序,因此不存在未定义的行为。该行为未指定,因为有两种可能性。 C 实现需要实现这两种可能性中的一种,这与未定义的行为不同,后者没有要求。