В коде функции в стек всегда сначала кладется четырехбайтовй %ebp, чтобы
запомнить, где был указатель стека для последующего избавления от локальных переменных функции, которые, при их наличии, кладутся следом за %ebp. Не знаю, как дело обстоит при оптимизации, но обычно эта схема верна даже когда локальных переменных и вообще операций со стеком в функции нет.
Блин, мазафака. Уже не первый день пытаюсь отправить, но LOR как раз в это время падает. Может быть сейчас пройдет:
>при вызове функции ее параметры помещаются в стек. кроме этих параметров туда еще
> что-то кладется. вот и хочу выяснить, что и сколько это кушает
А-а.. Теперь уже яснее.
В стек пихаются параметры в обратном порядке, чтобы была возможность иметь переменное их число.
Т.е. для функции int func (int a, int b) параметры будут:
адрес возврата [esp+0], 4 байта.
int a в [esp+4] или [ebp+8], если в заголовке ebp пихается в стек.
int b в [esp+8] или [ebp+12]
>В коде функции в стек всегда сначала кладется четырехбайтовй %ebp, чтобы
>запомнить, где был указатель стека для последующего избавления от локальных
>переменных функции, которые, при их наличии, кладутся следом за %ebp.
Верно. А я еще и умное название скажу: frame pointer :-)
Но так же верно, что если нет локальных переменных, то и ebp в стек пихать не обязательно. Например, в strlen() и регистров хватит.
> Не знаю, как дело обстоит при оптимизации, но обычно эта схема верна даже когда
>локальных переменных и вообще операций со стеком в функции нет.
Да. GCC так делает, пока ему не сказать -fomit-frame-pointer, после этого он избыточные push/pop убирает. Оптимизация, мать ее.
2автор сообщения (дополнительно):
Вообще все очень просто. Пишешь к-либо функцию, затем говоришь
gcc -S файл.c (добавь -mintel-syntax, если не нравится AT&T синтаксис, но только в gcc>3.0 вроде)
или
icl /FAs файл.c (если есть интелевый компилер).
и изучаешь исходник - где чего и как вызывается и что при этом в стек кладется.
Можно поинтересоваться - а нафига знать сколько занимает заголовок?
Писать код, закладываясь на такого рода информацию - моветон, и
вообще получается абсолютно непереносимый код.
Кроме того, каждый компилятор использует свои методы оптимизации, даже разные версии одного компилятора - то есть наверняка сказать никогда нельзя сколько займёт заголовок. Кроме того, функция вообще может быть объявлена inline, и там совсем всё иначе.
Ну разве что из чистого интереса - тогда понимаю...
2Dimentiy: решал, как функцию делать - рекурсивную или итерационную. соответственно, хотелось узнать, в какие расходы памяти выльется рекурсия, потому как итерационная версия оказалась несколько медленней.
2Олл: спасибо за исчерпывающие ответы и за наводки, где копать
> Писать код, закладываясь на такого рода информацию - моветон, и
> вообще получается абсолютно непереносимый код.
Золотые слова, типа.
> Кроме того, каждый компилятор использует свои методы оптимизации,
> даже разные версии одного компилятора - то есть наверняка сказать
> никогда нельзя сколько займёт заголовок.
Ну, зря ты так. По крайней мере все мне известные x86/32-компилеры (i386: gcc, watcom, symantec, borland, intel, ms, lcc, вроде никого не забыл) генерируют совместимый код, если, конечно, им не сказать передавать параметры в регистрах (тогда только сами с собой), это соглашения такие. И ассемблерная процедура, написанная под intel для винды так же будет работать и в gcc freebsd (nasm rulez).
Так же, dll, откомпилированная в mingw цепляется в MSVC (если только она не сиплюсовая). При условии, что написано это все правильно, разумеется.
> Кроме того, функция вообще может быть объявлена inline, и там совсем всё иначе.
Если функция объявлена inline, это еще ничего не значит. Это говорит только о том, что ее код _может быть_ будет скопирован в вызывающую функцию, но при этом она останется обычной функцией, ее можно будет вызвать из другого модуля.
Рекурсивные, к примеру, функции не могут быть заинлайнены, если только не сводятся к хвостовой рекурсии (AFAIR, без вариантов).
Если функция оптимизируется настолько, что нарушает соглашения, то для нее создается еще один вариант (пример - leaf-функция, где есть соблазн заменить call-ret на jmp(туда)-jmp(обратно), или функция, вызываемая с константой в качестве параметра - "function cloning").
Конечно, если компилер умеет так оптимизировать.
Обычные же трансформации пролог/эпилог не трогают.
Имейте в виду, что на стеке может оказаться больше, чем frame. Вот
еще одно умное слово - register spilling (как это по-русски?): если
не хватает регистров, промежуточные результаты кладутся на стек.
Это уже совершенно непредсказуемо, так как зависит от логики в run
time.
> Имейте в виду, что на стеке может оказаться больше, чем frame. Вот
> еще одно умное слово - register spilling (как это по-русски?): если
> не хватает регистров, промежуточные результаты кладутся на стек.
> Это уже совершенно непредсказуемо, так как зависит от логики в run
time.
Про register spilling: AFAIK run-time тут и рядом не валялся, все предопределяется на этапе генерации кода, так как то, что регистров не будет хватать известно заранее (кол-во регистров константа, не так ли?)
А насчет русских эквивалентов: про "register spilling" мне тоже интересно, и еще кто бы
сказал, как перевести "leaf procedure" на русский (не по смыслу, а как термин).