16
\$\begingroup\$

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;
add r19, r18;

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
    add r24, r18
    adc r25, r19
    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
add r24, r25
std Y+2, r24

When is there std instruction?

\$\endgroup\$
19
\$\begingroup\$

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

Long answer:

    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
    adc r25, r19

Add (r18,r19) to (r24,r25), including the carry on the second addition

    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.

\$\endgroup\$
  • \$\begingroup\$ Thank you! It's more clear now! Please find my edit in the question. \$\endgroup\$ – DarkCoffee Oct 28 '13 at 13:56
  • 1
    \$\begingroup\$ See my edit. Try -O2 as well. Maybe -O3, although that can produce broken code. \$\endgroup\$ – pjc50 Oct 28 '13 at 14:06
  • 3
    \$\begingroup\$ 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. \$\endgroup\$ – John U Oct 28 '13 at 15:22
  • \$\begingroup\$ 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. \$\endgroup\$ – MSalters Oct 28 '13 at 16:58
  • \$\begingroup\$ @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) \$\endgroup\$ – marcelm Jan 10 at 22:03
4
\$\begingroup\$

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 */
\$\endgroup\$
  • 3
    \$\begingroup\$ Good idea; uint8_t and friends are now part of the standard: stackoverflow.com/questions/16937459/… \$\endgroup\$ – pjc50 Oct 28 '13 at 15:35
  • \$\begingroup\$ How handy! We kinda inherited those with a project which was C89, so it's good to know there's an official version. \$\endgroup\$ – John U Oct 28 '13 at 16:50
2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Assembly is easier to read than C? \$\endgroup\$ – dext0rb Oct 28 '13 at 15:34
  • \$\begingroup\$ @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. \$\endgroup\$ – johnfound Oct 28 '13 at 15:37
  • \$\begingroup\$ 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. \$\endgroup\$ – soandos Oct 28 '13 at 15:55
  • \$\begingroup\$ @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. \$\endgroup\$ – johnfound Oct 28 '13 at 16:43
  • \$\begingroup\$ Of course, the guy who says "I am not so good in C" will be proclaiming the virtues of pure assembly. :D \$\endgroup\$ – dext0rb Oct 28 '13 at 17:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.