当前位置:  开发笔记 > IOS > 正文

这是C中未定义的行为吗?如果不是逻辑上预测输出

如何解决《这是C中未定义的行为吗?如果不是逻辑上预测输出》经验,为你挑选了3个好方法。

代码1

#include 
int f(int *a, int b) 
{
  b = b - 1;
  if(b == 0) return 1;
  else {
    *a = *a+1;

    return *a + f(a, b);
  }
}

int main() {
  int X = 5;
  printf("%d\n",f(&X, X));
}

考虑这个C代码.这里的问题是预测输出.从逻辑上讲,我得到31作为输出.(机器输出)

当我将return语句更改为

return f(a, b) + *a;

我逻辑上得到37.(机器输出)

我的一个朋友说,在计算返回语句时

return *a + f(a, b);

我们计算树的深度值,即*先f(a, b)调用然后调用,而在

return f(a,b) + *a;

它在返回时被解析,即先f(a, b)被计算然后*a被调用.

通过这种方法,我尝试自己预测以下代码的输出:

代码2

#include 
int foo(int n) 
{
    static int r;
    if(n <= 1)
        return 1;

    r = n + r;
    return r + foo(n - 2);
} 

int main () {
   printf("value : %d",foo(5));
}

对于 return(r+foo(n-2));

在此输入图像描述

我的逻辑输出为14(机器输出)

对于 return(foo(n-2)+r);

在此输入图像描述

我得到17作为输出.(机器输出)

但是,当我在我的系统上运行代码时,我在两种情况下都得到17.

我的问题:

我的朋友给出的方法是正确的吗?

如果是这样,为什么我在机器上运行时在代码2中获得相同的输出?

如果没有,解释代码1代码2的正确方法是什么

是否有任何未定义的行为,因为C不支持通过引用传递?由于它在代码1中使用很难,它可以使用指针实现吗?

简而言之,我只是想知道在上述4种情况下预测输出的正确方法.



1> Jonathan Lef..:

代码1

对于代码1,由于标准未指定return *a + f(a, b);(和in return f(a, b) + *a;)中的术语的评估顺序,并且函数修改了a指向的值,因此您的代码具有未指定的行为,并且可能有各种答案.

正如您在评论中从愤怒中可以看出的那样,术语"未定义的行为","未指定的行为"等在C标准中具有技术含义,并且此答案的早期版本滥用了"未定义的行为",它应该使用'未指定".

问题的标题是"这是C中未定义的行为吗?",答案是"否;它是未指定的行为,而不是未定义的行为".

守则2 - 经修订

对于固定的代码2,该函数还具有未指定的行为:静态变量的值r由递归调用更改,因此对评估顺序的更改可能会更改结果.

代码2 - 修订前

对于代码2,如最初所示int f(static int n) { … },代码不会(或者,至少不应该)编译.函数参数定义中允许的唯一存储类是register,所以存在static应该给你编译错误.

ISO/IEC 9899:2011§6.7.6.3 函数声明(包括原型) 2参数声明中唯一的存储类说明符是register.

在macOS Sierra 10.12.2上使用GCC 6.3.0进行编译,如下所示(注意,不需要额外的警告):

$ gcc -O ub17.c -o ub17
ub17.c:3:27: error: storage class specified for parameter ‘n’
 int foo(static int n)
                    ^

没有; 它根本没有编译 - 至少,不是我使用现代版本的GCC.

但是,假设已修复,该函数还具有未定义的未指定行为:静态变量的值r由递归调用更改,因此对评估顺序的更改可能会更改结果.


你确定它是未定义的吗?我希望未指定(gcc评估从右到左,MSVC从左到右评估)但未定义,因为我认为函数调用会在左右操作数的评估之间引入序列点(无论以哪种顺序) .
第一个例子是定义的.请看我的答案.http://stackoverflow.com/a/41777679/4082723

2> haccks..:

C标准规定

6.5.2.2/10函数调用:

在评估函数指示符和实际参数之后但在实际调用之前有一个序列点.调用函数(包括其他函数调用)中的每个评估(在执行被调用函数的主体之前或之后未特别排序)在被调用函数的执行方面不确定地被排序1.94)

脚注86(第6.5/3节)说:

程序执行期间不止一次评估的表达式中,不需要在不同的评估中一致地执行其子表达式的未序列不确定顺序的评估.

在表达式return f(a,b) + *a;return *a + f(a,b);子表达式的评价*a是不定测序.在这种情况下,可以看到同一程序的不同结果.
请注意,副作用a在上面的表达式中排序,但未指定顺序.


1.当A在B之前或之后进行测序时,评估A和B是不确定的,但未指明哪一个.(C11-5.1.2.3/3)


@pC_:订单可以通过优化设置,编译器版本,月亮阶段以及是否喜欢您的用户ID来更改.是的,实际上,在给定的一组优化设置下,给定版本的编译器通常会以相同的顺序评估事物,但是表达式"a"或"b"的复杂程度可能很重要(即,如果这些术语例如,`*c [i ++] +*d [ - j]`,你对发生的事情了解不多 - 编译器可能会对简单的"a + b"进行不同的命令.
在`f`和`f`的参数被评估之后@haccks,有一个序列点.然后执行`f`正文中的语句,并且这些语句使用`return`表达式的其余部分进行不确定的排序.(换句话说,`*a`要么在所有的主体陈述之前,要么在所有的之后)
在C99语言中,在函数调用之前和之后存在一个序列点,因此不会在没有序列点的情况下评估"*a"和修改同一对象.C11使用不同的语言,但它在逻辑上是等价的.
@pC_ - 不是真的.很可能首先不会计算"a"和"b".计算可能是交错的.

3> 2501..:

我将重点关注第一个例子的定义.

第一个示例使用未指定的行为进行定义.这意味着有多种可能的结果,但行为未定义.(如果代码可以处理这些结果,则定义行为.)

一个未指明的行为的简单例子是:

int a = 0;
int c = a + a;

未指定是否首先评估左a或右a,因为它们未被排序.该+运营商不指定任何顺序指出1.有两种可能的排序,左边a先评估然后右边a,反之亦然.由于两侧都没有被修改2,因此定义了行为.


留下了一个或右边被修改,没有顺序点,即未测序,行为将是不确定的2:

int a = 0;
int c = ++a + a;


如果左侧或右侧的序列点被修改过,那么左侧和右侧将被不确定地排序3.这意味着它们是按顺序排序的,但是未指定哪个是先评估的.行为将被定义.请注意逗号运算符引入序列点4:

int a = 0;
int c = a + ((void)0,++a,0);

有两种可能的排序.

如果首先评估左侧,则评估为0.然后评估右侧.评估First(void)0,然后是序列点.然后a增加,然后是序列点.然后将0评估为0并将其添加到左侧.结果是0.

如果首先评估右侧,则评估(void)0,然后是序列点.然后a增加,然后是序列点.然后将0评估为0.然后评估左侧,评估为1.结果为1.


您的示例属于后一类,因为操作数是不确定的顺序.函数调用与上例中的逗号运算符具有相同的用途5.你的例子很复杂,所以我会使用我的,这也适用于你的.唯一的区别是你的例子中有更多可能的结果,但我的推理是相同的:

void Function( int* a)
{
    ++(*a);
    return 0;
}
int a = 0;
int c = a + Function( &a );
assert( c == 0 || c == 1 );

有两种可能的排序.

如果首先评估左侧,则评估为0.然后评估右侧,有一个序列点并调用该函数.然后a递增,接着是由完整表达式6的末尾引入的另一个序列点,其结尾由分号表示.然后返回0并添加到0.结果为0.

如果首先评估右侧,则存在序列点并调用该函数.然后a递增,接着是完整表达式结束引入的另一个序列点.然后返回0.然后评估左侧,计算结果为1并添加到0.结果为1.


(引用自:ISO/IEC 9899:201x)

1(6.5表达式3)
除非后面指出,否则子表达的副作用和值计算未被排序.

2(6.5表达式2)
如果相对于同一标量对象的不同副作用或使用相同标量对象的值计算值对标量对象的副作用未被排序,则行为未定义.

3(5.1.2.3程序执行)
当A在B之前或之后进行测序时,评估A和B是不确定的,但未指定哪一个.

4(6.5.17逗号运算符2)逗号运算符
的左操作数被计算为void表达式; 它的评估与右操作数之间存在一个序列点.

5(6.5.2.2函数调用10)
在评估函数指示符和实际参数之后但在实际调用之前有一个序列点.

6(6.8语句和块4)
在完整表达式的评估和要评估的下一个完整表达式的评估之间存在一个序列点.

推荐阅读
赛亚兔备_393
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有