Від С до Асамблеї


16

Припустимо, у нас є такий фрагмент коду С для avr-8bit:

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 інструкція?

Відповіді:


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 в реєстрі чи на стеці, він зберігає його в стеку і обробляє кожен рядок коду окремо.


Дякую! Зараз зрозуміліше! Будь ласка, знайдіть мою редакцію у питанні.
DarkCoffee

1
Дивіться мою редакцію. Спробуйте також -O2. Можливо -O3, хоча це може призвести до зламаного коду.
pjc50

3
Багато вбудованого коду, з яким я працюю, визначає додаткові типи, характерні для їх розміру, наприклад, "uint8, uint16, uint32" для непідписаних вкладишів, наприклад. Таким чином, ви завжди точно знаєте, з якою змінною ви маєте справу. Особливо у невеликих вбудованих, підписаних, плавучих "int" невизначених розмірів / підписаності все це буде коштувати вам циклів процесора в кращому випадку і спричинить серйозні помилки в гіршому випадку.
Джон U

Справжні компілятори перестали так поводитися приблизно 10-15 років тому. Проблема розподілу регістрів здебільшого вирішена, і компілятори прокляті в цьому добре. Вони точно знають, коли змінна повинна бути в стеку, і коли вона може бути в реєстрі, чи варто докладати зусиль, щоб перемістити її, і коли це зробити. Ведення бухгалтерського обліку ведеться в час компіляції, а самі компілятори мають гігабайти пам'яті. Великим винятком є ​​режим налагодження з зрозумілих причин, але тоді все стоїть на стеці.
MSalters

@ pjc50 -O3може створити несправний код? [потрібне цитування] (і ні, код C, який викликає не визначене поведінку, а потім розривається з деякими налаштуваннями оптимізації, не враховується)
marcelm

4

Як я знайшов якийсь приклад коду, я зроблю свій коментар відповіддю - інші вже пояснили проблему.

Багато вбудованого коду, з яким я працюю, визначає додаткові типи, характерні для їх розміру, наприклад, "uint8, uint16, uint32" для непідписаних вкладишів, наприклад. Таким чином, ви завжди точно знаєте, з якою змінною ви маєте справу. Особливо у невеликих вбудованих, підписаних, плавучих "int" невизначених розмірів / підписаності все це буде коштувати вам циклів процесора в кращому випадку і спричинить серйозні помилки в гіршому випадку.

Ось наші поточні #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 */

3
Гарна ідея; uint8_t і друзі тепер є частиною стандарту: stackoverflow.com/questions/16937459 / ...
pjc50

Як зручно! Ми нібито успадкували тих, хто мав проект C89, тому добре знати, що існує офіційна версія.
Джон U

2

Ваш код C використовує 16-бітні цілочисельні змінні (int). Компілятор не може прочитати вашу думку, тому він збирає саме те, що є у вихідному файлі. Отже, якщо ви хочете 8-бітні змінні, вам доведеться використовувати відповідний тип.

В результаті ви все одно отримаєте збереження значень у пам'яті (хоча і простіших). Я не дуже хороший в C, але IMHO, є деякі варіанти призначити змінну якомусь реєстру, якщо ви хочете, щоб деякі змінні були в регістрах замість RAM. Щось на зразок:

register unsigned char VARNAME asm("r3");

Зауважте, що не всі регістри доступні для таких хитрощів.

Отже, висновок? Пишіть свої програми в зборі Вони завжди будуть меншими, швидшими та зручними для читання / підтримки.


Збірка легше читати, ніж C?
dext0rb

@ dext0rb - Так. Звичайно, якщо ви знаєте обох досить добре. Якщо ви знаєте лише C, то складання та будь-які інші мови буде важко читати.
johnfound

Мені доводиться не погоджуватися з останнім пунктом, програми, написані в асемблері, читати набагато складніше. Просто порівняйте вихідний код, наведений вище. Код С набагато чіткіший і коротший, як і його намір. Ця різниця лише зростає в міру використання структур.
soandos

@soandos - код C коротший, так. Ясніше? Я не впевнений. Якби це було так, вищезазначене питання взагалі не потрібно було б задавати. Власне ціна «короткості» - це «розмитість» деталей.
johnfound

Звичайно, хлопець, який каже "Я не такий хороший в С", буде проголошувати чесноти чистої збірки. : D
dext0rb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.