От C до сборки

16

Предположим, что у нас есть следующий фрагмент кода C для 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

Когда есть стандартная инструкция?

DarkCoffee
источник

Ответы:

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"

Редактировать : причина, по которой компилятор хранит переменные в стеке, а не хранит их в регистрах, заключается в том, что они хранятся с типом хранения по умолчанию «auto». Это может оптимизировать их в регистры, но это не обязательно, даже если вы объявите их с классом хранения «register».

Хотя это не строгое требование языка, это нормальное поведение компилятора. Если в какой-то момент вы берете адрес v1, тогда ему нужно назначить место хранения и сохранять там всякий раз, когда изменяется значение «v1». Таким образом, чтобы сохранить учет того, должен ли v1 храниться в регистре или в стеке, он сохраняет его в стеке и обрабатывает каждую строку кода отдельно.

pjc50
источник
Спасибо! Теперь все понятно! Пожалуйста, найдите мое редактирование в вопросе.
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");

Обратите внимание, что не все регистры доступны для таких приемов.

Итак, заключение? Напишите свои программы в сборке. Они всегда будут меньше, быстрее и удобнее для чтения / поддержки.

johnfound
источник
Сборка легче читать, чем С?
dext0rb
@ dext0rb - Да. Конечно, если вы знаете оба достаточно хорошо. Если вы знаете только C, тогда ассемблер и любые другие языки будут трудно читать.
Джоннаунд
Я должен не согласиться с последним пунктом, программы, написанные на ассемблере, гораздо сложнее читать. Просто сравните исходный код, приведенный выше. Код на C намного понятнее и короче, чем и его намерения. Эта разница только увеличивается по мере использования структур.
Soandos
@soandos - код на C короче, да. Яснее? Я не уверена. Если бы это было так, вопрос выше не нужно было бы задавать вообще. На самом деле цена «краткости» - это «размытость» деталей.
johnfound
Конечно, парень, который говорит: «Я не так хорош в Си», будет провозглашать достоинства чистого собрания. : D
dext0rb