我遇到了这段 C# 代码,它应该验证在早期阶段完成的内存分配。
for (int i = 0; i < Size; i++)
{
var b = *(BaseAddress + i); // type of BaseAddress is byte*
*(BaseAddress + i) = b;
}
在我看来,所有代码所做的就是将一个字节从原始内存复制到临时变量 b,然后将其写回原始内存。
从写入到内存位置的读取是否使内存有效且可以安全访问? 是否存在这样的情况,这实际上会捕获内存中的损坏?
最佳答案
它实际上验证了从BaseAddress
开始的内存最多 BaseAddress+(sizeof(ElementType))*(Size-1)
可以读取和写入。
例如,假设 BaseAddress 的内存有 protection attributes PAGE_EXECUTE_READ
.
在这种情况下读取不会失败,而写入它会导致访问冲突(据我所知)。
但是正如已经指出的那样,我们只能猜测为什么有人需要它。
理想情况下,它不应该是必需的,假设应用程序的托管和非托管部分都正确地实现了它们之间的契约。而且,显然,假设契约是合理的(例如模块 A promise 不分配无缘无故不可读或不可写的缓冲内存)。
是否优化为空操作?
正如@AndrewMedico 所指出的
what will happen assume the compiler doesn't just optimize it out (the loop is logically a no-op)
我怀疑所有的 C\C++ 编译器都会优化类似的代码,但这里我们谈论的是 C#,所以有两件事需要考虑
- 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
这不是空操作
- 但是还有一个JIT 编译器。对于那个我不能保证,所以 @AndrewMedico 可能是对的 如果不是现在,那么对于某些假设的 future JIT 编译器实现。
但现在我已经在我自己的机器上使用 simplest available method 检查了 Release模式反汇编。并且似乎打开了优化
对于下面的代码:
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、Windows 8.1 上的 .NET 4.6.1
关于c# - 读取和写入内存地址以验证不安全代码中的内存分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36677120/