It actually verifies that memory starting from BaseAddress
up to BaseAddress+(sizeof(ElementType))*(Size-1)
can be both read from and written to.
For example, assume that memory at BaseAddress has protection attributes PAGE_EXECUTE_READ
.
In such a case reading won't fail, while writing to it will cause an Access Violation (as far as I remember).
But as it has been already pointed, we can only guess why someone would need it.
Ideally, it should not be required, assuming that both managed and unmanaged parts of the application correctly implement the contract between them. And, obviously, assuming that the contract is reasonable (like module A promises not to allocate buffer memory that can be for no reason either non-readable or non-writeable).
Optimized out as no-op or not?
As pointed by @AndrewMedico
what will happen assume the compiler doesn't just optimize it out (the
loop is logically a no-op)
I doubt that all C\C++ compilers will optimize the analogous code, but here are we talking about C#, so there are two things to consider
- C# compiler
Assuming the following code
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);
}
And the test method will become:
.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
That is not a no-op
- But there is also a JIT Compiler. For that one I can't vouch, so @AndrewMedico
may be right if not now, then for some hypothetical future JIT compiler implementation.
But for now I have checked release mode disassembly on my own machine, using the simplest available method and with optimizations seemingly turned on

For the following code:
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;
}
}
We have
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
So it is not optimized, but if I remove Console.WriteLine, I won't be able to hit the breakpoint in Test
. At first I though that it got optimized out when the method does only the memory juggling, but it actually only got inlined in the 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
So, TLDR: It does what I have told it does. At least on .NET 4.6.1 on VS2015, Windows 8.1