Каково направление роста стека в большинстве современных систем?

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

В каком направлении растет стек C в Linux, Windows, Mac OSX (PPC и x86), Solaris и последние Unix?

Ответ 1

Рост стека обычно зависит не от самой операционной системы, а от процессора, на котором она работает. Например, Solaris работает на x86 и SPARC. Mac OSX (как вы упомянули) работает на PPC и x86. Linux работает на всем, от моей большой системы System z на работе до маленьких маленьких наручных часов.

Если процессор предоставляет какой-либо выбор, соглашение об использовании интерфейса ABI/вызовов, используемое ОС, определяет, какой выбор нужно сделать, если вы хотите, чтобы ваш код вызывал код всех остальных.

Процессоры и их направление:

  • x86: вниз
  • SPARC: выбирается. Стандарт ABI использует вниз.
  • КПП: вниз, я думаю.
  • Система z: в связанном списке я не шучу (но все еще не работает, по крайней мере, для zLinux).
  • ARM: выбирается, но Thumb2 имеет компактные кодировки только для понижения (LDMIA = увеличение после, STMDB = уменьшение до).
  • 6502: вниз (но только 256 байтов).
  • RCA 1802A: любым способом, в зависимости от реализации SCRT.
  • PDP11: вниз.
  • 8051: вверх

Показывая мой возраст на этих последних нескольких, 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 для вызова/возврата.

Ответ 2

В 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);
    }
}

Ответ 3

Преимущество роста в старых системах: стек обычно находился в верхней части памяти. Программы обычно заполняли память, начиная со дна, поэтому такое управление памятью сводило к минимуму необходимость измерять и помещать дно стека куда-нибудь разумным.

Ответ 4

Стек растет на x86 (определяется архитектурой, поп увеличивает указатель на стек, толкает декременты.)

Ответ 5

В MIPS нет инструкции push/pop. Все нажатия/всплытия явно выполняются с помощью load/store относительно указателя стека, а затем вручную корректируют указатель $sp. Однако, поскольку все регистры (кроме $0) являются общим назначением, теоретически любой регистр может быть указателем стека, и стек может расти в любом направлении, которое хочет программист. MIPS ABI обычно растут вниз.

В Intel 8051 стек растет, возможно, потому, что пространство памяти настолько маленькое (128 байт в исходной версии), что нет кучи, и вам не нужно класть стек сверху, чтобы он был отделен от куча, растущая снизу.

Ответ 6

Он растет, потому что память, выделенная для программы, имеет "постоянный", т.е. код для самой программы внизу, а затем кучу в середине. Вам нужна еще одна фиксированная точка, из которой можно ссылаться на стек, чтобы он оставил вам верх. Это означает, что стек растет вниз, пока он не будет потенциально смежным с объектами в куче.

Ответ 7

В большинстве систем стек растет, и моя статья в https://gist.github.com/cpq/8598782 объясняет, ПОЧЕМУ он растет. Причина в том, что это оптимальная компоновка двух растущих областей памяти (куча и стек).

Ответ 8

Этот макрос должен обнаружить его во время выполнения без 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;
}

Ответ 9

Просто небольшое дополнение к другим ответам, которое, насколько я вижу, не коснулось этого пункта:

Увеличение стека вниз приводит к тому, что все адреса в стеке имеют положительное смещение относительно указателя стека. Нет необходимости в отрицательных смещениях, поскольку они будут указывать только на неиспользуемое пространство стека. Это упрощает доступ к расположению стека, когда процессор поддерживает адресацию относительно указателя стека.

Многие процессоры имеют инструкции, которые разрешают доступ с положительным смещением только относительно некоторого регистра. К ним относятся многие современные архитектуры, а также некоторые старые. Например, ARM Thumb ABI обеспечивает относительный доступ к указателю стека с положительным смещением, закодированным в одном 16-битном командном слове.

Если бы стек рос вверх, все полезные смещения относительно указателя стека были бы отрицательными, что менее интуитивно понятно и менее удобно. Это также противоречит другим приложениям относительной к регистру адресации, например, для доступа к полям структуры.