Я готую кілька навчальних матеріалів на мові C і хочу, щоб мої приклади відповідали типовій моделі стеків.
У якому напрямку росте стек C у Linux, Windows, Mac OSX (PPC та x86), Solaris та останніх Unix?
Я готую кілька навчальних матеріалів на мові C і хочу, щоб мої приклади відповідали типовій моделі стеків.
У якому напрямку росте стек C у Linux, Windows, Mac OSX (PPC та x86), Solaris та останніх Unix?
Відповіді:
Зростання стека зазвичай не залежить від самої операційної системи, а від процесора, на якому вона працює. Наприклад, Solaris працює на x86 та SPARC. Mac OSX (як ви вже згадували) працює на PPC та x86. Linux працює на всьому, від моєї великої Honkin 'System z на роботі, до маленьких маленьких наручних годинників .
Якщо процесор надає будь-який вибір, конвенція ABI / виклику, що використовується ОС, визначає, який вибір потрібно зробити, якщо ви хочете, щоб ваш код називав код інших.
Процесори та їх напрямок:
Я показую мій вік на останніх кількох, 1802 - це мікросхема, яка використовується для управління ранніми човниками (я вважаю, що двері були відкриті, я підозрюю, виходячи з потужності процесора :-) і мого другого комп'ютера, COMX-35 ( слідкуючи за моїм ZX80 ).
Деталі PDP11 зібрані звідси , 8051 деталі звідси .
Архітектура SPARC використовує модель реєстрації розсувних вікон. Архітектурно видимі деталі також містять круговий буфер вікон регістрів, які є дійсними та кешовані внутрішньо, з пастками, коли це переповнює / перетікає. Детальніше дивіться тут . Як пояснюється посібник SPARCv8, інструкції Зберегти та ВІДКРИТИ подібні до інструкцій ADD плюс обертання вікна реєстрації. Використання позитивної константи замість звичайної негативної дало б зростаючу стадію вгору.
Згадана вище методика SCRT - інша - 1802 використовував кілька або це шістнадцять 16-бітних регістрів для SCRT (стандартна техніка виклику та повернення). Один був лічильником програм, ви можете використовувати будь-який реєстр як ПК з SEP Rn
інструкцією. Один був вказівником стека, а два завжди встановлювались для вказівки на адресу коду SCRT, один для виклику, один для повернення. Жоден реєстр не розглядався особливим чином. Майте на увазі, що ці дані з пам'яті, вони можуть бути не зовсім коректними.
Наприклад, якби R3 був ПК, R4 була адресою виклику SCRT, R5 була адресою повернення SCRT, а R2 - "стеком" (котирування, як це реалізовано в програмному забезпеченні), SEP R4
встановив би R4 як ПК та почав запускати SCRT код виклику.
Потім він буде зберігати R3 у "стеці" R2 (я думаю, що R6 використовувався для зберігання темпів), регулюючи його вгору або вниз, захоплюйте два байти, наступні за R3, завантажуйте їх у R3, а потім робіть SEP R3
і працюйте за новою адресою.
Для повернення це дозволило SEP R5
б витягнути стару адресу зі стека R2, додати до неї дві (щоб пропустити байти адреси виклику), завантажити її в R3 і SEP R3
почати виконувати попередній код.
Дуже важко обернути голову спочатку після всього коду, заснованого на стеці 6502/6809 / z80, але все-таки елегантно вдарив головою об стіну. Також однією з найбільших особливостей продажу чіпа був повний набір 16 16-бітових регістрів, незважаючи на те, що ви відразу втратили 7 з них (5 для SCRT, два для DMA та переривання з пам'яті). А-а-а, торжество маркетингу над реальністю :-)
Система z насправді досить схожа, використовуючи свої регістри R14 і R15 для виклику / повернення.
В C ++ (адаптується до C) stack.cc :
static int
find_stack_direction ()
{
static char *addr = 0;
auto char dummy;
if (addr == 0)
{
addr = &dummy;
return find_stack_direction ();
}
else
{
return ((&dummy > addr) ? 1 : -1);
}
}
static
для цього. Натомість ви можете передати адресу як аргумент рекурсивному виклику.
static
, якщо ви зателефонуєте до цього декілька разів, наступні дзвінки можуть закінчитися невдало ...
Перевага зростаючого коду полягає в тому, що в старих системах стек, як правило, знаходиться у верхній частині пам'яті. Програми, як правило, заповнюють пам'ять, починаючи знизу, таким чином таке управління пам'яттю зводило до мінімуму необхідність вимірювати та розміщувати нижню частину стека де-небудь розумним.
У MIPS та багатьох сучасних архітектурах RISC (таких як PowerPC, RISC-V, SPARC ...) немає push
і pop
інструкцій. Ці операції виконуються явним чином шляхом ручного налаштування покажчика стека та завантаження / збереження значення відносно відрегульованого покажчика. Усі регістри (крім нульового регістру) мають загальне призначення, тому теоретично будь-який регістр може бути покажчиком стека, і стек може рости в будь-якому напрямку хоче програміст
Однак, стек, як правило, зростає в більшості архітектур, ймовірно, щоб уникнути випадку, коли дані стека та програми або дані купи купують один одного. Існує також велика причина вирішення згаданої відповіді sh-sh . Деякі приклади: MIPS ABI зростає вниз і використовується$29
(AKA $sp
) в якості вказівника стека, RISC-V ABI також зростає вниз і використовує x2 як покажчик стека
В Intel 8051 стек зростає, ймовірно, тому, що простір пам'яті настільки крихітний (128 байт в оригінальній версії), що немає купи, і вам не потрібно ставити стек зверху, щоб він був відокремлений від наростаючої купи знизу
Ви можете знайти більше інформації про використання стека в різних архітектурах на https://en.wikipedia.org/wiki/Calling_convention
Дивитися також
Лише невелике доповнення до інших відповідей, яке, наскільки я бачу, не торкнулося цього моменту:
Якщо стек зростає вниз, всі адреси в стеку мають позитивне зміщення відносно вказівника стека. Немає необхідності в негативних зміщеннях, оскільки вони вказували б лише на невикористаний простір стеку. Це спрощує доступ до місць стека, коли процесор підтримує відношення стека-покажчика.
У багатьох процесорах є вказівки, які дозволяють отримати доступ із зміщенням лише позитивним відносно деякого регістра. Сюди входить багато сучасних архітектур, а також деякі старі. Наприклад, ARM Thumb ABI передбачає відношення до стекпоінтернентів з позитивним зміщенням, закодованим у межах одного 16-бітного слова інструкції.
Якщо стек зростає вгору, усі корисні зрушення відносно стекпоінта будуть негативними, що менш інтуїтивно та менш зручно. Це також суперечить іншим програмам відношення до регістру, наприклад, для доступу до полів структури.
У більшості систем стек зменшується, і моя стаття на веб- сайті https://gist.github.com/cpq/8598782 пояснює ЧОМУ він зростає. Це просто: як розташувати два зростаючих блоки пам'яті (купу та стек) у фіксованому шматі пам'яті? Найкраще рішення - покласти їх на протилежні кінці і нехай рости назустріч один одному.
Він зростає, тому що в пам'яті, виділеній програмі, є "постійні дані", тобто код для самої програми внизу, потім купа в середині. Вам потрібна інша фіксована точка, з якої слід посилатися на стек, щоб ви залишали верх. Це означає, що стек росте вниз, поки він потенційно не прилягає до об'єктів на купі.
Цей макрос повинен виявити його під час виконання без UB:
#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);
__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) {
return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}