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#采用的是指针拷贝方式。
也许我理解的不够深刻,等我理解的更加深入一些,也许我会来修改这篇帖子,也希望高人指点一二。
C#在函数传值的时候的有两种类型,值类型和引用传值。
值类型传值就不用多说了,肯定是采用拷贝传值方式,这在C/C++里都是这样的,C#也不能免俗。
从输出结果上可以看出a,b的值并未改变,由此可知C#也是采用了C语言的拷贝传值方式。
那么C#是如何实现引用传值的那?用ref和out,其中ref在传参之前要赋初值,而out不需要赋初值,但必须在被调用函数体内赋值。
C#的引用传值是采用了C语言的拷贝传值还是C++中那个诡异的引用传值那?
按<<.Net本质论>>的观点,是采用了C语言的方式,因为它的描述如下:在采用引用传值的时候,形参是一个指向实参的托管指针。如果按照C++中引用传值的概念,形参就是实参,实参就是形参。
但是如果翻开《C#高级编程》,上面赫然写着:“C#的引用传值就是C++里通过&方式的引用传值。"
这两本巨著显然是矛盾的。
由于不能通过查看地址的方式查看形参和实参的内存地址,我们只有通过查看其生成的MSIL来探究其实质了。下面是通过引用来传参的源代码。
通过输出结果可以看出,a,b的值确实发生了变化,可是无论是C的指针,还是C++的引用都会实现这样的结果,从代码的风格看更像C++的引用传值,因为没有取内容的符号*。下面是用ILDASM反汇编的MSIL,来看一下到底是怎么回事吧!
上面的是函数swap的反汇编代码。可以看到C++中可爱的&符号,这似乎是C++的代码。首先解释一下这些代码中汇编符号的意思。
ldarg.<0>将参数压入栈,<0>代表是第1个参数
ldind.i4 将间接的值(通过地址)压入栈,i4是值的类型,注意这个操作首先是栈顶元素出栈,作为地址取值
stloc.0 从栈中弹出元素,将值赋值给局部变量
stind.i4
从堆栈中弹出间接(通过地址)的值,注意这个操作是将栈顶元素的值赋给紧接栈定元素的元素指向的值,并将两个元素都出栈
ldloc.0 将本地变量压入栈
ret 从方法返回
下面是上述操作MSIL的分析结果
从以上的讨论可以看出,MSIL把参数c,d当作地址来使用,即是指针,而不是像C++那样的引用传值.
当然如果认为在调用前先取值,偶后对地址采用引用传值,那也是说不过去的,参数类型都不正确!
查看Main函数的MSIL可以知道,确实有变量a,b入栈的过程,那么c,d是a,b的拷贝那,还是引用?如果按照C++的理论,根本没必要取a,b的地址入栈,直接a,b入栈就可以了。
所以我还是认为C#采用的是指针拷贝方式。
也许我理解的不够深刻,等我理解的更加深入一些,也许我会来修改这篇帖子,也希望高人指点一二。
浙公网安备 33010602011771号