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

问题描述 投票:2回答:2

我在使用不同的编译器的相同代码中得到了不同的结果。这是未定义的行为吗?

#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);
}
c gcc clang side-effects unspecified-behavior
2个回答
6
投票

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

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

未指定行为

行为,是由于使用了未指定的值或本文档提供两种或多种可能性并且在任何情况下都不对所选择的内容施加其他要求的其他行为所致]

而3.4.3 1说:

未定义行为

行为,在使用非便携式或错误的程序构造或错误的数据时,对此文档没有任何要求

[在某些情况下,当同时使用对象的值和修改对象时,C标准中的规则会使行为不确定。 6.5 2说:

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

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

  1. [aa=修改。
  2. a用于a+
  3. a用于参数(a,b)
  4. 在功能puan_ekle中,用a修改了a=puan-5;
  5. 修改是副作用-它们与计算表达式的值是分开发生的。如果相对于任何其他项目,修改1或4中的任何一个均未排序,则行为未定义。

关于1,6.5.16 3说:

…更新左操作数的存储值的副作用在计算左和右操作数的值之后排序…

因此1在2和3之后排序。由于4是副作用,而不是值计算,因此我们仍然必须考虑1和4的关系。要解决此问题,我们将考虑序列点。根据5.1.2.3,“在表达式A

B的求值之间存在序列点意味着,在每次值计算和与[[B相关的副作用。”下一步,我们需要知道完整表达式是什么,并且每个完整表达式之后都有一个序列点。 6.8 4说:

A

full expression
是一个表达式,它不是另一个表达式的一部分,也不是声明符或抽象声明符的一部分……在对完整表达式的求值与对下一个完整表达式的求值之间存在一个序列点。被评估。

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

因为,对于a=,修改a的副作用在操作数的值计算之后进行排序。评估这些操作数包括调用函数,该函数包括其序列点。因此,必须在执行继续到下一条语句之前完成对效果4的修改,然后才能对效果1进行排序。

剩下的就是考虑效果2相对于2和3的影响。4在3之后排序,因为根据6.5.2.2 10,函数调用在对其参数求值之后进行排序:

在函数指定符和实际参数的求值之后但在实际调用之前有一个序列点…

现在剩下的就是2相对于4的排序。在此,没有哪个规范是第一个。对a的操作数的求值是无序列的,因此,对于a=puan-5;,C实现可以先执行+或先执行a+puan_ekle(a,b)。但是,无论先执行哪个操作,都会在2和4之间存在一个序列点:

如果首先评估a,那么在函数调用之前,有一个序列点(根据6.5.2.2 10,如上所引用)。

    如果首先计算puan_ekle(a,b),则在完整表达式a之后有一个序列点。
  • 因此,2和4被
  • 不确定地排序
  • 。 (5.1.2.3 3:“…在

    B

之前或之后对AB进行不确定的排序,但未指定是哪个……”)但是它们不是无序列的,因此没有未定义的行为。该行为未指定,因为有两种可能性。需要C实现来实现这两种可能性之一,这与未定义的行为不同,在后者中将没有要求。
未指定加法运算符的操作数的顺序pf评估。
例如,在此语句中
puan_ekle(a,b)

一个编译器可以首先评估a的值,然后调用具有改变a的副作用的函数puan_ekle(a,b)。而其他编译器可以首先调用该函数,然后在函数中对其进行更改后获得a的值。

因此该程序具有未定义的行为。

如果函数没有副作用,则行为将被良好定义,而与加法运算符的操作数的求值顺序无关。

3
投票
© www.soinside.com 2019 - 2024. All rights reserved.