我非常了解用于操作,生成,反编译JVM字节码(ASM,cglib,jad等)的Java工具.CLR字节码有哪些类似的工具?人们为CLR做字节码操作吗?
您是否知道有关CLR,.NET Framework和CIL的工作方式的任何好书,而不是任何特定的.NET语言?
让我们比较两段代码:
String str = null;
//Possibly do something...
str = "Test";
Console.WriteLine(str);
Run Code Online (Sandbox Code Playgroud)
和
String str;
//Possibly do something...
str = "Test";
Console.WriteLine(str);
Run Code Online (Sandbox Code Playgroud)
我一直认为这些代码是相同的.但是在我构建这些代码(检查了优化的发布模式)并比较生成的IL方法后,我注意到第一个样本中还有两个IL指令:
第一个示例代码IL:
.maxstack 1
.locals init([0] string str)
IL_0000:ldnull
IL_0001:stloc.0
IL_0002:ldstr"Test"
IL_0007:stloc.0
IL_0008:ldloc.0
IL_0009:call void [mscorlib] System.Console :: WriteLine (字符串)
IL_000e:ret
第二个示例代码IL:
.maxstack 1
.locals init([0] string str)
IL_0000:ldstr"Test"
IL_0005:stloc.0
IL_0006:ldloc.0
IL_0007:call void [mscorlib] System.Console :: WriteLine(string)
IL_000c:ret
可能这个代码是由JIT compiller优化的?那么带有null的本地bethod变量的初始化会影响性能(我知道这是非常简单的操作,但无论如何)我们应该避免它吗?先谢谢.
我想我已经知道了一堂课的答案,只想确认我的理解是正确的.假设我有一个ClassA名为的实例a.何时a.MethodA()被调用:
(1)CLR找到的类型ClassA由所述类型的指针的a在堆(类型已经装载到堆)
(2)MethodA在类型中找到,如果没有找到,则转到它的基类型,直到object上课.
也许我的理解不太准确,但我认为这是基本正确的(纠正我,如果它是错的!).这是一个简单结构的问题.
struct MyStruct
{
public void MethodA() { }
}
Run Code Online (Sandbox Code Playgroud)
我有var x = new MyStruct();,它的值在堆栈上,并且MyStruct已经加载到堆中的类型.执行时x.MethodA(),当然没有拳击.CLR如何找到MethodA并获取IL并执行/ JIT呢?我想答案可能是:(再次,如果我错了,请纠正我)
(1)我们有声明类型的x堆栈上.CLR通过堆栈上的信息找到它的类型,并MethodA在其类型中查找.- 让我们来称呼它assumptionA.
如果你告诉我我assumptionA是对的,我会很高兴的.但即使它是错的,它也说明了一个事实:CLR有一种方法可以在没有装箱的情况下找到结构类型.
现在怎么样x.ToString()还是x.GetType()?我们知道该值将被加框,然后它将像一个类一样执行.但为什么我们需要拳击呢?既然我们可以得到它的类型(假设A告诉我们),为什么不去它的基类型并找到方法(就像一个类)?为什么这里需要昂贵的箱子操作?
我正在使用System.Reflection.Emit名称空间编写一些IL机构来创建int和double使用数组.
为了创建int数组我正在使用以下代码.
LocalBuilder arr = gen.DeclareLocal(typeof(int));
gen.Emit(OpCodes.Ldc_I4_1);
gen.Emit(OpCodes.Newarr, typeof(int));
gen.Emit(OpCodes.Stloc, arr);
gen.Emit(OpCodes.Ldloc, arr);
gen.Emit(OpCodes.Ldc_I4_0);
gen.Emit(OpCodes.Ldc_I4, 500);
gen.Emit(OpCodes.Stelem_I4);
gen.Emit(OpCodes.Ldloc, arr);
gen.Emit(OpCodes.Ldc_I4_0);
gen.Emit(OpCodes.Ldelem_I4);
gen.Emit(OpCodes.Call,typeof(Console).GetMethod("WriteLine",new Type[]{typeof(int)}));
Run Code Online (Sandbox Code Playgroud)
它按预期工作,并在控制台上打印500.
我尝试创建double数组的方式如下所示.
LocalBuilder arr = gen.DeclareLocal(typeof(double));
gen.Emit(OpCodes.Ldc_I4_1);
gen.Emit(OpCodes.Newarr, typeof(double));
gen.Emit(OpCodes.Stloc, arr);
gen.Emit(OpCodes.Ldloc, arr);
gen.Emit(OpCodes.Ldc_I4_0);
gen.Emit(OpCodes.Ldc_R8, 500D);
gen.Emit(OpCodes.Stelem_R8);
gen.Emit(OpCodes.Ldloc, arr);
gen.Emit(OpCodes.Ldc_I4_0);
gen.Emit(OpCodes.Ldelem_I8);
gen.Emit(OpCodes.Call,typeof(Console).GetMethod("WriteLine",new Type[]{typeof(double)}));
Run Code Online (Sandbox Code Playgroud)
不幸的是,这不起作用,当我使用pereview它检查生成的程序集时,给了我以下错误.
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.1
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [C:\temp\Research\Research\bin\Debug\MyMod.exe : Foo::Main][offset 0x00000006][found ref array …Run Code Online (Sandbox Code Playgroud) 是否有任何文档或是否有一部分cecil源代码我可以参考,以全面了解Operandcecil将用于给定Code值的类型?例如:我可以搜集来自MethodBodyRocks该Ldloc接受一个Operand类型的VariableDefinition,但我一直无法追查下来用于其他一些指令代码.
C#有一个条件运算符和IF语句,我怀疑条件运算符只是语法糖.因此在编译时它将与IF操作相同.
然而,他们没有(见下文),他们确实有不同的IL.试图绕过它,我的假设是,这是条件运算符得到的性能优化,因为它的范围有限.
想知道我的假设是否正确,也许还有更多内容?
同样在IF的IL中,有一些关于int值的检查(L_000c,L_000d,L_000f),我无法弄清楚其含义.这使我认为这是一个更强大的解决方案,因为IF的范围更广,代价是性能.
var result = "";
if (Environment.Is64BitOperatingSystem)
{
result = "Yes";
}
else
{
result = "No";
}
Console.WriteLine(result);
Run Code Online (Sandbox Code Playgroud)
条件运算符的代码(我意识到差异,但无论我如何改变它 - 分配给变量等......它几乎没有区别)
Console.WriteLine("Is the OS x64? {0}", Environment.Is64BitOperatingSystem ? "Yes" : "No");
Run Code Online (Sandbox Code Playgroud)
IL为IF
L_0001: ldstr ""
L_0006: stloc.0
L_0007: call bool [mscorlib]System.Environment::get_Is64BitOperatingSystem()
L_000c: ldc.i4.0
L_000d: ceq
L_000f: stloc.2
L_0010: ldloc.2
L_0011: brtrue.s L_001d
L_0013: nop
L_0014: ldstr "Yes"
L_0019: stloc.0
L_001a: nop
L_001b: br.s L_0025
L_001d: nop
L_001e: ldstr "No"
L_0023: stloc.0
L_0024: nop …Run Code Online (Sandbox Code Playgroud) 在为我最喜欢的突变测试框架(NinjaTurtles)编写"Off By One"变异测试程序的过程中,我编写了以下代码,以便提供检查我的实现的正确性的机会:
public int SumTo(int max)
{
int sum = 0;
for (var i = 1; i <= max; i++)
{
sum += i;
}
return sum;
}
Run Code Online (Sandbox Code Playgroud)
现在这看起来很简单了,并没有让我觉得尝试改变IL中的所有文字整数常量会有问题.毕竟,只有3(the 0,the 1,and ++).
错误!
在第一次运行中它变得非常明显,它在这个特定的实例中永远不会起作用.为什么?因为将代码更改为
public int SumTo(int max)
{
int sum = 0;
for (var i = 0; i <= max; i++)
{
sum += i;
}
return sum;
}
Run Code Online (Sandbox Code Playgroud)
只在总和上加0(零),这显然没有效果.不同的故事,如果它是多组,但在这种情况下,它不是.
现在有一个相当简单的算法来计算整数之和
sum = max * (max + 1) / 2;
Run Code Online (Sandbox Code Playgroud)
我可以轻易地使突变失败,因为从任一常数中加1或减1会导致错误.(鉴于此max >= …
IL有一些操作参数的操作码,例如Ldarg.0,Ldarg.1等等.
我知道在call执行操作码之前,这些参数被压入堆栈,在某些情况下Ldarg.0用于获取对this(例如成员)的引用
我的问题是:在启动呼叫时存储的参数在哪里?是否可以从执行的调用中访问调用者堆栈的副本?
我在哪里可以找到有关该主题的更多信息?
更新
我知道虚拟机是抽象的,JIT编译器负责处理这些问题,但让我们想象一下IL是否被解释,就像它在.NET Micro Framework上一样
我使用protobuf-net来序列化/反序列化我的数据.
我有一些相当简单的类,所以这不是真正的问题.
据我所知,protobuf-net使用IL生成来创建序列化/反序列化代码.虽然我的模型中只有字段,但我想知道用IL写这样一个字段怎么可能?我可以清楚地看到它运作良好,但我不知道为什么......
我试图在代码中窥探它,但它有点太复杂了.
我自己尝试生成此类代码总是会导致IL验证程序错误.
il ×10
c# ×8
clr ×4
.net ×3
cil ×3
algorithm ×1
bytecode ×1
jit ×1
mono ×1
mono.cecil ×1
performance ×1
protobuf-net ×1
readonly ×1
value-type ×1