第44课 函数参数的秘密 (上)

函数参数:

函数参数的求值顺序依赖于编译器的实现:

第一感觉,这个函数会输出1和2,k的最终值会变成3。

示例程序:

 1 #include <stdio.h>
 2 
 3 int func(int i, int j)
 4 {
 5     printf("%d, %d\n", i, j);
 6     
 7     return 0;
 8 }
 9 
10 int main()
11 {
12     int k = 1;
13     
14     func(k++, k++);
15     
16     printf("%d\n", k);
17     
18     return 0;
19 }

运行结果如下:

这与我们的预期是不符的,这是为什么呢?

因为函数参数的求值顺序是和编译器相关的,并没有规定第一个k++先求职,第二个k++后求职。只是规定了参数的值必须求出来之后才进行确切的函数的调用。

在这里我们可以得出,求值顺序是先求了第二个k++,又求了第一个k++。

这个现象可以扩展到操作符上,例如乘法操作的左右两个操作数并没有规定哪一个先把值求出来。这也是依赖于编译器的实现。

上述程序在bcc32下的输出如下;

程序中的顺序点:

程序中有如下顺序点,例:

上面例子中,我们第一直觉会认为k的最终值为5。

示例如下:

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     int k = 2;
 6     int a = 1;
 7     
 8     k = k++ + k++;
 9     
10     printf("k = %d\n", k);
11     
12     if( a-- && a )
13     {
14         printf("a = %d\n", a);
15     }
16     
17     return 0;
18 }

运行结果如下:

第8行的语句,存在3个改变内存的操作,两个++操作和一个赋值操作。编译器回先执行加法操作,编译器先取出第一个k的值,然后是一个++操作,这个操作先挂起不执行,然后取出第二个k的值,这个k的++操作也是先挂起,这时候有两个++操作悬挂着。这时候到了分号(;)了,改变内存的操作必须完成了,于是执行加法操作,将结果写入k代表的内存中,然后执行悬挂着的两个++操作,对k所代表的内存连加两次1。这时k就变成了6。

上述程序在bcc中的运行结果如下:

在vc编译器中的反汇编如下:

可以看到先取出k的值相加,然后使用ecx、edx对这个内存连加两次1。

得到的最终结果也是6。

上述程序第12行也存在顺序点,第14行的语句并没有执行到,这是为什么?

因为执行完a--操作之后就遇到了&&,这是一个顺序点,对内存的操作必须立刻完成,于是a变为了0,再读取a时就是0了,因此1&&0就变为了假。

函数调用时所有实参求值完成后(进入函数体之前)也是一个顺序点,示例如下:

 1 #include <stdio.h>
 2 
 3 int func(int i, int j)
 4 {
 5     printf("%d, %d\n", i, j);
 6     
 7     return 0;
 8 }
 9 
10 int main()
11 {
12     int k = 1;
13     
14     func(k++, k++);
15     
16     printf("%d\n", k);
17     
18     return 0;
19 }

第14行存在顺序点,这两个++操作,在进入func函数体之前必须完成,这里说的完成是修改k所在的内存,而不是传进func函数中的值,有可能将k先取出,两个++先悬挂着,这时k为1,然后将k传入到i和j(这就是所谓的实参求值完成后),在进入func前是顺序点,因此,调用func前,执行悬挂着的++,然后改变k所在的内存。

 上述程序在vc编译器下的运行结果如下:

可以看到和我们分析的一样。

小结:

猜你喜欢

转载自www.cnblogs.com/wanmeishenghuo/p/9557758.html
今日推荐