Od C do montażu


16

Załóżmy, że mamy następujący fragment kodu C dla avr-8bit:

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

Spodziewałem się następującego demontażu

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

ale po tym jak pobiegłem:

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

i

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

Mam następujący demontaż

    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

czy jest ktoś, kto może mi pomóc zrozumieć wynik deasemblera?

Edycja: Za pomocą char zespół staje się:

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

Kiedy jest instrukcja standardowa?

Odpowiedzi:


20

Krótka odpowiedź: twoje rejestry są 8-bitowe, a twoje wartości 16-bitowe. Dlatego zajmuje się nimi w dwóch częściach.

Długa odpowiedź:

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

Przechowuj 16-bitową wartość 1 w rejestrach 8-bitowych r24, r25.

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

Przechowuj w miejscach stosu Y + 1, Y + 2.

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

Przechowuj 16-bitową wartość 2 w rejestrach 8-bitowych r24, r25.

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

Przechowuj w miejscach stosu Y + 3, Y + 4.

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

Skopiuj je z powrotem ze stosu do (r18, r19) i (r24, r25)

    add r24, r18
    adc r25, r19

Dodaj (r18, r19) do (r24, r25), w tym przeniesienie drugiego dodawania

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

Umieść go z powrotem na stosie.

Aby uzyskać oryginalny zestaw, spróbuj dwóch rzeczy:

  • użyj zmiennych „char”
  • użyj opcji kompilatora „-O2”

Edycja : powodem, dla którego kompilator przechowuje zmienne na stosie zamiast przechowywać je w rejestrach, jest to, że są one przechowywane z domyślnym typem przechowywania „auto”. To może zoptymalizować je w rejestrach, ale nie musi, nawet jeśli deklarują ich „register” klasa przechowywania.

Chociaż nie jest to ścisłe wymaganie języka, jest to normalne zachowanie kompilatora. Jeśli w którymś momencie podasz adres v1, wówczas należy mu przypisać miejsce przechowywania i zapisać tam z powrotem, gdy zmieni się wartość „v1”. Aby więc zachować księgowość, czy v1 powinna być przechowywana w rejestrze, czy na stosie, utrzymuje ją na stosie i traktuje każdą linię kodu osobno.


Dziękuję Ci! Teraz jest lepiej! Proszę znaleźć moją edycję w pytaniu.
DarkCoffee

1
Zobacz moją edycję. Spróbuj również -O2. Może -O3, chociaż może to spowodować uszkodzenie kodu.
pjc50,

3
Dużo osadzonego kodu, z którym pracuję, definiuje dodatkowe typy, które są specyficzne dla ich rozmiaru, takie jak na przykład „uint8, uint16, uint32” dla znaków wewnętrznych bez znaku. W ten sposób zawsze wiesz dokładnie, z jaką zmienną masz do czynienia. Zwłaszcza w przypadku małych osadzonych, podpisanych, zmiennoprzecinkowych znaków „int” o nieokreślonym rozmiarze / sygnaturze wszystko co najwyżej będzie kosztować cykle procesora, aw najgorszym wypadku spowoduje poważne błędy.
John U

Prawdziwe kompilatory przestały się tak zachowywać około 10-15 lat temu. Problem przydziału rejestrów jest w większości rozwiązany, a kompilatory są w tym cholernie dobre. Wiedzą dokładnie, kiedy zmienna musi znajdować się na stosie i kiedy może znajdować się w rejestrze, czy warto ją przenieść i kiedy to zrobić. Księgowość jest wykonywana w czasie kompilacji, a same kompilatory mają gigabajty pamięci. Dużym wyjątkiem jest tryb debugowania z oczywistych powodów, ale wtedy wszystko jest na stosie.
MSalters

@ pjc50 -O3może produkować uszkodzony kod? [potrzebne źródło] (i nie, kod C, który wywołuje niezdefiniowane zachowanie, a następnie psuje się przy niektórych ustawieniach optymalizacji się nie liczy)
marcelm 10.01.19

4

Gdy znalazłem przykładowy kod, zmienię komentarz w komentarz - inni już wyjaśnili problem.

Dużo osadzonego kodu, z którym pracuję, definiuje dodatkowe typy, które są specyficzne dla ich rozmiaru, takie jak na przykład „uint8, uint16, uint32” dla znaków wewnętrznych bez znaku. W ten sposób zawsze wiesz dokładnie, z jaką zmienną masz do czynienia. Zwłaszcza w przypadku małych osadzonych, podpisanych, zmiennoprzecinkowych znaków „int” o nieokreślonym rozmiarze / sygnaturze wszystko co najwyżej będzie kosztować cykle procesora, aw najgorszym wypadku spowoduje poważne błędy.

Oto nasze obecne # definicje:

/*
 * 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
Dobry pomysł; uint8_t i przyjaciele są teraz częścią standardu: stackoverflow.com/questions/16937459/…
pjc50 28.10.2013

Jak przydatne! W pewnym sensie odziedziczyliśmy te z projektem C89, więc dobrze wiedzieć, że istnieje oficjalna wersja.
John U

2

Twój kod C używa 16-bitowych zmiennych całkowitych (int). Kompilator nie może czytać w twoich myślach, więc kompiluje dokładnie to, co jest w pliku źródłowym. Tak więc, jeśli chcesz 8-bitowych zmiennych, musisz użyć odpowiedniego typu.

W rezultacie nadal będziesz przechowywać wartości w pamięci (choć prostsze). Nie jestem tak dobry w C, ale IMHO, istnieje kilka opcji przypisania zmiennej do jakiegoś rejestru, jeśli chcesz, aby niektóre zmienne były w rejestrach zamiast pamięci RAM. Coś jak:

register unsigned char VARNAME asm("r3");

Pamiętaj, że nie wszystkie rejestry są dostępne dla takich sztuczek.

Więc wniosek? Napisz swoje programy w asemblerze. Zawsze będą mniejsze, szybsze i łatwiejsze do czytania / obsługi.


Montaż jest łatwiejszy do odczytania niż C?
dext0rb

@ dext0rb - Tak. Oczywiście, jeśli znasz oba wystarczająco dobrze. Jeśli znasz tylko C, asembler i inne języki będą trudne do odczytania.
johnfound

Muszę się nie zgodzić z ostatnim punktem, programy napisane w asemblerze są znacznie trudniejsze do odczytania. Wystarczy porównać kod źródłowy podany powyżej. Kod C jest znacznie wyraźniejszy i krótszy, podobnie jak jego przeznaczenie. Różnica ta rośnie tylko wtedy, gdy używane są struktury.
soandos

@soandos - kod C jest krótszy, tak. Jaśniejsze? Nie jestem pewien. Gdyby tak było, powyższe pytanie w ogóle nie musiałoby być zadawane. W rzeczywistości ceną „krótkości” jest „rozmycie” szczegółów.
johnfound

Oczywiście facet, który mówi „Nie jestem tak dobry w C”, będzie głosił zalety czystego zgromadzenia. : D
dext0rb 28.10.13
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.