Farseer

导航

C#函数参数传值讨论(三)

那么C#是如何处理函数的传值的那?

C#在函数传值的时候的有两种类型,值类型和引用传值。

值类型传值就不用多说了,肯定是采用拷贝传值方式,这在C/C++里都是这样的,C#也不能免俗。

using System;

public class MyClass
{
    
public static void Main()
    
{
        
int a=0;
        
int b=1;
        swap(a,b);
        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.ReadLine();
    }

    
public static void swap(int c,int d)
    
{
        
int temp;
        temp
=c;
        c
=d;
        d
=temp;
    }

}


从输出结果上可以看出a,b的值并未改变,由此可知C#也是采用了C语言的拷贝传值方式。

那么C#是如何实现引用传值的那?用ref和out,其中ref在传参之前要赋初值,而out不需要赋初值,但必须在被调用函数体内赋值。
C#的引用传值是采用了C语言的拷贝传值还是C++中那个诡异的引用传值那?

按<<.Net本质论>>的观点,是采用了C语言的方式,因为它的描述如下:在采用引用传值的时候,形参是一个指向实参的托管指针。如果按照C++中引用传值的概念,形参就是实参,实参就是形参。

但是如果翻开《C#高级编程》,上面赫然写着:“C#的引用传值就是C++里通过&方式的引用传值。"

这两本巨著显然是矛盾的。

由于不能通过查看地址的方式查看形参和实参的内存地址,我们只有通过查看其生成的MSIL来探究其实质了。下面是通过引用来传参的源代码。

using System;

public class MyClass
{
    
public static void Main()
    
{
        
int a=0;
        
int b=1;
        swap(
ref a,ref b);
        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.ReadLine();
    }

    
public static void swap(ref int c,ref int d)
    
{
        
int temp;
        temp
=c;
        c
=d;
        d
=temp;
    }

}

通过输出结果可以看出,a,b的值确实发生了变化,可是无论是C的指针,还是C++的引用都会实现这样的结果,从代码的风格看更像C++的引用传值,因为没有取内容的符号*。下面是用ILDASM反汇编的MSIL,来看一下到底是怎么回事吧!

.method public hidebysig static void  swap(int32& c,
                                           int32
& d) cil managed
{
  
// 代码大小       11 (0xb)
  .maxstack  2
  .locals init ([
0] int32 temp)
  IL_0000:  ldarg.
0
  IL_0001:  ldind.i4
  IL_0002:  stloc.
0
  IL_0003:  ldarg.
0
  IL_0004:  ldarg.
1
  IL_0005:  ldind.i4
  IL_0006:  stind.i4
  IL_0007:  ldarg.
1
  IL_0008:  ldloc.
0
  IL_0009:  stind.i4
  IL_000a:  ret
}
 // end of method MyClass::swap

上面的是函数swap的反汇编代码。可以看到C++中可爱的&符号,这似乎是C++的代码。首先解释一下这些代码中汇编符号的意思。
ldarg.<0>将参数压入栈,<0>代表是第1个参数
ldind.i4  将间接的值(通过地址)压入栈,i4是值的类型,注意这个操作首先是栈顶元素出栈,作为地址取值
stloc.0     从栈中弹出元素,将值赋值给局部变量
stind.i4   
从堆栈中弹出间接(通过地址)的值,注意这个操作是将栈顶元素的值赋给紧接栈定元素的元素指向的值,并将两个元素都出栈
ldloc.0  将本地变量压入栈
ret            从方法返回

下面是上述操作MSIL的分析结果
.method public hidebysig static void  swap(int32& c, int32& d) cil managed

{
  
// 代码大小       11 (0xb)
 .maxstack  2
 .locals init ([
0] int32 temp)  //定义局部变量
  IL_0000:  ldarg.0              
 
//操作 将参数c压栈        结果:栈中元素为 c
 IL_0001:  ldind.i4   
//操作:将c出栈,以c为地址找到对应的值,并将对应的值压栈  结果:c出栈,
//c指向的值入栈  栈中元素为 c指向的值
IL_0002:  stloc.0    
//操作:栈顶元素出栈,并赋值给局部变量temp  
//结果:c指向的值出栈,temp的值为指向的值,栈为空

                           
//以上代码对应C#源码为:temp=c;

  IL_0003:  ldarg.
0           
//操作:参数c压栈    结果: 栈中元素为c
  IL_0004:  ldarg.1            
//操作:参数d压栈     结果:栈中元素为c,d
  IL_0005:  ldind.i4    
//操作:将栈顶元素d出栈,以d为地址找到对应的值,并将对应的值压栈 
//结果:d出栈,d指向的值入栈  栈中元素为 c和d指向的值 
 IL_0006:  stind.i4
//操作:将栈顶元素的值赋值给紧接栈顶元素所指向的值,即将d指向的值赋值给c指向的值,并将两个栈中
//元素出栈  结果:栈为空
               
//以上代码对应C#源码为:c=d;
 IL_0007:  ldarg.1
//操作:参数d入栈   结果:栈中元素为d
 IL_0008:  ldloc.0
//将本地变量temp入栈  结果:栈中元素为d,temp
 IL_0009:  stind.i4
//操作:将栈顶元素的值赋值给紧接栈顶元素所指向的值,即将temp的值赋值给d指向的值,并将两个栈中元//素出栈  结果:栈为空
               
//以上代码对应C#源码为:d=temp;
 IL_000a:  ret
//操作:返回调用函数   结果:程序结束

}
 // end of method MyClass::swap

从以上的讨论可以看出,MSIL把参数c,d当作地址来使用,即是指针,而不是像C++那样的引用传值.
当然如果认为在调用前先取值,偶后对地址采用引用传值,那也是说不过去的,参数类型都不正确!
查看Main函数的MSIL可以知道,确实有变量a,b入栈的过程,那么c,d是a,b的拷贝那,还是引用?如果按照C++的理论,根本没必要取a,b的地址入栈,直接a,b入栈就可以了。
所以我还是认为C#采用的是指针拷贝方式。

也许我理解的不够深刻,等我理解的更加深入一些,也许我会来修改这篇帖子,也希望高人指点一二。

posted on 2005-03-15 14:28  佛西亚  阅读(1745)  评论(0)    收藏  举报