Start by doing a line-by-line analysis to figure out what the code does.
push 5
This instruction pushes the constant value "5" onto the stack. Why? Well, because...
call ??_U@YAPAXI@Z ; operator new[](uint)
This instruction calls operator new[], which takes a single uint parameter. That parameter is evidently passed on the stack in whatever calling convention is used by this code. So, clearly, so far we have called operator new[] to allocate an array that is 5 bytes in size.
In C++, that would be written as:
BYTE* eax = new BYTE[5];
The call to operator new[] returns its value (the pointer to the beginning of the allocated memory block) in the EAX register. This is a general rule for all x86 calling conventions—functions always return their result in the EAX register.
mov [ebp+esi*4+var_14], eax
The above code stores (moves) the resulting pointer (the one that is returned in EAX) into the memory location addressed by EBP + (ESI * 4) + var_14. In other words, it scales the value in the ESI register by 4 (presumably, the size of a uint), adds the offset from the EBP register, and then adds the offset of the constant var_14.
This is roughly equivalent to the following pseudo-C++ code:
void* address = (EBP + (ESI * 4) + var_14);
*address = eax;
add esp, 4
This cleans the stack, effectively undoing the initial push 5 instruction.
push pushed a 32-bit (4 byte) value onto the stack, which decremented the stack pointer, which is maintained in the ESP register (note that the stack grows downward on x86). This add instruction increments the stack pointer (again, the ESP register) by 4 bytes.
Balancing the stack in this way is an optimization. You could equivalently have written pop eax, but that would have the added side-effect of clobbering the value in the EAX register.
There is no direct C++ equivalent of this instruction, as it's just doing bookkeeping work that would normally be hidden from you by a high-level language.
inc esi
This increments the value of the ESI register by 1. It is equivalent to:
esi += 1;
mov byte ptr [eax+4], 0
This stores the constant value 0 in the BYTE-sized memory block at EAX + 4. It corresponds to the following pseudo-C++:
BYTE* ptr = (eax + 4);
*ptr = 0;
cmp esi, 4
This compares the value of the ESI register to the constant value 4. The CMP instruction actually sets the flags as if a subtraction was done.
Therefore, the subsequent instruction:
jl short loc_1C1D40
conditionally jumps if the value of the ESI register is less than 4.
The compare-and-jump is a hallmark of a looping construct in a higher-level language, like a for or while loop.
Putting it all together, you have something like:
void Foo(char** var_14)
{
for (int esi = 0; esi < 4; ++esi)
{
var_14[esi] = new char[5];
var_14[esi][4] = 0;
}
}
That's not exactly right, of course. Reconstituting the original C or C++ code from the compiled assembly is a lot like reconstituting the original cow from a ground-beef patty.
But it's pretty good. In fact, if you compile the above function in MSVC, optimizing for speed and targeting 32-bit x86, you get the following assembly generated:
void Foo(char**) PROC
push esi
push edi
mov edi, DWORD PTR _var_14$[esp+4]
xor esi, esi
$LL4@Foo:
push 5
call void * operator new[](unsigned int) ; operator new[]
mov DWORD PTR [edi+esi*4], eax
add esp, 4
inc esi
mov BYTE PTR [eax+4], 0
cmp esi, 4
jl SHORT $LL4@Foo
pop edi
pop esi
ret 0
void Foo(char**) ENDP
That's pretty much exactly the same as what you had in the question, assuming you ignore the prologue and epilogue (which you didn't show in the question anyway).
The major difference is that the compiler is applying a fairly obvious loop-hoisting optimization to the MOV instruction. Instead of the original code's:
mov [ebp + esi * 4 + var_14], eax
it instead pre-computes esp + var_14 in the prologue, caching the result in the free EDI register:
mov edi, DWORD PTR _var_14$[esp + 4]
allowing the loading instruction inside the loop to be simply:
mov DWORD PTR [edi + esi * 4], eax
I have no idea why your code doesn't do this, or why it's using EBP to hold the offset.
add esp,4is balances thepushinside the loop. Maybe more efficient to push once outside the loop, and thenmov dword [esp], 5/call, but worse for code-size and not worth it for a tiny loop. Pretty obvious this is a loop that allocates arrays, storing the pointers in another array. (And storing a0into each array). - Peter Cordes