使用Peverify和ILVerify验证不安全C#返回类型时出现验证错误
首先得明确一个核心点:IL验证器(Peverify/ILVerify)是为类型安全的托管代码设计的,而unsafe代码本身就是用来突破CLR的类型安全约束的——所以这类代码本质上就不符合验证器的规则,通不过验证是意料之中的事。
接下来咱们拆解你遇到的两个错误,以及背后的深层原因:
1. A::GetAnswer()的验证错误
错误信息:[IL]: Error: [C:\src\test\example.dll : A::GetAnswer()][offset 0x00000006][found address of Int32] Expected numeric type on the stack.
你的GetAnswer方法返回了局部变量fakeValue的指针,编译器生成的IL逻辑大致是:
- 将42存入局部变量
fakeValue - 通过
ldloca.s指令获取fakeValue的地址(得到byref类型的值) - 用
conv.i指令把这个地址转换为原生整数(对应C#里的int*指针) - 返回这个值
但CLR的类型安全规则严格禁止返回局部变量的地址:局部变量存储在方法的栈帧中,方法执行完毕后栈帧会被销毁,对应的内存会被回收或覆盖,返回这个地址会产生野指针,属于严重的类型安全违规。IL验证器检测到了这个违规,所以报错——它期望栈上是符合返回类型的数值类型(原生整数),但实际看到的是局部变量的byref地址,认为这个操作不符合类型安全规范。
2. A::Main()的验证错误
错误信息:[IL]: Error: [C:\src\test\example.dll : A::Main()][offset 0x00000009][found Native Int] Expected ByRef on the stack.
在Main方法中,你解引用了GetAnswer返回的指针,对应的IL逻辑是调用GetAnswer得到原生整数(指针),然后用ldind.i4指令解引用它。但在类型安全的托管代码中,解引用操作只能针对byref类型(托管引用),而不能直接针对原生指针。IL验证器遵循类型安全规则,所以当它看到栈上是原生整数(指针)时,就报错说期望ByRef类型,而不是原生指针。
额外提醒:你的代码存在未定义行为
虽然这段代码在你的测试中能编译运行并输出42,但这只是巧合——fakeValue是栈上的局部变量,GetAnswer返回后,它的内存已经不再属于有效范围,解引用这个指针可能会得到随机垃圾值,甚至导致程序崩溃,这属于C#中的未定义行为,绝对不能在生产代码中使用。
内容的提问来源于stack exchange,提问作者miniwolf

