从C到汇编


16

假设对于avr-8bit,我们具有以下C代码:

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

我期望以下拆卸

ldi r18, 1;
ldi r19, 2;
add r19, r18;

但是我跑了之后:

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

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

我得到以下拆卸

    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

有没有人可以帮助我了解反汇编程序的结果?

编辑:使用char程序集成为:

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

什么时候有std指令?

Answers:


20

简短的答案:您的寄存器是8位,而值是16位。因此,它分为两部分进行处理。

长答案:

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

将16位值1存储在8位寄存器r24,r25中。

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

将其存储在堆栈位置Y + 1,Y + 2。

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

将16位值2存储在8位寄存器r24,r25中。

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

将其存储在堆栈位置Y + 3,Y + 4。

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

将它们从堆栈复制回(r18,r19)和(r24,r25)

    add r24, r18
    adc r25, r19

将(r18,r19)加到(r24,r25),包括进行第二次加法

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

将其存储回堆栈中。

要获得原始程序集,请尝试两件事:

  • 使用“ char”变量
  • 使用“ -O2”编译器选项

编辑:编译器之所以将变量存储到堆​​栈而不是将其保留在寄存器中,是因为它们以默认的“自动”存储类型存储。它可能它们优化为寄存器,但是不必这样做,即使您使用“寄存器”存储类声明它们也是如此。

虽然这不是对该语言的严格要求,但它是正常的编译器行为。如果在某个时候使用了v1的地址,则必须为其分配一个存储位置,并在“ v1”的值更改时将其保存回该位置。因此,为了保存v1是否应存储在寄存器中或堆栈中的记帐,它将v1保留在堆栈中并分别处理每一行代码。


谢谢!现在更清楚了!请在问题中找到我的编辑。
DarkCoffee 2013年

1
看到我的编辑。也尝试-O2。可能是-O3,尽管这可能会产生损坏的代码。
2013年

3
我使用的许多嵌入式代码都定义了特定于其大小的其他类型,例如,用于无符号int的“ uint8,uint16,uint32”。这样,您始终可以确切知道要处理的变量类型。特别是在小的嵌入式,带符号的,浮点型的,不确定大小/符号的“ int”将最多花费您的CPU周期,而最坏的情况下会导致严重的错误。
John U

大约10到15年前,真正的编译器就停止了这种行为。寄存器分配问题大部分已经解决,编译器对此非常擅长。他们确切地知道什么时候变量必须在堆栈上,什么时候可以在寄存器中,移动它是否值得,以及何时进行。记账是在编译时完成的,并且编译器本身具有千兆字节的内存。出于明显的原因,最大的例外是调试模式,但是所有内容都在堆栈中。
MSalters 2013年

@ pjc50 -O3会产生残破的代码吗?[需要引用](并且不,调用Undefined Behavior并随后因某些优化设置而中断的C代码不计算在内)
marcelm

4

当我找到一些示例代码时,我将以注释作为答案-其他人已经解释了该问题。

我使用的许多嵌入式代码都定义了特定于其大小的其他类型,例如,用于无符号int的“ uint8,uint16,uint32”。这样,您始终可以确切知道要处理的变量类型。特别是在小的嵌入式,带符号的,浮点型的,不确定大小/符号的“ int”将最多花费您的CPU周期,而最坏的情况下会导致严重的错误。

这是我们当前的#define:

/*
 * 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 */

3
好主意; uint8_t和朋友现在是标准的一部分:stackoverflow.com/questions/16937459/...
pjc50

多么方便!我们有点继承了C89项目,所以很高兴知道有正式版本。
约翰·U

2

您的C代码使用16位整数变量(int)。编译器无法理解您的想法,因此它会完全编译源文件中的内容。因此,如果要使用8位变量,则必须使用相应的类型。

结果,您仍然可以将这些值存储在内存中(尽管更简单)。我的C语言不太好,但是恕我直言,如果您希望某些变量位于寄存器而不是RAM中,则可以使用一些选项将变量分配给某些寄存器。就像是:

register unsigned char VARNAME asm("r3");

请注意,并非所有寄存器都可用于此类技巧。

那么,结论呢?汇编编写程序。它们将始终更小,更快并且易于阅读/支持。


汇编比C更容易阅读?
dext0rb 2013年

@ dext0rb-是的。当然,如果您对两者都足够了解。如果您只知道C,那么汇编语言和任何其他语言都将很难阅读。
johnfound

我确实不同意最后一点,用汇编器编写的程序很难阅读。只需比较上面给出的源代码即可。C代码及其意图更加清晰明了。这种差异只会随着使用结构而增长。
soandos

@soandos-C代码更短,是的。更清晰?我不确定。如果是这样的话,就根本不需要问上述问题。实际上,“短缺”的价格就是细节的“模糊性”。
johnfound

当然,说“我的C语言不太好”的人会宣称纯汇编的优点。:D
dext0rb
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.