读取和写入内存地址以验证不安全代码中的内存分配

Reading and writing to memory addresses to verify memory allocation in unsafe code

我发现了这段 C# 代码,它应该验证在早期阶段完成的内存分配。

for (int i = 0; i < Size; i++)
{
    var b = *(BaseAddress + i); // type of BaseAddress is byte*
    *(BaseAddress + i) = b;
}

在我看来,所有代码所做的就是将一个字节从原始内存复制到临时变量 b,然后将其写回原始内存。

从写入到内存位置的读取是否使内存有效且可以安全访问? 是否存在这实际上会捕获内存损坏的情况?

它实际上验证了从BaseAddressBaseAddress+(sizeof(ElementType))*(Size-1)的内存可以读取和写入[=77] =].

例如,假设 BaseAddress 的内存有 protection attributes PAGE_EXECUTE_READ.

在这种情况下读取不会失败,而写入它会导致访问冲突(据我所知).


但是由于已经指出,我们只能猜测为什么有人需要它。

理想情况下,假设应用程序的托管部分和非托管部分都正确地实现了它们之间的契约,则不需要。而且,显然,假设契约是合理的(比如模块 A 承诺不分配无缘无故不可读或不可写的缓冲内存)。


是否优化为空操作?

正如@AndrewMedico 所指出的那样

what will happen assume the compiler doesn't just optimize it out (the loop is logically a no-op)

我怀疑所有 C\C++ 编译器都会优化类似的代码,但这里我们谈论的是 C#,所以有两件事需要考虑

  1. C# 编译器

假设以下代码

private unsafe static void Test(Int32* ptr, Int32 size)
{
    for (int i = 0; i < size; i++)
    {
        var a = * (ptr + i);
        *(ptr + i) = a;
    }
}

...

var array = new Int32[] { 1, 2, 3 };

fixed (Int32* ptr = array)
{
    Test(ptr, array.Length);
}

测试方法将变为:

.method private hidebysig static void  Test(int32* ptr,
                                            int32 size) cil managed
{
  // Code size       29 (0x1d)
  .maxstack  3
  .locals init ([0] int32 i,
           [1] int32 a)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  br.s       IL_0018
  IL_0004:  ldarg.0
  IL_0005:  ldloc.0
  IL_0006:  conv.i
  IL_0007:  ldc.i4.4
  IL_0008:  mul
  IL_0009:  add
  IL_000a:  ldind.i4
  IL_000b:  stloc.1
  IL_000c:  ldarg.0
  IL_000d:  ldloc.0
  IL_000e:  conv.i
  IL_000f:  ldc.i4.4
  IL_0010:  mul
  IL_0011:  add
  IL_0012:  ldloc.1
  IL_0013:  stind.i4
  IL_0014:  ldloc.0
  IL_0015:  ldc.i4.1
  IL_0016:  add
  IL_0017:  stloc.0
  IL_0018:  ldloc.0
  IL_0019:  ldarg.1
  IL_001a:  blt.s      IL_0004
  IL_001c:  ret
} // end of method Program::Test

那是不是空操作

  1. 但是还有一个JIT编译器。对于那个我不能保证,所以 @AndrewMedico 可能是对的 如果不是现在,那么对于某些假设的未来 JIT 编译器实现。

但现在我已经检查了我自己机器上的发布模式反汇编,使用 simplest available method 并且似乎打开了优化

对于以下代码:

private unsafe static void Test(Int32* ptr, Int32 size)
{
    Console.WriteLine("test");

    for (int i = 0; i < size; i++)
    {
        var a = * (ptr + i);
        *(ptr + i) = a;
    }
}

我们有

        Console.WriteLine("test");
00742E70  push        ebp  
00742E71  mov         ebp,esp  
00742E73  push        edi  
00742E74  push        esi  
00742E75  mov         esi,ecx  
00742E77  mov         edi,edx  
00742E79  mov         ecx,dword ptr ds:[27B2310h]  
00742E7F  call        65BDD4B0  

        for (int i = 0; i < size; i++)
00742E84  xor         edx,edx  
        for (int i = 0; i < size; i++)
00742E86  test        edi,edi  
00742E88  jle         00742E95  
        {
            var a = * (ptr + i);
00742E8A  mov         ecx,dword ptr [esi+edx*4]  
            *(ptr + i) = a;
00742E8D  mov         dword ptr [esi+edx*4],ecx  
        for (int i = 0; i < size; i++)
00742E90  inc         edx  
00742E91  cmp         edx,edi  
00742E93  jl          00742E8A  
00742E95  pop         esi  
00742E96  pop         edi  
00742E97  pop         ebp  
00742E98  ret  

所以它没有优化,但是如果我删除Console.WriteLine,我将无法在Test中设置断点。起初我虽然它 优化了 当该方法只做内存杂耍时,但它实际上只在 Main:

中内联
        Test(ptr, array.Length);
00482DFD  mov         ecx,dword ptr [ebp-20h]  
00482E00  mov         edi,dword ptr [edx+4]  
00482E03  xor         esi,esi  
00482E05  test        edi,edi  
00482E07  jle         00482E14  
00482E09  mov         edx,dword ptr [ecx+esi*4]  
00482E0C  mov         dword ptr [ecx+esi*4],edx  
00482E0F  inc         esi  
00482E10  cmp         esi,edi  
00482E12  jl          00482E09  
00482E14  mov         dword ptr [ebp-18h],0  
00482E1B  mov         dword ptr [ebp-14h],0FCh  
00482E22  push        482E36h  
00482E27  jmp         00482E2E  
00482E29  call        66DBBDC2  
00482E2E  xor         edx,edx  
00482E30  mov         dword ptr [ebp-20h],edx  
00482E33  pop         eax  
00482E34  jmp         eax  
00482E36  mov         dword ptr [ebp-14h],0  
00482E3D  jmp         00482E4B  

所以,TLDR:它按照我说的去做。至少在 VS2015 上的 .NET 4.6.1 上,Windows 8.1