As others have pointed out, LEA (load effective address) is often used as a "trick" to do certain computations, but that's not its primary purpose. The x86 instruction set was designed to support high-level languages like Pascal and C, where arrays—especially arrays of ints or small structs—are common. Consider, for example, a struct representing (x, y) coordinates:
struct Point
{
int xcoord;
int ycoord;
};
Now imagine a statement like:
int y = points[i].ycoord;
where `points[]` is an array of `Point`. Assuming the base of the array is already in `EBX`, and variable `i` is in `EAX`, and `xcoord` and `ycoord` are each 32 bits (so `ycoord` is at offset 4 bytes in the struct), this statement can be compiled to:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
which will land `y` in `EDX`. The scale factor of 8 is because each `Point` is 8 bytes in size. Now consider the same expression used with the "address of" operator &:
int *p = &points[i].ycoord;
In this case, you don't want the value of `ycoord`, but its address. That's where `LEA` (load effective address) comes in. Instead of a `MOV`, the compiler can generate
LEA ESI, [EBX + 8*EAX + 4]
which will load the address in `ESI`.