令人困惑的C代码,有人为我解释?

时间:2010-07-14 20:34:50

标签: c

评估顺序确实很重要,因此,这是否称为非参照透明度?

int i = 1;
int counter(){
     i = i + 1;
     return i;
}
int foo(int i, int j){
    return i*2 + 3*j;
}
int main(){
   printf("%d", foo(counter(), counter()));
}

8 个答案:

答案 0 :(得分:10)

我猜你可能想到的是,函数参数的评估顺序在C中没有标准化。因为counter()将在每次调用时返回不同的结果,结果foo(2, 3)foo(3, 2)不同,编译和执行此代码可能会在不同平台上为您提供不同的结果

然而,在同一平台上,它是确定性的,正如其他人所解释的那样。 [更新] (准确地说:一旦在具有特定编译器选项的特定平台上编译成可执行文件,所有执行都将产生相同的输出。但是,正如评论者指出的那样,可能< / em>使用不同的编译选项构建时,甚至可以在同一平台上生成不同的输出。) [/ Update]

答案 1 :(得分:7)

严格地说,即使在具有相同编译器和设置的同一平台上编译,有问题的代码也可能给出不同的结果。未指定评估函数参数的顺序。 C标准将“未指定的行为”定义为

  

使用未指明的值或本国际标准提供两种或更多种可能性的其他行为,并且在任何情况下都不会对其进行任何进一步的要求(C99§3.4.4/ 1)。

重要的部分是“在任何情况下”实现可能会做一些不同的事情,因此,例如,您的编译器可以发出代码,随机选择评估参数的顺序。< / p>

显然,非常不可能任何实现都会在同一程序的不同运行期间以不同方式评估函数的参数。

关键是你不应该依赖于评估函数参数的顺序;在一个正确的程序中,它应该没关系。

答案 2 :(得分:6)

它是确定性的,每次都会返回相同的值。

答案 3 :(得分:4)

每次调用它时,

counter()都会返回不同的数字,因为i是全局的。但是,全局变量仅在执行期间保持其值。如果重新启动程序,它将获得值1并重新开始!

答案 4 :(得分:3)

有几个答案表明,虽然不同的平台可能会给出不同的结果,但结果在给定平台上是确定性的。

这不正确

C99标准说(6.5 / 3表达式):

  

除了稍后指定的(对于函数调用(),&amp;&amp;,||,?:和逗号运算符),子表达式的评估顺序和副作用的发生顺序都未指定

因此,标准未指定对foo()的调用中的参数的评估顺序。无法计算2调用counter()的顺序。特定编译器可以根据以下方式对调用进行不同的排序:

  • 要求编译器执行的优化
  • 确切的代码集(包含文件,翻译单元中的略有或显着不同的源代码,无论如何)
  • 该计划的一周中的哪一天
  • 随机数

虽然使用优化之外的其他内容不太可能,但其他编译器选项的差异或转换单元的差异将导致参数评估的顺序不同(因为编译器可能没有太多理由生成不同的输出),事实上你根本不能依赖于排序。

事实上,每次调用foo()时,调用的评估顺序都是不同的(就标准而言)。例如,假设你的示例程序看起来像(在更明显的时候发生了什么):

#include <stdio.h>

int i = 1;

int counter1(){
     i = i * 3;
     printf( "counter1()\n");
     return i;
}

int counter2(){
     i = i * 5;
     printf( "counter2()\n");
     return i;
}

int foo(int i, int j){
    return i + j;
}

int main(){
   int x;
   for (x=0; x<2; ++x) {
       printf("%d\n", foo(counter1(), counter2()));
   }

   return 0;
}

输出看起来像以下任何一种都是完全有效的(注意至少还有一种可能性):

可能性1:

counter1()
counter2()
18
counter1()
counter2()
270

可能性2:

counter1()
counter2()
18
counter2()
counter1()
300

可能性3:

counter2()
counter1()
20
counter2()
counter1()
300

编译器每次执行该代码行时都会以不同的方式评估参数,这是可以的(即使非常奇怪),但是标准未指定顺序的事实允许这样做。

虽然评估不太可能“随机化”,但我确实认为难以控制优化级别(或其他编译器设置),编译器的精确版本/补丁级别甚至是精确级别。围绕表达式的代码可能导致编译器选择不同的评估路径。

依赖于函数参数的评估顺序,即使在特定的平台上,也在调整危险。

作为旁注,这是在可能的情况下避免在函数中隐藏副作用的原因之一。

答案 5 :(得分:1)

代码是确定性的,但它打印的内容可能取决于编译器,因为foo可能会收到2,3或3,2。

答案 6 :(得分:0)

Code Clown提到代码是确定性的。它会在相同的“编译器”上给你相同的输出。

C标准没有指定方法调用参数的评估顺序。因此,首先调用方法foo的两个调用中的哪一个由编译器决定。

答案 7 :(得分:0)

函数foo()不是引用透明的。引用透明性意味着函数应在相同输入上调用时返回相同的值。要做到这一点,功能必须是纯粹的,即它不应该有任何副作用。

C语言不保证功能是纯粹的,必须通过以下方式管理它:

  • 不在本地静态字段中存储方法的形式参数
  • 不依赖于全局变量的值

(有许多方法可以使函数引用不透明,这些更常见)

此处对counter()的后续调用会产生不同的值,因此它是参考不透明的。