|
|
  |
Организация стека в компиляторе WinAVR |
|
|
|
Jan 28 2011, 13:41
|
Участник

Группа: Участник
Сообщений: 18
Регистрация: 28-01-11
Пользователь №: 62 532

|
Добрый день всем! Дано: Компилятор WinAVR-20090313 в связке со средой разработки AVRStudio 4. МК - ATMega1280, но это не важно. Пусть есть программа на ЯВУ Си: int SumEvaluate(int a, int b, int c) { int sum = 0; sum = a + b + c; return sum; } void main(void) { int i; char ch1; char ch2; i = evaluate(5,4,10); printf("%d",i); } Известно, что стек начинается с конца SRAM и растет в сторону уменьшения адреса. Возникает вопрос где сохраняются переменные i, ch1, ch2? В соответствии с документацией на WinAVR, они могут сохраниться в двух местах: Call-used registers (r18-r27) или Call-saved ergisters (r2-r17) Если бы переменных было много (ch3, ch4, ch5, ch6, ...), места в регистрах РОН не хватило бы и тогда был бы задействован стек. В этом случае на вершину стека указывал бы указатель стека, хранящийся в Z-регистре или Y-регистре соответственно. А каким образом в этом случае известна глубина стека? Повторю вопрос: а где на самом деле сохраняются эти переменные в Call-used registers или Call-saved ergisters? И как становиться известна глубина стека когда для размещения переменных не хватает регистров общего назначения ? И второй вопрос, аналогичен первому - что происходит при вызове функции? Счетчик PC помещается в стек, это понятно. а куда помещаются локальные переменные для данной функции? Сначала в регистры общего назначения или сразу в стек? Куда помещается значение, возвращаемое функцией? Какое максимальное количество параметров, которое возможно передать функции? Непанятна
Сообщение отредактировал Giekelberri - Jan 28 2011, 13:43
|
|
|
|
|
Jan 28 2011, 16:19
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (Giekelberri @ Jan 28 2011, 15:41)  Возникает вопрос где сохраняются переменные i, ch1, ch2? Поскольку переменные локальные, они создаются на стеке. Однако, если в регистрах есть свободное место - оптимизатор выкинет сохранение на стек и восстановление, таким образом переменная будет жить в регистре. До тех пор, пока вы не попытаетесь взять и передать куда-то адрес переменной - в этом случае компилятор положит ее на стек. QUOTE (Giekelberri @ Jan 28 2011, 15:41)  Если бы переменных было много (ch3, ch4, ch5, ch6, ...), места в регистрах РОН не хватило бы и тогда был бы задействован стек. В этом случае на вершину стека указывал бы указатель стека, хранящийся в Z-регистре или Y-регистре соответственно. Это у ИАРа указатель на стек данных хранится в Y. GCC использует один стек и на него указывает регистр спец. назначения (SFR) SP. QUOTE (Giekelberri @ Jan 28 2011, 15:41)  А каким образом в этом случае известна глубина стека? Неизвестна. А зачем? QUOTE (Giekelberri @ Jan 28 2011, 15:41)  Повторю вопрос: а где на самом деле сохраняются эти переменные в Call-used registers или Call-saved ergisters? Могут быть и в тех и в других. На время вызова функций переменные из call-used регистров временно выгружаются на стек, после возврата - вынимаются из стека обратно в регистры. QUOTE (Giekelberri @ Jan 28 2011, 15:41)  И как становиться известна глубина стека когда для размещения переменных не хватает регистров общего назначения ? Компилятору она неизвестна. QUOTE (Giekelberri @ Jan 28 2011, 15:41)  Счетчик PC помещается в стек, это понятно. а куда помещаются локальные переменные для данной функции? Сначала в регистры общего назначения или сразу в стек? Вы имеете ввиду параметры? В зависимости от типа и количества - могут передаваться или в регистрах, или на стеке или и там и там. QUOTE (Giekelberri @ Jan 28 2011, 15:41)  Куда помещается значение, возвращаемое функцией? Аналогично. Какие-то типы возвращаются в регистрах, другие - через стек. QUOTE (Giekelberri @ Jan 28 2011, 15:41)  Какое максимальное количество параметров, которое возможно передать функции? По стандарту их там столько, что вы устанете набирать исходник
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jan 29 2011, 05:38
|
;
     
Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509

|
Цитата(Giekelberri @ Jan 28 2011, 16:41)  Дано: Компилятор WinAVR-20090313 Перейдите на крайнюю версию, она лучше. Цитата Если бы переменных было много Непанятна  Отключите оптимизацию - все поймете сразу, либо устройте веселый микс из локальных переменных и локальных volatile переменных, т.к. последние будут на стеке. Возврат значений и передача параметров - есть порядок назначения регистров, начинается с R24:R25 а дальше не помню  и то в командной строке этот порядок можно изменить, - при написании функций на ассмеблере так и стараюсь дальше R24:R25 не заплывать. Баюс
|
|
|
|
|
Jan 31 2011, 04:56
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
Цитата Возникает вопрос где сохраняются переменные i, ch1, ch2 i - резервируется место в стеке ch1, ch2 - скорее всего выкинутся компилятором после оптимизации т.к. не используются Цитата Куда помещается значение, возвращаемое функцией? Return values: 8-bit in r24 (not r25!), 16-bit in r25:r24, up to 32 bits in r22-r25, up to 64 bits in r18-r25. Перевода надеюсь не надо. Цитата Call-used registers (r18-r27) Call-used registers (r18-r27, r30-r31): May be allocated by gcc for local data. You may use them freely in assembler subroutines. Calling C subroutines can clobber any of them - the caller is responsible for saving and restoring. Могут быть распределены gcc для локальных переменных. Вы МОЖЕТЕ их использовать в ассемблерных подпрограммах. Вызываемая Си-подпрограмма МОЖЕТ ЗАТЕРЕТЬ любое содержимое - вызывающая программа ответственна за сохранение и воссстановление. Цитата Call-saved registers (r2-r17) Call-saved registers (r2-r17, r28-r29): May be allocated by gcc for local data. Calling C subroutines leaves them unchanged. Assembler subroutines are responsible for saving and restoring these registers, if changed. Могут быть распределены gcc для локальных переменных. Вызываемая Си-подпрограмма ДОЛЖНА их вернуть НЕИЗМЕНЕННЫМИ. Ассемблерная подпрограмма ответственна за сохранение и восстановление, если их изменяет. Цитата Повторю вопрос: а где на самом деле сохраняются эти переменные в Call-used registers или Call-saved ergisters? И те и другие сохраняются в стеке, только Call-used registers сохраняте вызывающая подпрограмма, а Call-saved ergisters ДОЛЖНА сохранять вызываемая подпрограмма (если, тем более, она написана на асме) Если хотите узнать как компилятор СИ (WinAVR) работает со стеком, то посмотрите оттранслированный листинг любой функции. В кратце при вызове функции это происходит так: 1. Вызывающая функция сохраняет в стеке (если надо) Call-used registers, и параметры некоторых функций (например printf) 2. Процессор аппаратно (в команде CALL или FCALL) сохраняет в стеке адресс возврата 3. Вызываемая программа сохраняет в стеке используемые Call-saved registers, в том числе и указатель стека (r28-r29)=(Y). 4. Вызываемая программа играется со стеком как душе угодно, в том числе размещает там свои локальные переменные. 5. Вызываемая программа восстанавливает Call-saved registers из стека 6. Процеесор аппаратно (в команде RET или FRET), воостанавливает PC из стека 7. Вызывающая функция восстанавливает из стека Сall-used registers, и также очищает стек от параметров некоторых функций (например printf) Если Вы пишите на Си, то замарачиваться по этому поводу не надо. Если только на Асме, то также замарачиваться не надо. И если только миксуете код - то надо знать правила вызовов Си-функций
|
|
|
|
|
Jan 31 2011, 08:44
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
Цитата Не про avr-gcc (WinAVR). Не использует он программный стек вовсе, только аппаратный (SPL,SPH). А как же фраза из WinAVR-хелпа: r29:r28 (Y pointer) is used as a frame pointer (points to local data on stack) if necessary. Во-вторых, я описал общую работу со стеком при вызове функции на Си. О реализации данного механизма в WinAVR можно говорить долго, и я не считаю нужным начинать спор из-за этого. Топик-стартер задал конкретный вопрос - я на него постарался ответить
|
|
|
|
|
Jan 31 2011, 10:26
|

неотягощённый злом
     
Группа: Свой
Сообщений: 2 746
Регистрация: 31-01-08
Из: Санкт-Петербург
Пользователь №: 34 643

|
Цитата(alexeyv @ Jan 31 2011, 11:44)  А как же фраза из WinAVR-хелпа: r29:r28 (Y pointer) is used as a frame pointer (points to local data on stack) if necessary. Вы её не так понимаете. Указатель стека в avr-gcc один - аппаратный. И точка. Цитата Во-вторых, я описал общую работу со стеком при вызове функции на Си. О реализации данного механизма в WinAVR можно говорить долго, и я не считаю нужным начинать спор из-за этого. Топик-стартер задал конкретный вопрос - я на него постарался ответить Прочитайте название топика внимательно. Вы сами себе противоречите. Его не интересовали общие принципы. Вопрос конкретно сформулирован.
--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
|
|
|
|
|
Jan 31 2011, 10:39
|
Местный
  
Группа: Участник
Сообщений: 298
Регистрация: 26-01-09
Из: Пермь
Пользователь №: 43 940

|
Цитата Вы её не так понимаете. Указатель стека в avr-gcc один - аппаратный. И точка. А как правильно понимать эту фразу? Помогите немощному! То есть, хватит меня критиковать - если знаете попробуйте объяснить сами, а не докапываться до чужих слов. Скомпилировал простую программку и посмотрел ее листинг пролог функции: Код push r29 push r28 in r28, 0x3d; 61 in r29, 0x3e; 62 sbiw r28, 0x1f; 31 in r0, 0x3f; 63 cli out 0x3e, r29; 62 out 0x3f, r0; 63 out 0x3d, r28; 61 эпилог функции: Код adiw r28, 0x1f; 31 in r0, 0x3f; 63 cli out 0x3e, r29; 62 out 0x3f, r0; 63 out 0x3d, r28; 61 pop r28 pop r29 ret Из кода видно что компилятор копирует указатель стека в Y, и поэтому Y - указывает на стек (это так сказать алиас стека). И работает c локальными переменными в функции через указатель Y. Так что мои слова Цитата в том числе и указатель стека (r28-r29)=(Y). не являются ложными , а являются слегка неточными. Про один аппаратный стек в WinAVR, я полностью согласен. Про наличие двух стеков, я не говорил!
Сообщение отредактировал alexeyv - Jan 31 2011, 11:07
|
|
|
|
|
Jan 31 2011, 11:05
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (alexeyv @ Jan 31 2011, 12:39)  А как правильно понимать эту фразу? Помогите немощному! Этот указатель заводится временно внутри функции для доступа к данным на стеке.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jan 31 2011, 13:35
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(Сергей Борщ @ Jan 31 2011, 13:05)  Этот указатель заводится временно внутри функции для доступа к данным на стеке. Дополню: и только втом случае, если это нужно для данной функции (не все аргументы или локальные переменные влезли в регистры либо куда-то передаётся адрес локальной переменной). frame pointer — указатель на кадр в стеке. А сам стек живёт по SP Совершенно аналогично использованию регистра BP в x86/16 - в той системе команд относительно SP адресоваться было невозможно.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
Jan 31 2011, 14:29
|
Участник

Группа: Участник
Сообщений: 18
Регистрация: 28-01-11
Пользователь №: 62 532

|
Всем спасибо за ответы! Частично разобрался с организацией стека. Для этого воспользовался советами посмотреть откомпилированный код без оптимизации. Код int evaluate(int a, int b, int c) { int sum = 0; sum = a + b + c; return sum; } int main(void) { int i; char ch1; char ch2; i = evaluate(5,4,10); return 0; } Даже без оптимизации компилятор выкинул ch1 и ch2. Параметры функции сначала сохранил в РОН. Войдя в функцию evaluate() выделил для них место в стеке и сохранил их в стеке. Далее вычисление суммы. Тут заметна избыточность кода. Так как переменных всего три можно было их вычислить и без использования стека. Результат, как вы правильно сказали, и в соответствии с документацией, был помещен в r24:r25 Вывод я сделал следующий: 1. для навигации внутри стека используется указатель Y. 2. глубину стека компилятор определяет заранее, поэтому при вызове функции указатель Y, а за ним и указатель стека SP прыгают на необходимую величину. А количество параметров, передаваемых функции, как я понял ограничивается только размером стека, т. е. стремиться к Internal SRAM.
|
|
|
|
|
Jan 31 2011, 14:59
|

неотягощённый злом
     
Группа: Свой
Сообщений: 2 746
Регистрация: 31-01-08
Из: Санкт-Петербург
Пользователь №: 34 643

|
Цитата(alexeyv @ Jan 31 2011, 13:39)  То есть, хватит меня критиковать - если знаете попробуйте объяснить сами, а не докапываться до чужих слов. Не стоит так кипятиться:-) У Вас всего-навсего была лишь проблема в терминологии. А в остальном Вы большой молодец! Цитата(Giekelberri @ Jan 31 2011, 17:29)  1. для навигации внутри стека используется указатель Y. Для индексации стекового фрейма...используется указатель Y.
--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
|
|
|
|
|
Jun 29 2011, 12:40
|

Местный
  
Группа: Участник
Сообщений: 253
Регистрация: 15-04-10
Из: Волгоград
Пользователь №: 56 658

|
Цитата(Сергей Борщ @ Jan 28 2011, 19:19)  ... Это у ИАРа указатель на стек данных хранится в Y. GCC использует один стек и на него указывает регистр спец. назначения (SFR) SP. ... Всегда считал что для организации стека в AVR используется указ.стека SP. А РОН Y - в качестве указателя стека - это уже самодеятельность я был лучшего мнения о ИАР )
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|