34

C# 没有公开哪些 IL 指令?

我指的是 sizeof 和 cpblk 之类的指令——没有执行这些指令的类或命令(C# 中的 sizeof 是在编译时计算的,而不是在运行时 AFAIK)。

其他的?

编辑:我问这个的原因(希望这会让我的问题更有效)是因为我正在开发一个小型库,它将提供这些指令的功能。sizeof 和 cpblk 已经实现 - 在继续之前,我想知道我可能错过的其他内容。

EDIT2:使用 Eric 的回答,我编制了一份说明清单:

  • 休息
  • 跳转
  • 愈伤组织
  • 对象
  • 有限的
  • 前缀[1-7]
  • 前缀引用
  • 末端过滤器
  • 未对齐
  • 尾声
  • cpblk
  • 初始化块

还有许多其他指令未包含在列表中,我将它们分开是因为它们基本上是其他指令的快捷方式(压缩以节省时间和空间):

  • Ldarg[0-3]
  • Ldloc[0-3]
  • 斯特洛克[0-3]
  • Ldc_[I4_[M1/S/0-8]/I8/R4/R8]
  • Ldind_[I1/U1/I2/U2/I4/U4/I8/R4/R8]
  • Stind_[I1/I2/I4/I8/R4/R8]
  • 转换_[I1/I2/I4/I8/R4/R8/U4/U8/U2/U1]
  • Conv_Ovf_[I1/I2/I4/I8/U1/U2/U4/U8]
  • Conv_Ovf_[I1/I2/I4/I8/U1/U2/U4/U8]_Un
  • Ldelem_[I1/I2/I4/I8/U1/U2/U4/R4/R8]
  • Stelem_[I1/I2/I4/I8/R4/R8]
4

4 回答 4

36

我指的是 sizeof 和 cpblk 之类的指令——没有执行这些指令的类或命令(C# 中的 sizeof 是在编译时计算的,而不是在运行时 AFAIK)。

这是不正确的。sizeof(int)当然,将被视为编译时常量 4,但有很多情况(全部在unsafe代码中)编译器依赖运行时来确定结构的内存大小。例如,考虑一个包含两个指针的结构。在 32 位机器上它的大小为 8,但在 64 位机器上为 16。在这些情况下,编译器将生成 sizeof 操作码。

其他的?

我没有我们不产生的所有操作码的列表——我从来没有需要建立这样的列表。但是,在我的脑海中,我可以告诉你,没有办法在 C# 中生成“间接调用”(calli)指令;我们偶尔会被要求提供该功能,因为它可以提高某些互操作场景的性能。

更新:我只是对源代码进行 grep 以生成我们肯定生成的操作码列表。他们是:

添加
add_ovf
add_ovf_un

arglist 中BEQ beq_s BGE
bge_s bge_un bge_un_s BGT bgt_s bgt_un bgt_un_s BLE ble_s ble_un ble_un_s BLT blt_s blt_un blt_un_s bne_un bne_un_s 盒 BR br_s brfalse brfalse_s brtrue brtrue_s 呼叫 callvirt castclass CEQ CGT cgt_un CLT clt_un 约束 conv_i conv_ovf_i





































conv_ovf_i_un
conv_ovf_u
conv_ovf_u_un
conv_r
conv_r_un
conv_u

div_un DUP
endfinally
initobj isinst
ldarg
ldarg_
ldarg_s
ldarga
ldarga_s
ldc_i
ldc_r
ldelem
ldelem_i
ldelem_r
ldelem_ref
ldelem_u
ldelema
ldfld
ldflda
ldftn
ldind_i
ldind_r
ldind_ref
ldind_u
ldlen
ldloc
ldloc_
ldloc_s ldloca ldloca_s
ldnull
ldobj ldsfld





ldsflda
ldstr
ldtoken
ldvirtftn
leave
leave_s
localloc
mkrefany
mul
mul_ovf
mul_ovf_un
neg
newarr
newobj
nop
not
or
pop
readonly
refanytype
refanyval
rem
rem_un
ret
rethrow
shl
shr
shr_un
sizeof
starg
starg_s
stelem
stelem_i
stelem_r loc
stelem_ref
stfld stind_s stref_ _ _ _






stsfld
sub
sub_ovf
sub_ovf_un
switch
throw
unbox_any
volatile
xor

我不能保证这就是全部,但肯定是大多数。然后,您可以将其与所有操作码的列表进行比较,看看缺少什么。

于 2011-08-18T16:50:19.880 回答
13

根据埃里克的回答,我发现了一些。我在哪里可以看到我已经指出的原因,如果没有,我可以自由推测。如果这些猜测是错误的,请随时指出。

Break

向公共语言基础结构 (CLI) 发出信号以通知调试器已触发断点。

您可以通过调用 System.Diagnostics.Debugger.Break() 来执行此操作,这似乎不直接使用该指令,而是使用 CLR 中的 BreakInternal() 方法。

CpblkCpobj

将指定数量的字节从源地址复制到目标地址。将位于对象地址的值类型(类型 &、* 或原生 int)复制到目标对象的地址(类型 &、* 或原生 int)。

我认为这些是为 C++/CLI(以前的托管 C++)添加的,但这纯粹是我的猜测。它们也可能出现在某些系统调用中,但不是由编译器正常生成,并为不安全的乐趣和游戏提供了一些范围。

Endfilter

将控制从异常的过滤子句转移回公共语言基础结构 (CLI) 异常处理程序。

C# 不支持异常过滤。VB 编译器无疑利用了这一点。

Initblk

将特定地址处的指定内存块初始化为给定大小和初始值。

我将再次推测这在不安全代码和 C++/CLI 中可能很有用

Jmp

退出当前方法并跳转到指定方法。

我推测这种蹦床可能对那些想要避免尾声的人有用。也许 DLR 使用它?

Tailcall

执行后缀方法调用指令,以便在执行实际调用指令之前删除当前方法的堆栈帧。

在别处进行了深入讨论,目前 c# 编译器不会发出此操作码

Unaligned

指示当前位于评估堆栈顶部的地址可能未与紧随其后的 ldind、stind、ldfld、stfld、ldobj、stobj、initblk 或 cpblk 指令的自然大小对齐。

C#(和 CLR)对大部分生成的代码和数据的对齐性质做出了相当多的保证。没有发出这并不奇怪,但我可以理解为什么会包含它。

Unbox

将值类型的装箱表示转换为其未装箱形式。

c# 编译器更喜欢Unbox_Any专门为此目的使用该指令。我认为,基于将其添加到 2.0 版本中的指令集,它使泛型变得可行,或者更简单。那时,在所有代码中使用它,无论是泛型还是其他,都更安全、更简单或更快(或所有这些的某种组合)。


脚注:

Prefix1、Prefix2、Prefix3、Prefix4、Prefix5、Prefix6、Prefix7、Prefixref

基础设施。这是一条保留指令。

这些不是指令本身,一些 IL 指令比其他指令长。这些可变长度的应该以本身永远不会有效的前缀开头,以使解析清晰。这些前缀操作码是为此保留的,因此它们不会在其他地方使用。毫无疑问,为 IL 序列实现基于 switch 语句的解析器的人会喜欢这些,因此他们可以捕获这些并保持状态。

于 2011-08-18T18:47:33.603 回答
4

一个有趣的例子是tail.callOpCodes.Tailcall),它可以使递归的尾调用优化成为可能。

于 2011-08-18T17:00:22.427 回答
1

.overrideIL指令(我不知道它是否正确,但它肯定不是指令是由 C# 编译器生成的,但仅限于显式接口实现的特殊情况。

能够更自由地使用它会很有趣,比如在 VB.NET 中,实现成员可以是别名,甚至可以具有与接口成员不同的访问修饰符。

于 2011-08-18T19:55:13.180 回答