几乎所有的书或文章都说,引用类型的对象是在托管堆里面的,而值类型的对象可以在栈里也可以在堆里(例如:装箱与拆箱),但是,今天写一个推翻引用类型的对象是在托管堆里面的情况:
首先,在这里使用了大量的Emit和反射,因为不喜欢直接写IL汇编,所以看起来有点累,但是IL的知识是必须的,否则很难看明白这些代码是在干什么。
其次,要理解为什么会这样,需要了解一些程序底层的知识。
最后,准确的说,这里的对象并不是一个真实的对象,仅仅是把某段内存认为是某个对象,一般的高级语言不允许这样的代码,所以只能用最基础的IL来做。
乍一看,代码有些长,下面仔细分析一下:
1、Foo方法,作用是为Emit做准备,并且测试,调用"test"方法
2、Bar方法,Emit一个叫"Foo"的引用类型的类,带有一个"Bar"的字段,并且有一个实例构造器和一个"test"方法
3、在实例构造器中,对"Bar"字段赋初始值0
4、在"test"方法中,用local2的地址作为对象的地址,Call实例构造器(注意不能用newobj),这样的效果等于初始化对象的初始值(也就是说,Call实例构造器后,"Bar"字段的值就是构造器中赋的初始值)
5、再次用local2的地址作为对象的地址,给"Bar"字段赋数字123(注意,只有这里出现了123这个数字)
6、从控制台分别输出local1、local2、local3,以及用local2的地址作为对象的地址的"Bar"字段的内存地址和他们的值
代码的作用就是这些,看一下运行的结果:
运行结果中,可以意外的发现,local1也是123,而且local1的地址和"Bar"字段的地址一致。
当然这个值并不固定,而且local1、local2、local3的地址并不一定是递减的,也有可能是递增的,或者其它情况(虽然还没遇到,但是也不排除有分散的可能性)。
在递减的情况下,local1与以local2的地址作为对象地址的"Bar"字段的地址是一致的,也就是说,以local2的地址作为对象地址的情况下,"Bar"字段并不是在堆上,而是在栈上。对local1的任何改动可以影响"Bar"字段,反过来,对"Bar"字段的任何改动也可以影响local1,因为本质上它们就是同一个地址。
同样,以local2的地址作为对象地址的这个虚假引用类型的"对象",也是存在于栈上的,是从local2地址开始的,一片隶属于栈的内存。
最后,需要注意的一点是,这个方法有种钻运行时漏洞的感觉,在x86 .net 2.0的环境上测试通过,不保证其它运行时也能正常运行。
首先,在这里使用了大量的Emit和反射,因为不喜欢直接写IL汇编,所以看起来有点累,但是IL的知识是必须的,否则很难看明白这些代码是在干什么。
其次,要理解为什么会这样,需要了解一些程序底层的知识。
最后,准确的说,这里的对象并不是一个真实的对象,仅仅是把某段内存认为是某个对象,一般的高级语言不允许这样的代码,所以只能用最基础的IL来做。
1 static void Foo()
2 {
3 AssemblyName name = new AssemblyName("a");
4 AssemblyBuilder a = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
5 ModuleBuilder m = a.DefineDynamicModule("test", false);
6 TypeBuilder tb = Bar(m);
7 Type t = tb.CreateType();
8 MethodInfo mi = t.GetMethod("test", BindingFlags.Public | BindingFlags.Static);
9 mi.Invoke(null, null);
10 }
11
12 static TypeBuilder Bar(ModuleBuilder m)
13 {
14 // 创建一个Foo类
15 TypeBuilder result = m.DefineType("Foo");
16 // 定义一个bar字段
17 FieldBuilder field = result.DefineField("bar", typeof(int), FieldAttributes.Assembly);
18 // Foo的实例构造器
19 ConstructorBuilder ctor = result.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
20 ILGenerator gen = ctor.GetILGenerator();
21 gen.Emit(OpCodes.Ldarg_0);
22 gen.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
23 gen.Emit(OpCodes.Ldarg_0);
24 gen.Emit(OpCodes.Ldc_I4_0);
25 gen.Emit(OpCodes.Stfld, field);
26 gen.Emit(OpCodes.Ret);
27
28 // 定义test方法
29 MethodBuilder method = result.DefineMethod("test",
30 MethodAttributes.Public | MethodAttributes.Static,
31 CallingConventions.Standard, typeof(void), Type.EmptyTypes);
32
33 gen = method.GetILGenerator();
34 // 定义本地变量1、2、3
35 LocalBuilder local1 = gen.DeclareLocal(typeof(int));
36 LocalBuilder local2 = gen.DeclareLocal(typeof(int));
37 LocalBuilder local3 = gen.DeclareLocal(typeof(int));
38
39 // 用local2的地址作为对象地址,调用实例构造器
40 gen.Emit(OpCodes.Ldloca, local2);
41 gen.Emit(OpCodes.Call, ctor);
42 // 用local2的地址作为对象地址,把数字123放到字段里面
43 gen.Emit(OpCodes.Ldloca, local2);
44 gen.Emit(OpCodes.Ldc_I4, 123);
45 gen.Emit(OpCodes.Stfld, field);
46
47 // 来看看local1里面是什么
48 gen.Emit(OpCodes.Ldstr, "local1 address=");
49 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
50 gen.Emit(OpCodes.Ldloca, local1);
51 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
52 gen.Emit(OpCodes.Ldstr, "local1=");
53 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
54 gen.Emit(OpCodes.Ldloc, local1);
55 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
56 // 来看看local2里面是什么
57 gen.Emit(OpCodes.Ldstr, "local2 address=");
58 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
59 gen.Emit(OpCodes.Ldloca, local2);
60 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
61 gen.Emit(OpCodes.Ldstr, "local2=");
62 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
63 gen.Emit(OpCodes.Ldloc, local2);
64 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
65 // 来看看local3里面是什么
66 gen.Emit(OpCodes.Ldstr, "local3 address=");
67 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
68 gen.Emit(OpCodes.Ldloca, local3);
69 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
70 gen.Emit(OpCodes.Ldstr, "local3=");
71 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
72 gen.Emit(OpCodes.Ldloc, local3);
73 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
74
75 // 用local2的地址作为对象地址,看看字段里面是什么
76 gen.Emit(OpCodes.Ldstr, "Field address=");
77 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
78 gen.Emit(OpCodes.Ldloca, local2);
79 gen.Emit(OpCodes.Ldflda, field);
80 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
81 gen.Emit(OpCodes.Ldstr, "Field =");
82 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
83 gen.Emit(OpCodes.Ldloca, local2);
84 gen.Emit(OpCodes.Ldfld, field);
85 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
86
87 gen.Emit(OpCodes.Ret);
88
89 return result;
90 }
91
2 {
3 AssemblyName name = new AssemblyName("a");
4 AssemblyBuilder a = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
5 ModuleBuilder m = a.DefineDynamicModule("test", false);
6 TypeBuilder tb = Bar(m);
7 Type t = tb.CreateType();
8 MethodInfo mi = t.GetMethod("test", BindingFlags.Public | BindingFlags.Static);
9 mi.Invoke(null, null);
10 }
11
12 static TypeBuilder Bar(ModuleBuilder m)
13 {
14 // 创建一个Foo类
15 TypeBuilder result = m.DefineType("Foo");
16 // 定义一个bar字段
17 FieldBuilder field = result.DefineField("bar", typeof(int), FieldAttributes.Assembly);
18 // Foo的实例构造器
19 ConstructorBuilder ctor = result.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
20 ILGenerator gen = ctor.GetILGenerator();
21 gen.Emit(OpCodes.Ldarg_0);
22 gen.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
23 gen.Emit(OpCodes.Ldarg_0);
24 gen.Emit(OpCodes.Ldc_I4_0);
25 gen.Emit(OpCodes.Stfld, field);
26 gen.Emit(OpCodes.Ret);
27
28 // 定义test方法
29 MethodBuilder method = result.DefineMethod("test",
30 MethodAttributes.Public | MethodAttributes.Static,
31 CallingConventions.Standard, typeof(void), Type.EmptyTypes);
32
33 gen = method.GetILGenerator();
34 // 定义本地变量1、2、3
35 LocalBuilder local1 = gen.DeclareLocal(typeof(int));
36 LocalBuilder local2 = gen.DeclareLocal(typeof(int));
37 LocalBuilder local3 = gen.DeclareLocal(typeof(int));
38
39 // 用local2的地址作为对象地址,调用实例构造器
40 gen.Emit(OpCodes.Ldloca, local2);
41 gen.Emit(OpCodes.Call, ctor);
42 // 用local2的地址作为对象地址,把数字123放到字段里面
43 gen.Emit(OpCodes.Ldloca, local2);
44 gen.Emit(OpCodes.Ldc_I4, 123);
45 gen.Emit(OpCodes.Stfld, field);
46
47 // 来看看local1里面是什么
48 gen.Emit(OpCodes.Ldstr, "local1 address=");
49 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
50 gen.Emit(OpCodes.Ldloca, local1);
51 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
52 gen.Emit(OpCodes.Ldstr, "local1=");
53 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
54 gen.Emit(OpCodes.Ldloc, local1);
55 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
56 // 来看看local2里面是什么
57 gen.Emit(OpCodes.Ldstr, "local2 address=");
58 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
59 gen.Emit(OpCodes.Ldloca, local2);
60 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
61 gen.Emit(OpCodes.Ldstr, "local2=");
62 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
63 gen.Emit(OpCodes.Ldloc, local2);
64 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
65 // 来看看local3里面是什么
66 gen.Emit(OpCodes.Ldstr, "local3 address=");
67 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
68 gen.Emit(OpCodes.Ldloca, local3);
69 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
70 gen.Emit(OpCodes.Ldstr, "local3=");
71 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
72 gen.Emit(OpCodes.Ldloc, local3);
73 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
74
75 // 用local2的地址作为对象地址,看看字段里面是什么
76 gen.Emit(OpCodes.Ldstr, "Field address=");
77 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
78 gen.Emit(OpCodes.Ldloca, local2);
79 gen.Emit(OpCodes.Ldflda, field);
80 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
81 gen.Emit(OpCodes.Ldstr, "Field =");
82 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
83 gen.Emit(OpCodes.Ldloca, local2);
84 gen.Emit(OpCodes.Ldfld, field);
85 gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
86
87 gen.Emit(OpCodes.Ret);
88
89 return result;
90 }
91
乍一看,代码有些长,下面仔细分析一下:
1、Foo方法,作用是为Emit做准备,并且测试,调用"test"方法
2、Bar方法,Emit一个叫"Foo"的引用类型的类,带有一个"Bar"的字段,并且有一个实例构造器和一个"test"方法
3、在实例构造器中,对"Bar"字段赋初始值0
4、在"test"方法中,用local2的地址作为对象的地址,Call实例构造器(注意不能用newobj),这样的效果等于初始化对象的初始值(也就是说,Call实例构造器后,"Bar"字段的值就是构造器中赋的初始值)
5、再次用local2的地址作为对象的地址,给"Bar"字段赋数字123(注意,只有这里出现了123这个数字)
6、从控制台分别输出local1、local2、local3,以及用local2的地址作为对象的地址的"Bar"字段的内存地址和他们的值
代码的作用就是这些,看一下运行的结果:
local1 address=1240840
local1=123
local2 address=1240836
local2=0
local3 address=1240832
local3=0
Field address=1240840
Field =123
local1=123
local2 address=1240836
local2=0
local3 address=1240832
local3=0
Field address=1240840
Field =123
运行结果中,可以意外的发现,local1也是123,而且local1的地址和"Bar"字段的地址一致。
当然这个值并不固定,而且local1、local2、local3的地址并不一定是递减的,也有可能是递增的,或者其它情况(虽然还没遇到,但是也不排除有分散的可能性)。
在递减的情况下,local1与以local2的地址作为对象地址的"Bar"字段的地址是一致的,也就是说,以local2的地址作为对象地址的情况下,"Bar"字段并不是在堆上,而是在栈上。对local1的任何改动可以影响"Bar"字段,反过来,对"Bar"字段的任何改动也可以影响local1,因为本质上它们就是同一个地址。
同样,以local2的地址作为对象地址的这个虚假引用类型的"对象",也是存在于栈上的,是从local2地址开始的,一片隶属于栈的内存。
最后,需要注意的一点是,这个方法有种钻运行时漏洞的感觉,在x86 .net 2.0的环境上测试通过,不保证其它运行时也能正常运行。