Von C bis zur Montage

16

Angenommen, wir haben den folgenden C-Code für ein avr-8bit:

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

Ich habe folgendes Zerlegen erwartet

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

aber nachdem ich gelaufen bin:

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

und

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

Ich habe das folgende zerlegen

    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

Gibt es jemanden, der mir helfen kann, das Ergebnis des Disassemblers zu verstehen?

Edit: Mit char wird die Assembly:

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

Wann gibt es std Unterricht?

DarkCoffee
quelle

Antworten:

20

Kurze Antwort: Ihre Register sind 8-Bit und Ihre Werte sind 16-Bit. Es geht darum, sie in zwei Teilen zu handhaben.

Lange Antwort:

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

Speichern Sie den 16-Bit-Wert 1 in den 8-Bit-Registern r24, r25.

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

Lagern Sie es an den Stapelplätzen Y + 1, Y + 2.

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

Speichern Sie den 16-Bit-Wert 2 in den 8-Bit-Registern r24, r25.

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

Lagern Sie es an den Stapelplätzen Y + 3, Y + 4.

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

Kopiere sie vom Stapel zurück nach (r18, r19) und (r24, r25)

    add r24, r18
    adc r25, r19

Addiere (r18, r19) zu (r24, r25), einschließlich des Übertrags der zweiten Addition

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

Lagere es wieder auf dem Stapel.

Versuchen Sie zwei Dinge, um Ihre ursprüngliche Baugruppe zu erhalten:

  • benutze "char" Variablen
  • Verwenden Sie die Compiler-Option "-O2"

Bearbeiten : Der Grund, warum der Compiler Variablen im Stapel speichert, anstatt sie in Registern zu speichern, ist, dass sie mit dem Standardspeichertyp "auto" gespeichert werden. Es kann sie in Registern optimieren, muss dies aber nicht, selbst wenn Sie sie mit der Speicherklasse "register" deklarieren.

Dies ist zwar keine zwingende Voraussetzung für die Sprache, aber das normale Compiler-Verhalten. Wenn Sie irgendwann die Adresse von v1 übernehmen, muss ihr ein Speicherort zugewiesen und dort gespeichert werden, wenn sich der Wert von "v1" ändert. Um die Buchhaltung darüber zu schützen, ob v1 in einem Register oder auf dem Stapel gespeichert werden soll oder nicht, wird es auf dem Stapel gehalten und jede Codezeile wird separat behandelt.

pjc50
quelle
Vielen Dank! Es ist jetzt klarer! Bitte finden Sie meine Bearbeitung in der Frage.
DarkCoffee
1
Siehe meine Bearbeitung. Versuchen Sie auch -O2. Vielleicht -O3, obwohl das zu fehlerhaftem Code führen kann.
PJC50
3
Viele eingebettete Codes, mit denen ich arbeite, definieren zusätzliche Typen, die für ihre Größe spezifisch sind, wie z. B. "uint8, uint16, uint32" für nicht signierte Ints. Auf diese Weise wissen Sie immer genau, mit welcher Art von Variable Sie es zu tun haben. Vor allem in kleinen eingebetteten, signierten, float, "int" von undefinierter Größe / Signiertheit werden Sie alle CPU-Zyklen am besten kosten und im schlimmsten Fall ernsthafte Fehler verursachen.
John U
Echte Compiler haben vor 10-15 Jahren aufgehört, sich so zu verhalten. Das Registerzuordnungsproblem ist meistens gelöst und Compiler sind verdammt gut darin. Sie wissen genau, wann sich eine Variable auf dem Stapel befinden muss und wann sie sich in einem Register befinden kann, ob und wann es sich lohnt, sie zu verschieben. Die Buchhaltung wird zur Kompilierungszeit durchgeführt, und die Compiler selbst verfügen über Gigabyte an Speicher. Die große Ausnahme ist aus offensichtlichen Gründen der Debug-Modus, aber dann ist alles auf dem Stapel.
MSalters
@ pjc50 -O3kann kaputten Code erzeugen? [Zitieren erforderlich] (und nein, C-Code, der Undefined Behaviour aufruft und dann mit einigen Optimierungseinstellungen bricht, zählt nicht)
Marcelm
4

Wenn ich einen Beispielcode gefunden habe, werde ich meinen Kommentar beantworten - andere haben das Problem bereits erklärt.

Viele eingebettete Codes, mit denen ich arbeite, definieren zusätzliche Typen, die für ihre Größe spezifisch sind, wie z. B. "uint8, uint16, uint32" für nicht signierte Ints. Auf diese Weise wissen Sie immer genau, mit welcher Art von Variable Sie es zu tun haben. Vor allem in kleinen eingebetteten, signierten, float, "int" von undefinierter Größe / Signiertheit werden Sie alle CPU-Zyklen am besten kosten und im schlimmsten Fall ernsthafte Fehler verursachen.

Hier ist unser aktuelles #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 */
John U.
quelle
3
Gute Idee; uint8_t und Freunde sind jetzt Teil des Standards: stackoverflow.com/questions/16937459/…
pjc50
Wie praktisch! Wir haben die mit einem Projekt geerbt, das C89 war, also ist es gut zu wissen, dass es eine offizielle Version gibt.
John U
2

Ihr C-Code verwendet 16-Bit-Ganzzahlvariablen (int). Der Compiler kann Ihre Gedanken nicht lesen und kompiliert genau das, was in der Quelldatei enthalten ist. Wenn Sie also 8-Bit-Variablen möchten, müssen Sie den entsprechenden Typ verwenden.

Als Ergebnis erhalten Sie immer noch die Werte im Speicher (obwohl einfacher). Ich bin nicht so gut in C, aber meiner Meinung nach gibt es einige Optionen, um die Variable einem Register zuzuweisen, wenn Sie möchten, dass sich einige Variablen in den Registern statt im RAM befinden. Etwas wie:

register unsigned char VARNAME asm("r3");

Beachten Sie, dass für solche Tricks nicht alle Register verfügbar sind.

Also das Fazit? Schreiben Sie Ihre Programme in Assembler. Sie werden immer kleiner, schneller und einfacher zum Lesen / Unterstützen.

Johnfound
quelle
Assembly ist leichter zu lesen als C?
Dext0rb
@ dext0rb - Ja. Natürlich, wenn Sie beide gut genug kennen. Wenn Sie nur C kennen, sind Assembler und andere Sprachen schwer zu lesen.
Johnfound
Ich muss mit dem letzten Punkt nicht einverstanden sein, Programme, die in Assembler geschrieben wurden, sind viel schwerer zu lesen. Vergleichen Sie einfach den oben angegebenen Quellcode. Der C-Code ist viel klarer und kürzer, ebenso wie seine Absicht. Dieser Unterschied wächst nur, wenn Strukturen verwendet werden.
Soandos
@soandos - C-Code ist kürzer, ja. Klarer? Ich bin mir nicht sicher. Wenn dem so wäre, müsste die obige Frage überhaupt nicht gestellt werden. Tatsächlich ist der Preis für die "Kürze" die "Unschärfe" der Details.
Johnfound
Natürlich wird der Typ, der sagt "Ich bin nicht so gut in C", die Tugenden der reinen Versammlung verkünden. : D
dext0rb