• CLR的黑暗面——栈与引用对象


        几乎所有的书或文章都说,引用类型的对象是在托管堆里面的,而值类型的对象可以在栈里也可以在堆里(例如:装箱与拆箱),但是,今天写一个推翻引用类型的对象是在托管堆里面的情况:
        首先,在这里使用了大量的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(nullnull);
    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,而且local1的地址和"Bar"字段的地址一致。
        当然这个值并不固定,而且local1、local2、local3的地址并不一定是递减的,也有可能是递增的,或者其它情况(虽然还没遇到,但是也不排除有分散的可能性)。
        在递减的情况下,local1与以local2的地址作为对象地址的"Bar"字段的地址是一致的,也就是说,以local2的地址作为对象地址的情况下,"Bar"字段并不是在堆上,而是在栈上。对local1的任何改动可以影响"Bar"字段,反过来,对"Bar"字段的任何改动也可以影响local1,因为本质上它们就是同一个地址。
        同样,以local2的地址作为对象地址的这个虚假引用类型的"对象",也是存在于栈上的,是从local2地址开始的,一片隶属于栈的内存。
        最后,需要注意的一点是,这个方法有种钻运行时漏洞的感觉,在x86 .net 2.0的环境上测试通过,不保证其它运行时也能正常运行。
  • 相关阅读:
    STL容器 erase的使用陷井
    转:VC++线程同步-事件对象
    VC线程同步方法
    C/C++四种退出线程的方法
    rabbitMQ 常用命令
    Spring @Configuration
    Spring RabbitMQ 延迟队列
    rabbitmq web管理界面 用户管理
    Linux下tar.gz 安装
    Linux下RPM软件包的安装及卸载
  • 原文地址:https://www.cnblogs.com/vwxyzh/p/1093782.html
Copyright © 2020-2023  润新知