# From C to Assembly

Suppose that we have the following piece of C code for an avr-8bit:

int v1=1;
int v2=2;
v2=v2+v1;


I expected the following disassemble

ldi r18, 1;
ldi r19, 2;


but after I ran:

avr-gcc -mmcu=atmega2560 Test.c -o Test.elf


and

avr-objdump -S Test.elf > Test.lss


I got the following disassemble

    ldi r24, 0x01   ; 1
ldi r25, 0x00   ; 0
std Y+2, r25    ; 0x02
std Y+1, r24    ; 0x01
ldi r24, 0x02   ; 2
ldi r25, 0x00   ; 0
std Y+4, r25    ; 0x04
std Y+3, r24    ; 0x03
ldd r18, Y+3    ; 0x03
ldd r19, Y+4    ; 0x04
ldd r24, Y+1    ; 0x01
ldd r25, Y+2    ; 0x02
std Y+4, r25    ; 0x04
std Y+3, r24    ; 0x03


is there anyone that can help me to understand the result of the disassembler?

Edit: Using char the assembly become:

ldi r24, 0x01
std Y+1, r24
ldi r24, 0x02
std Y+2, r24
ldd r25, Y+2
ldd r24, Y+1
std Y+2, r24


When is there std instruction?

Short answer: your registers are 8-bit and your values are 16-bit. It's therefore handling them in two pieces.

    ldi r24, 0x01   ; 1
ldi r25, 0x00   ; 0


Store the 16-bit value 1 in 8-bit registers r24,r25.

    std Y+2, r25    ; 0x02
std Y+1, r24    ; 0x01


Store it at stack locations Y+1, Y+2.

    ldi r24, 0x02   ; 2
ldi r25, 0x00   ; 0


Store the 16-bit value 2 in 8-bit registers r24,r25.

    std Y+4, r25    ; 0x04
std Y+3, r24    ; 0x03


Store it at stack locations Y+3, Y+4.

    ldd r18, Y+3    ; 0x03
ldd r19, Y+4    ; 0x04
ldd r24, Y+1    ; 0x01
ldd r25, Y+2    ; 0x02


Copy them back from the stack to (r18,r19) and (r24,r25)

    add r24, r18


    std Y+4, r25    ; 0x04
std Y+3, r24    ; 0x03


Store it back on the stack.

To get your original assembly, try two things:

• use "char" variables
• use "-O2" compiler option

Edit: the reason the compiler stores variables to the stack rather than keeping them in registers is because they're stored with the default "auto" storage type. It may optimise them into registers but it doesn't have to, even if you declare them with "register" storage class.

While this isn't a strict requirement of the language it's normal compiler behaviour. If at some point you take the address of v1 then it must be assigned a storage location, and saved back there whenever the value of "v1" changes. So to save the bookkeeping of whether or not v1 should be stored in a register or on the stack, it keeps it on the stack and treats each line of code separately.

• Thank you! It's more clear now! Please find my edit in the question. Commented Oct 28, 2013 at 13:56
• See my edit. Try -O2 as well. Maybe -O3, although that can produce broken code. Commented Oct 28, 2013 at 14:06
• A lot of embedded code I work with defines extra types which are specific to their size, such as "uint8, uint16, uint32" for the unsigned ints, for example. That way you always know exactly which sort of variable you're dealing with. Especially in small embedded, signed, float, "int" of undefined size/signedness will all cost you CPU cycles at best and cause serious bugs at worst. Commented Oct 28, 2013 at 15:22
• Real compilers stopped behaving like this about 10-15 years ago. The register allocation problem is mostly solved and compilers are damned good at it. They know exactly when a variable must be on the stack, and when it can be in a register, whether it's worth the effort to move it, and when to do so. The bookkeeping is done at compile time, and compilers themselves have gigabytes of memory. The big exception is debug mode, for obvious reasons, but then everything is on the stack. Commented Oct 28, 2013 at 16:58
• @pjc50 -O3 can produce broken code? [citation needed] (and no, C code that invokes Undefined Behaviour and then breaks with some optimization setting doesn't count) Commented Jan 10, 2019 at 22:03

As I found some example code I'll make my comment an answer - others have already explained the problem.

A lot of embedded code I work with defines extra types which are specific to their size, such as "uint8, uint16, uint32" for the unsigned ints, for example. That way you always know exactly which sort of variable you're dealing with. Especially in small embedded, signed, float, "int" of undefined size/signedness will all cost you CPU cycles at best and cause serious bugs at worst.

Here's our current #defines:

/*
* Example - the basic data types from our embedded code
*/
typedef unsigned char       uint8;  /*  8 bits */
typedef unsigned short int  uint16; /* 16 bits */
typedef unsigned long int   uint32; /* 32 bits */

typedef char                int8;   /*  8 bits */
typedef short int           int16;  /* 16 bits */
typedef int                 int32;  /* 32 bits */

typedef volatile int8       vint8;  /*  8 bits */
typedef volatile int16      vint16; /* 16 bits */
typedef volatile int32      vint32; /* 32 bits */

typedef volatile uint8      vuint8;  /*  8 bits */
typedef volatile uint16     vuint16; /* 16 bits */
typedef volatile uint32     vuint32; /* 32 bits */

• Good idea; uint8_t and friends are now part of the standard: stackoverflow.com/questions/16937459/… Commented Oct 28, 2013 at 15:35
• How handy! We kinda inherited those with a project which was C89, so it's good to know there's an official version. Commented Oct 28, 2013 at 16:50

Your C code uses 16bit integer variables (int). The compiler can't read your mind, so it compiles exactly what is in the source file. So, if you want 8bit variables, you have to use respective type.

As a result you still will get storing the values in the memory (although more simple). I am not so good in C, but IMHO, there is some options to assign the variable to some register, if you want some variables to be in the registers instead of RAM. Something like:

register unsigned char VARNAME asm("r3");


Note that not all registers are available for such tricks.

So, the conclusion? Write your programs in assembly. They will always be smaller, faster and easy for reading/supporting.

• Assembly is easier to read than C? Commented Oct 28, 2013 at 15:34
• @dext0rb - Yes. Of course if you know both well enough. If you know only C, then assembly and any other languages will be hard to read. Commented Oct 28, 2013 at 15:37
• I do have to disagree with the last point, programs written in assembler are much harder to read. Just compare the source code given above. The C code is much much clearer and shorter, as well as its intention. This difference only grows as structs are used. Commented Oct 28, 2013 at 15:55
• @soandos - C code is shorter, yes. Clearer? I am not sure. If it were so, the question above wouldn't have to be asked at all. Actually the price of the "shortness" is the "blurriness" of the details. Commented Oct 28, 2013 at 16:43
• Of course, the guy who says "I am not so good in C" will be proclaiming the virtues of pure assembly. :D Commented Oct 28, 2013 at 17:05