How to "translate" to C the assembly command mov
Overview

EESE, when programming is at question, is more about embedded programming. So the following is in-scope for the site.

For others (and I can't tell if this helps you, too, but you may already know all this), a C compiler will (it doesn't have to, as there are often other mechanisms that a creative compiler author may also apply while staying within the required machine model for C) place parameters onto the stack before calling a function. In order to allow for local (automatic) variables for the function when compiling it, the C compiler usually reserves space for these also on the stack.

The C compiler generating code for the function call, running in a completely separate compilation step, may only have access to the declaration for the function. The same C compiler generating code for the function itself, again running in a completely separate compilation step -- perhaps on a completely different day of the week, will not know anything about whether or not anyone is actually calling the function. All it knows is that it must compile this function, now.

So, there is an agreement made. The C compiler compiling the function call is responsible for handling the parameters to the function in an agreed way -- usually by pushing them onto the stack in a certain order and using agreed methods for the various types of values and pointers. And the C compiler compiling the function itself is responsible for knowing the size of the automatic variable space that the function requires and for allocating that space whenever the function is invoked. This is also usually done by using the stack.

The C compiler also has agreements about how function values are returned, which registers in the CPU must be preserved by the called function and which can be simply used as "scratch" registers, and some other agreements such as which registers may be used for a stack pointer, activation frame pointer (I'll get to this), and any other special purposes that a C compiler author may come up with.

(Note that while I say all these things above, C compilers are also free to make more sophisticated rules, too. For example, many will reserve out a few registers in the CPU for the first couple of parameters to be passed to a function and require that those functions obey that choice.)

In order to implement all of the above in a relatively simple fashion and in a way that the C compiler can generate recursive call support, as well as making it possible for re-entrant support (at least on a single-threaded machine model), the concept of a function activation frame has long been in wide use. (From the first days, in fact, 50 years ago.)

The idea is simple and effective. (I won't provide a complete discussion of the entire scope of possibilities. Instead, I'm keeping this relatively limited.) Here's a stack diagram that is typical (an expanded and more readable diagram is available here.)



The activation frame pointer, FP, is kept unchanged throughout the duration of an activation of a function. It's a "fixed" pointer to the current activation frame of the current instantiation of the function.

All parameters (\$P_i\$) to the function are known at compile-time and will have fixed, positive offsets relative to the FP. In C, these are arranged so that \$P_1\$ is closest to the FP activation frame reference pointer and \$P_\text{N}\$ is furthest away from it. The C language designers chose this method (which, by the way, was different than most other compilers at the time) for at least two good reasons:


Other compilers required the called function to remove the parameters before returning to the caller. But this meant that the callee has to know how many parameters the caller supplied. And this meant that you could not use a variable parameter list, which concepts such as printf() required.

But by making it the caller's responsibility to remove the parameters, the C language kept the knowledge of how many parameters were passed contained within one localized place -- the caller. The caller sets up the parameters and the caller cleans up the mess, afterwards. This frees the called function of having to have separate knowledge about the number of parameters. And since C supports separate compilation very well, and since each separate C compilation unit has exact knowledge of how many parameters are being passed, it makes a great deal of sense to keep the responsibility of cleaning up that mess also within the same separate compilation phase.
Arranging the parameters to be pushed onto the stack in reverse order as they are listed in the source code, the first parameter is always in the exact same location in the activation frame. (\$P_1\$ is always closest to the FP and in a known position by the C compiler when compiling the callee function.) So the callee function always knows where to find \$P_1\$ regardless of what \$N\$ is, from the perspective of the caller. This allows the called function to consume as many parameters as required, but no more. If the caller passes more parameters, there's no problem. The called function doesn't use them and isn't confused by not knowing how many were passed. And the caller has the job of cleaning things up afterwards, so the callee function again needs no knowledge about that. This allows functions like printf() to work so well in C.


All automatic variables (\$Auto_i\$) required by the callee function are of course also known at compile-time and will have fixed, negative offsets relative to the FP.

These details allow the compiler to generate code that "just works right."

There are a few other details. There is an optional return value. This may be required in cases where the function doesn't return a simple value (or none) but instead returns a structure (by value.) The called function will use that area for returning the function value, in such cases. (But it might be a pointer to an automatic variable on the caller's activation frame. Or any of a number of other inventive ways to get this job done.)

In addition, I've included something more from the C++ world; the exception handler vector (pointer.) C doesn't use this. But early versions of C++ most certainly did in order to support "try" blocks. When a try-block is entered, this value is modified in order to point to the handler that will execute if an exception takes place. If null, then there is no active try-block at this activation level and the exception handling code uses the caller's FP stored on the stack to move backward through the activation frames to find a non-null handler for the exception. (This is called a "stack-unwind" in some literature.)

(Note that allocating automatic variable space is very simple. All the compiler has to do is generate code that subtracts a number from the stack pointer and the space has been allocated. It's that simple. It's similarly simple for the caller to clean up the pushed parameters. All it has to do is add a value to the stack pointer to remove them.)

I've included places in the above diagram where the called function is required to save preserve registers. These are registers which the caller expects to be unchanged after the call, but where the callee function may still require those registers for its own use. In this case, the called function will "preserve" these registers by first pushing them onto the stack. (These are represented in the above diagram as \$R_i\$.) The called function is required to restore these register values from the stack before returning to the caller.

I've avoided adding oddities such as the access link pointer, which is useful when the language supports lexical scopes for a function (as in, say, Pascal) that can access variables that are outside of the function body itself but within an outer function that encloses it. There are also "visibility lists" and so on. But these complexities are beyond the scope of my answer here.

Also, as a final note about the above diagram, I've also shown the possibility of additional stack usage after the current activation frame. This is on purpose. Usually, the current function instantiation is the "deepest" on the stack. So the lower addresses below the current activation frame region are most often currently "unused." However, it's possible for compilers to support iterators and these generate what amounts to a special kind of co-routine. A good example might be something like this in C:

for p in Primes(1,100) printf( "%d\n", p );


In such a case, a C compiler may create a nested sub-function to hold only the statement, "printf( "%d\n", p );", and pass a hidden pointer to it to Primes(). The function Primes() then starts up by generating its own activation frame (of course) and begins its work. But to yield the next value to the caller's "for loop", it instead actually calls the unnamed (anonymous) generated nested sub-function for which it was passed the address. But the "for loop" body's anonymous function must also have access to the activation frame of its parent function. So the point here is that things can be a little more complex, depending on what's being done. So I left the diagram  just a little more general for that reason.

With the above background out of the way (but it's appropriate that you know these things in embedded programming situations, so it is broadly germane to this group), I can now proceed to your questions.



1


  I'm not sure how to understand the assembly command mov. e.g. in the
  following section:

mov -4(%ebp),%eax      # %eax=*(%ebp-4) so eax is geting the **value** that is stored in ebp-4



The register EBP is the "extended base pointer" but for our purposes here it is the FP mentioned in the diagram I provided above. In short, it's the current activation frame pointer for the current instantiation of this function. The line of code in this case (using an AT&amp;T syntax assembler that I never use, myself) is grabbing the value of the first (and that's compiler-decided) automatic variable (which appears to be 32-bits in size) and moving that value into the EAX register. Likely, the EAX will be used in the next instruction that follows. But it might be that this is one of the last instructions in the function and that it is going to return the function value in EAX and is grabbing that return value from a local, automatic variable just before returning.



2


  in general, whenever I do:

mov (mem_address), %reg # reg = *(mem_address), so the **value** inside the adress mem is stored in the register

  
  but if I do:

mov %eax, -8(%ebp) # in C: ebp-8 = eax

  
  so if I try to translate into C then, moving data from mem to reg is
  like reg=*mem but moving data from reg to mem is just mem=reg because
  the register stores the value and not the address to the value? did I
  get it right?


You don't really get to specify registers in C. Those choices and assignments are made by the C compiler you are using. (The use of EAX is special, too, because many instructions use it by default and the compiler must also be aware of this fact when generating code.)

Sometimes, C compilers will allow you to "insert" assembly code inside of a C function. But if so, they usually disable a lot of their "optimization features" and, to be honest, in most cases this isn't particularly useful.

To write assembly code that can be called by C, you will need to carefully read the manual regarding the rules and assumptions that the C compiler imposes and uses in generating its own code from C. You will need to follow those guidelines, yourself. So you can't just write "any old assembly function" if you expect it to be called by C. You have to follow the rules of the road. Otherwise, bad things happen.

You can also write assembly code that calls C functions, too. This works. But then you have some other issues to deal with. And discussing those also goes beyond the scope of my answer here.



3


  and one last thing, is a label an address or a value? e.g.

mov label, %eax # in C: eax = *label? just like regular address in memory?

  
  thank you for your help!


In assembly code (and as I said I don't use AT&amp;T syntax, so I'm not promising to be an expert about your particular assembler tool), a label is just a symbol for something. What precisely it is will depend on the declaration (which you don't show.) It may be a constant value (say, "LABEL1 EQU 5"), in which case the assembler will hopefully treat it as an immediate constant and generate appropriate code. It may be an absolute address label, in which case the assembler will hopefully also generate appropriate code. It may be a relocatable address label, in which case the assembler should generate object code that the linker will later "fix-up" before generating the executable, after the linker does its job of positioning the relocatable segments it has to deal with. (It still may require additional modifications by the operating system, certainly if running in "real mode", as the position in memory of the final executable is determined at program load time and cannot be decided entirely by the linker. In these cases, the linker will include additional tables in the executable image so that the operating system can locate and make appropriate adjustments at load-time.)

There are also scoping rules for assembly labels and a host of other details. But the above covers it, broadly.



Final Notes

Writing a C compiler is really a lot of fun. And it's not terribly hard. If you've "been there, done that" it can be done in a few weeks' time. (So long as you don't care too much about optimization, register colorization, and so on.) Seriously, this is something every programmer should take a crack at -- at least once in their life. It is really a lot of fun. I mean it. Lexical analysis, parsing, the construction of basic blocks and expression trees (and DAGs -- directed acyclic graphs -- if you want to optimize sub-expressions within a basic block), and the generation of assembly code for an assembler is one of the few simple pleasures in life. I recommend it to everyone.

It should be one of those "bucket list" items on everyone's list. If you are a programmer and you haven't experienced the pleasure of writing a C compiler, then you simply haven't lived!

If you can think for yourself (you'll need to do so), then "A Retargetable C Compiler: Design and Implementation," is a good choice to start learning how to do it. Sure, it could be improved. But it's a pretty good start for those interested in learning how and having to start from scratch about how to proceed. And you can use your own imagination on improving the book's approach. The book keeps it pretty simple. And that is its strong point. So it's a good read for getting started.

(The several [and there's more than one] so-called "Dragon Book" by Aho, et al., is more for those dedicated in the field. Also, the book developed from a Ph.D thesis, called "Bulldog: A Compiler for VLIW Architectures," is a must-read for enthusiasts.)