1

函数参数

  • 函数参数在本质上与局部变量相同在栈上分配空间
  • 函数参数的初始值是函数调用时的实参值

clipboard.png

  • 函数参数的求值顺序依赖于编译器的实现
  • 操作符的求值顺序依赖于编译器的实现(+-*/...)

下面的程序输出什么?为什么呢?

int k = 1;
printf("%d, %d\n", k++, k++);

实例分析: 函数参数的求值顺序

#include <stdio.h>

int func(int i, int j)
{
    printf("i = %d, j = %d\n", i, j);
}

int main()
{
    int k = 1;
    
    func(k++, k++);
    
    printf("%d\n", k);
}
输出:【gcc】
i = 2, j = 1
3

特别说明:此处暂时没有找到有其它求值顺序的编译器来输出说明。
由于C语言未明确规定函数参数的求值顺序,其交由具体的编译器厂商决定,因此为了提高程序的可移植性,不可依赖某一编译器的求值顺序行为。

程序中的顺序点

  • 程序中存在一定的顺序点
  • 顺序点是指执行过程中修改变量值(内存)的最晚时刻
  • 在程序到达顺序点的时候,之前所作的一切操作必须完成

C 语言中的顺序点

  • 每个完整表达式结束时,即分号处
  • &&, ||, ?: 及 逗号表达式 的每个参数计算之后
  • 函数调用时所有实参求值完成后(进入函数体之前)

下面的程序运行结束后 k 的值为多少呢?

int k = 2;
k = k++ + k++;

编程实验: 程序中的顺序点

实验 1

#include <stdio.h>

int main()
{
    int k = 2;
    int a = 1;
    
    k = k++ + k ++;          // 注意这里!
    
    printf("k = %d\n", k);
    
    if( a-- && a )           // 注意这里!
    {
        printf("a = %d\n", a);
    }
    
    return 0;
}
输出【gcc】:
k = 6

输出【vs2010】:
k = 6

VS2010 汇编

k = k++ + k ++;
003D356C  mov         eax,dword ptr [k]  
003D356F  add         eax,dword ptr [k] 
003D3572  mov         dword ptr [k],eax ; k = 2 + 2 = 4 
003D3575  mov         ecx,dword ptr [k]  
003D3578  add         ecx,1   
003D357B  mov         dword ptr [k],ecx ; k = k + 1 = 4 + 1 = 5
003D357E  mov         edx,dword ptr [k]  
003D3581  add         edx,1  
003D3584  mov         dword ptr [k],edx ; k = k + 1 = 4 + 1 = 6

gcc 汇编

int k = 2;
080483cd:   movl $0x2,0x1c(%esp)

k = k++ + k ++;    
080483dd:   mov  0x1c(%esp),%eax
080483e1:   add  %eax,%eax 
080483e3:   mov  %eax,0x1c(%esp)   ; k = 2 + 2 = 4
080483e7:   addl $0x1,0x1c(%esp)   ; k = k + 1 = 4 + 1 = 5
080483ec:   addl $0x1,0x1c(%esp)   ; k = k + 1 = 5 + 1 = 6

实验 2

#include <stdio.h>

int func(int i, int j)
{
    printf("i = %d, j = %d\n", i, j);
}

int main()
{
    int k = 1;
    
    func(k++, k++);
    
    printf("%d\n", k);
}
输出:【gcc】
i = 2, j = 1
3

输出:【用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 16.00.30319.01 版】
i = 1, j = 1
3

分析:
C 文件会被编译成汇编文件。一条C代码可能对应多条汇编代码,汇编代码的顺序也许没有特定的规定。对于不同的编译器,可能有不同的编译方式,但都必须满足这一原则:在程序到达顺序点时,所有改变内存的操作必须完成。

注意:
在实际工程中, 需要遵循一定的规则避免非 C 语言规定而与编译器相关的写法。
对于“函数参数的求值顺序”、“程序中的顺序点“不必过度深究,遇到奇怪的问题时,思考是否是这里导致的问题即可。

小结

  • 函数的参数在栈上分配空间
  • 函数的实参并没有固定的计算次序
  • 顺序点时 C 语言中变量修改的最晚时机

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
737 声望139 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧