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

102

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

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

Ури
источник
3
А почему нисходящая версия: stackoverflow.com/questions/2035568/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Ответы:

147

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

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

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

  • x86: вниз.
  • SPARC: по выбору. Стандартный ABI использует down.
  • КПП: думаю, да.
  • System 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 и прерывания из памяти). Ах, победа маркетинга над реальностью :-)

System z на самом деле очень похожа, используя регистры R14 и R15 для вызова / возврата.

paxdiablo
источник
3
Чтобы добавить в список, ARM может расти в любом направлении, но может быть настроен на одно или другое с помощью конкретной реализации кремния (или может быть оставлен выбираемым программным обеспечением). Те немногие, с которыми я имел дело, всегда находились в режиме роста.
Майкл Берр,
1
В той небольшой части мира ARM, которую я видел до сих пор (ARM7TDMI), стек полностью обрабатывается программным обеспечением. Адреса возврата хранятся в регистре, который при необходимости сохраняется программным обеспечением, а инструкции пре- / пост-инкремента / декремента позволяют помещать его и другие данные в стек в любом направлении.
starblue
1
У HPPA стек вырос! Достаточно редко среди достаточно современных архитектур.
tml
2
Для любопытных, вот хороший ресурс о том, как работает стек на z / OS: www-03.ibm.com/systems/resources/Stack+and+Heap.pdf
Диллон
1
Спасибо @paxdiablo за понимание. Иногда люди воспринимают такой комментарий как личное оскорбление, особенно если он старше. Я знаю только, что есть разница, потому что сам совершал ту же ошибку в прошлом. Береги себя.
CasaDeRobison
23

В 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);
    }
}
jfs
источник
14
Вау, я давно не видел ключевое слово "авто".
paxdiablo
9
(& dummy> addr) не определено. Результат подачи двух указателей на реляционный оператор определяется только в том случае, если два указателя указывают на один и тот же массив или структуру.
sigjuice
2
Попытка исследовать структуру вашего собственного стека - то, что C / C ++ вообще не определяет - изначально «непереносима», так что меня это особо не заботит. Однако похоже, что эта функция будет работать правильно только один раз.
ephemient,
9
Для этого не нужно использовать static. Вместо этого вы можете передать адрес в качестве аргумента рекурсивного вызова.
R .. GitHub ОСТАНОВИТЬ ПОМОЩЬ ICE
5
плюс, используя a static, если вы вызовете это более одного раза, последующие вызовы могут потерпеть неудачу ...
Крис Додд,
7

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

mP.
источник
3
Не «преимущество», а на самом деле тавтология.
Маркиз Лорн
1
Не тавтология. Дело в том, чтобы две растущие области памяти не мешали (если память все равно не заполнена), как указал @valenok.
YvesgereY
6

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

Майкл
источник
5

Не в MIPS и многих современных архитектур RISC (например , PowerPC, RISC-V, SPARC ...) есть нет pushи popинструкции. Эти операции явно выполняются путем ручной настройки указателя стека, а затем загрузки / сохранения значения относительно настроенного указателя. Все регистры (кроме нулевого) являются универсальными, поэтому теоретически любой регистр может быть указателем стека, и стек может расти в любом направлении, которое хочет программист.

Тем не менее, стек обычно увеличивается вниз на большинстве архитектур, вероятно, чтобы избежать случая, когда стек и данные программы или данные кучи растут и конфликтуют друг с другом. Также есть веские причины адресации, упомянутые в ответе . Некоторые примеры: MIPS ABI растет вниз и использует $29(AKA $sp) в качестве указателя стека, RISC-V ABI также растет вниз и использует x2 в качестве указателя стека.

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

Дополнительную информацию об использовании стека в различных архитектурах можно найти в https://en.wikipedia.org/wiki/Calling_convention.

Смотрите также

Phuclv
источник
2

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

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

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

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

ш-
источник
2

В большинстве систем стек растет вниз, и моя статья на https://gist.github.com/cpq/8598782 объясняет, ПОЧЕМУ он растет. Это просто: как разместить два растущих блока памяти (кучу и стек) в фиксированном фрагменте памяти? Лучшее решение - поставить их на противоположные концы и дать расти друг другу.

валенок
источник
эта суть кажется мертва :(
Вен,
@Ven - Я могу добраться до этого
Бретт Холман
1

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

Кай
источник
0

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