Я готовлю некоторые учебные материалы на 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 работает на всем, от моей большой системы System z на работе до маленьких маленьких наручных часов.
Если процессор предоставляет какой-либо выбор, соглашение об использовании интерфейса ABI/вызовов, используемое ОС, определяет, какой выбор нужно сделать, если вы хотите, чтобы ваш код вызывал код всех остальных.
Процессоры и их направление:
Показывая мой возраст на этих последних нескольких, 1802 был чипом, используемым для управления ранними шаттлами (я подозреваю, что, если двери были открыты, исходя из вычислительной мощности, которую он имел :-) и моего второго компьютера, COMX-35 ( после моего ZX80).
Детали PDP11 почерпнуты отсюда, детали 8051 отсюда.
В архитектуре SPARC используется модель регистра скользящего окна. Архитектурно видимые детали также включают в себя круговой буфер окон регистров, которые являются действительными и кэшируются внутри, с ловушками, когда это переполняет/переполняет. Смотрите здесь для деталей. Как объясняется в руководстве SPARCv8, инструкции SAVE и RESTORE аналогичны инструкциям 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);
}
}
Преимущество роста в старых системах: стек обычно находился в верхней части памяти. Программы обычно заполняли память, начиная со дна, поэтому такое управление памятью сводило к минимуму необходимость измерять и помещать дно стека куда-нибудь разумным.
Стек растет на x86 (определяется архитектурой, поп увеличивает указатель на стек, толкает декременты.)
В MIPS нет инструкции push
/pop
. Все нажатия/всплытия явно выполняются с помощью load/store относительно указателя стека, а затем вручную корректируют указатель $sp
. Однако, поскольку все регистры (кроме $0
) являются общим назначением, теоретически любой регистр может быть указателем стека, и стек может расти в любом направлении, которое хочет программист. MIPS ABI обычно растут вниз.
В Intel 8051 стек растет, возможно, потому, что пространство памяти настолько маленькое (128 байт в исходной версии), что нет кучи, и вам не нужно класть стек сверху, чтобы он был отделен от куча, растущая снизу.
Он растет, потому что память, выделенная для программы, имеет "постоянный", т.е. код для самой программы внизу, а затем кучу в середине. Вам нужна еще одна фиксированная точка, из которой можно ссылаться на стек, чтобы он оставил вам верх. Это означает, что стек растет вниз, пока он не будет потенциально смежным с объектами в куче.
В большинстве систем стек растет, и моя статья в 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;
}
Просто небольшое дополнение к другим ответам, которое, насколько я вижу, не коснулось этого пункта:
Увеличение стека вниз приводит к тому, что все адреса в стеке имеют положительное смещение относительно указателя стека. Нет необходимости в отрицательных смещениях, поскольку они будут указывать только на неиспользуемое пространство стека. Это упрощает доступ к расположению стека, когда процессор поддерживает адресацию относительно указателя стека.
Многие процессоры имеют инструкции, которые разрешают доступ с положительным смещением только относительно некоторого регистра. К ним относятся многие современные архитектуры, а также некоторые старые. Например, ARM Thumb ABI обеспечивает относительный доступ к указателю стека с положительным смещением, закодированным в одном 16-битном командном слове.
Если бы стек рос вверх, все полезные смещения относительно указателя стека были бы отрицательными, что менее интуитивно понятно и менее удобно. Это также противоречит другим приложениям относительной к регистру адресации, например, для доступа к полям структуры.