Вопрос

Я пытаюсь понять, как C и C++ хранят большие объекты в стеке.Обычно стек имеет размер целого числа, поэтому я не понимаю, как там хранятся более крупные объекты.Они просто занимают несколько «слотов» стека?

Это было полезно?

Решение

Стек - это часть памяти. Указатель стека указывает на вершину. Значения могут быть помещены в стек и извлечены для их извлечения.

Например, если у нас есть функция, которая вызывается с двумя параметрами (размером 1 байт и размером 2 байта; просто предположим, что у нас 8-битный ПК).

Оба помещаются в стек, это перемещает указатель стека вверх:

03: par2 byte2
02: par2 byte1
01: par1

Теперь функция вызывается и адрес возврата помещается в стек:

05: ret byte2
04: ret byte1
03: par2 byte2
02: par2 byte1
01: par1

Хорошо, внутри функции у нас есть 2 локальные переменные; один из 2 байтов и один из 4. Для них зарезервирована позиция в стеке, но сначала мы сохраняем указатель стека, чтобы мы знали, где переменные начинаются с обратного отсчета, а параметры - с обратного отсчета.

11: var2 byte4
10: var2 byte3
09: var2 byte2
08: var2 byte1
07: var1 byte2
06: var1 byte1
    ---------
05: ret byte2
04: ret byte1
03: par2 byte2
02: par2 byte1
01: par1

Как видите, вы можете помещать в стек что угодно, если у вас есть свободное место. И еще вы получите феномен, который дает этому сайту свое имя.

Другие советы


Стек и куча не так уж различны, как вы думаете!


Действительно, некоторые операционные системы имеют ограничения стека.(Некоторые из них также имеют неприятные ограничения на кучу!)

Но сейчас уже не 1985 год.

Сейчас я использую Linux!

Мой по умолчанию размер стека ограничен 10 МБ.Мой по умолчанию размер кучи неограничен.Ограничить размер стека довольно просто.(*кашель* [ткш] неограниченный размер стека *кашель*.Или setrlimit().)

Самые большие различия между куча и куча являются:

  1. куча выделения просто смещают указатель (и, возможно, выделяют новые страницы памяти, если стек стал достаточно большим). Куча должен просмотреть свои структуры данных, чтобы найти подходящий блок памяти.(И, возможно, также выделить новые страницы памяти.)
  2. куча выходит за пределы области видимости, когда текущий блок заканчивается. Куча выходит за рамки при вызове delete/free.
  3. Куча может фрагментироваться. Куча никогда не фрагментируется.

В Linux оба куча и куча управляются через виртуальную память.

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

В зависимости от вашей ОС, часто это происходит только тогда, когда вы действительно используете эти новые страницы памяти, в которые они отображаются.(НЕТ в течение маллок() распределение!) (Это ленивая оценка вещь.)

(новый вызовет конструктор, который предположительно будет использовать эти страницы памяти...)


Вы можете разгромить систему виртуальных машин, создавая и уничтожая большие объекты на куча или куча.От вашей ОС/компилятора зависит, может ли система освободить память.Если он не будет восстановлен, куча сможет использовать его повторно.(Предполагая, что он не был перепрофилирован другим маллок() тем временем.) Аналогично, если стек не будет освобожден, он будет просто использован повторно.

Хотя страницы, которые были заменены, необходимо будет вернуть обратно, и это будет вашим самым большим ударом по времени.


Из всех этих вещей, Больше всего меня беспокоит фрагментация памяти!

Продолжительность жизни (когда она выходит за рамки) всегда является решающим фактором.

Но когда вы запускаете программы в течение длительного времени, фрагментация создает постепенно увеличивающийся объем памяти.Постоянная замена в конечном итоге убивает меня!




ДОБАВЛЕНО:


Чувак, меня испортили!

Что-то здесь просто не складывалось...Я подумал, что либо *я* чертовски не в себе.Или все остальные были.Или, что более вероятно, и то, и другое.Или, может быть, ни то, ни другое.

Каким бы ни был ответ, я должен был знать, что происходит!

...Это будет долго.Потерпите меня...


Большую часть последних 12 лет я провёл, работая под Linux.И примерно за 10 лет до этого под различными версиями Unix.Мой взгляд на компьютеры несколько предвзят.Я был испорчен!

Я немного разбирался в Windows, но недостаточно, чтобы говорить авторитетно.Как это ни печально, но и с Mac OS/Darwin...Хотя Mac OS/Darwin/BSD достаточно близки, поэтому некоторые мои знания сохраняются.


При использовании 32-битных указателей вам не хватает адресного пространства размером 4 ГБ (2^32).

Практически говоря, КУЧА+КУЧА в сочетании обычно ограничивается где-то между 2-4 ГБ, так как туда необходимо сопоставить другие вещи.

(Есть общая память, общие библиотеки, файлы, отображенные в памяти, исполняемый образ, который вы всегда запускаете, и т. д.)


В Linux/Unix/MacOS/Darwin/BSD вы можете искусственно ограничить КУЧА или КУЧА любым произвольным значениям, которые вы хотите во время выполнения.Но в конечном итоге существует жесткий системный предел.

В этом заключается различие (в tcsh) "ограничение" против "ограничение -h".Или (в bash) "улимит -Са" против "улимит -Ха".Или, программно, rlim_cur против rlim_max в структура rlimit.


Теперь мы переходим к самому интересному.Что касается Кодекс Мартина Йорка.(Спасибо Мартин!Хороший пример.Всегда приятно попробовать что-нибудь!.)

Мартина предположительно работает на Mac.(довольно недавний.Его сборка компилятора новее моей!)

Конечно, его код по умолчанию не запускается на его Mac.Но все будет работать нормально, если он сначала вызовет «неограниченный размер стека» (тчш) или "ulimit -Ss безлимит" (баш).


СУТЬ ДЕЛА:


Тестирование на древнем (устаревшем) ядре Linux RH9 2.4.x, выделяющем большие объемы КУЧА   ИЛИ   КУЧА, любой из них сам по себе имеет максимальный размер от 2 до 3 ГБ.(К сожалению, объем оперативной памяти + подкачка машины составляет чуть менее 3,5 ГБ.Это 32-битная ОС.А это НЕТ единственный запущенный процесс.Мы обходимся тем, что имеем...)

Так что на самом деле ограничений нет. КУЧА размер против КУЧА размер под Linux, кроме искусственных...


НО:


На Mac существует жесткое ограничение размера стека: 65532 килобайта.Это связано с тем, как вещи располагаются в памяти.


Обычно вы думаете об идеализированной системе как о КУЧА на одном конце адресного пространства памяти, КУЧА друг на друга, и они строят навстречу друг другу.Когда они встречаются, ты теряешь память.

Mac, похоже, придерживаются своих Общие системные библиотеки между ними с фиксированным смещением, ограничивающим обе стороны.Ты все еще можешь бежать Кодекс Мартина Йорка с «неограниченным размером стека», поскольку он выделяет только около 8 МБ (<64 МБ) данных. Но у него закончится КУЧА задолго до того, как у него закончится КУЧА.

Я использую Linux.Я не буду. Прости, малыш.Вот никель.Иди и купи себе лучшую ОС.

Есть обходные пути для Mac.Но они становятся уродливыми и запутанными и требуют настройки параметров ядра или компоновщика.

В долгосрочной перспективе, если Apple не сделает что-то действительно глупое, 64-битные адресные пространства когда-нибудь сделают всю эту идею с ограничением стека устаревшей. Очень скоро.


Переходим к фрагментации:


Каждый раз, когда вы нажимаете что-то на КУЧА оно добавлено в конец.И он удаляется (откатывается) при выходе из текущего блока.

В результате дырок в корпусе нет. КУЧА.Это все один большой сплошной блок используемой памяти.Возможно, в самом конце осталось лишь немного неиспользованного пространства, и все готово для повторного использования.

Напротив, как КУЧА выделяется и освобождается, вы получаете дыры в неиспользуемой памяти.Это может постепенно привести к увеличению объема памяти.Это не то, что мы обычно подразумеваем под утечкой ядра, но результаты схожи.

Фрагментация памяти – это НЕТ причина избегать КУЧА хранилище.Это просто то, о чем нужно помнить, когда вы кодируете.


Что вызывает СВОП-МОТЬБА:


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

Тогда вы можете получить большое количество переменных, которые используются в небольшой локализованной области кода и разбросаны по множеству страниц виртуальной памяти.(То есть вы используете 4 байта на этой 2-килобайтной странице, 8 байтов на этой 2-килобайтной странице и так далее для множества страниц...)

Все это означает, что для запуска вашей программы необходимо заменить большое количество страниц.Или он будет постоянно менять местами страницы.(Мы называем это избиением.)

С другой стороны, если бы эти небольшие ассигнования были сделаны на КУЧА, все они будут расположены в непрерывном участке памяти.Потребуется загружать меньше страниц памяти виртуальной машины.(4+8+...<2 тыс. за победу.)

Примечание:Причина, по которой я привлек внимание к этому, связана с одним знакомым мне инженером-электриком, который настаивал на том, чтобы все массивы размещались в HEAP.Мы занимались матричной математикой для графики.*МНОГО* массивов из 3 или 4 элементов.Управление созданием/удалением в одиночку было кошмаром.Даже абстрагируясь на занятиях, это вызывало горе!


Следующая тема.Резьба:


Да, потоки по умолчанию ограничены очень маленькими стеками.

Вы можете изменить это с помощью pthread_attr_setstacksize().Хотя в зависимости от вашей реализации потоков, если несколько потоков используют одно и то же 32-битное адресное пространство, большие отдельные стеки для каждого потока будут проблемой! Там просто не так много места!Опять же, поможет переход на 64-битное адресное пространство (ОС).

pthread_t       threadData;
pthread_attr_t  threadAttributes;

pthread_attr_init( & threadAttributes );
ASSERT_IS( 0, pthread_attr_setdetachstate( & threadAttributes,
                                             PTHREAD_CREATE_DETACHED ) );

ASSERT_IS( 0, pthread_attr_setstacksize  ( & threadAttributes,
                                             128 * 1024 * 1024 ) );

ASSERT_IS( 0, pthread_create ( & threadData,
                               & threadAttributes,
                               & runthread,
                               NULL ) );

Что касается Мартин Йорк Стековые фреймы:


Возможно, мы с вами думаем о разном?

Когда я думаю о стек кадров, Я думаю о стеке вызовов.Каждая функция или метод имеет свой собственный стек кадров состоящий из обратного адреса, аргументов и локальных данных.

Я никогда не видел никаких ограничений на размер стек кадров.Существуют ограничения на КУЧА в целом, но это все стек кадров комбинированный.

Там хорошая диаграмма и обсуждение стек кадров в Вики.


В заключение:


В Linux/Unix/MacOS/Darwin/BSD можно изменить максимальное значение. КУЧА ограничения размера программно, а также предел(тчш) или ulimit(баш):

struct rlimit  limits;
limits.rlim_cur = RLIM_INFINITY;
limits.rlim_max = RLIM_INFINITY;
ASSERT_IS( 0, setrlimit( RLIMIT_STACK, & limits ) );

Только не пытайтесь установить INFINITY на Mac...И измените его, прежде чем пытаться его использовать.;-)


Дальнейшее чтение:



Инструкции

Push и pop обычно не используются для хранения локальных переменных стекового фрейма. В начале функции кадр стека устанавливается путем уменьшения указателя стека на количество байтов (выровненных по размеру слова), требуемых локальными переменными функции. Это распределяет необходимое количество места в стеке. для этих значений. Затем все локальные переменные доступны через указатель на этот кадр стека ( ebp на x86).

Стек - это большой блок памяти, в котором хранятся локальные переменные, информация для возврата из вызовов функций и т. д. Фактический размер стека значительно варьируется в ОС. Например, при создании нового потока в Windows по умолчанию размер - 1 МБ .

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

Стек не делится на куски целого размера. Это просто массив байтов. Он индексируется как "целое число" типа size_t (не int). Если вы создаете большой объект стека, который помещается в доступное в настоящее время пространство, он просто использует это пространство, увеличивая (или уменьшая) указатель стека.

Как уже отмечали другие, лучше использовать кучу для больших объектов, а не для стека. Это позволяет избежать проблем переполнения стека.

РЕДАКТИРОВАТЬ: Если вы используете 64-разрядное приложение и ваша ОС и библиотеки времени выполнения хороши для вас (см. сообщение mrree), тогда хорошо разместить большие временные объекты на стек. Если ваше приложение 32-битное и / или ваша ОС / библиотека времени выполнения не подходит, вам, вероятно, нужно разместить эти объекты в куче.

Когда вы вводите функцию, стек увеличивается в соответствии с локальными переменными в этой функции. Имеется класс largeObject , который использует, скажем, 400 байт:

void MyFunc(int p1, largeObject p2, largeObject *p3)
{
   int s1;
   largeObject s2;
   largeObject *s3;
}

Когда вы вызываете эту функцию, ваш стек будет выглядеть примерно так (детали могут различаться в зависимости от соглашения о вызовах и архитектуры):

   [... rest of stack ...]
   [4 bytes for p1] 
   [400 bytes for p2]
   [4 bytes for p3]
   [return address]
   [old frame pointer]
   [4 bytes for s1]
   [400 bytes for s2]
   [4 bytes for s3]

См. Соглашения о вызовах x86 для получения дополнительной информации о работе стека. В MSDN также есть несколько хороших диаграмм для нескольких различных конвенций вызовов, с примером кода и получившиеся диаграммы стека .

Как уже говорили другие, не совсем понятно, что вы имеете в виду под "большими объектами" ... Однако, поскольку вы затем спрашиваете

  

Они просто занимают несколько стеков?   & Quot; слоты & Quot;?

Я собираюсь предположить, что вы просто имеете в виду что-то большее, чем целое число. Однако, как заметил кто-то другой, в стеке нет целочисленных слотов. - это просто часть памяти, и каждый байт имеет свой адрес. Компилятор отслеживает каждую переменную по адресу байта first этой переменной - это значение, которое вы получите, если используете оператор address-of ( & amp; var ), и значение указателя является просто этим адресом для некоторой другой переменной. Компилятор также знает, к какому типу относится каждая переменная (вы говорили об этом, когда объявляли переменную), и он знает, какой должен быть каждый тип - когда вы компилируете программу, он делает все, что нужно для вычисления, чтобы выяснить, сколько места у них есть. переменные понадобятся при вызове функции и включают результат этого в код точки входа в функцию (кадр стека, упомянутый PDaddy).

В C и C ++ вы не должны хранить большие объекты в стеке, потому что стек ограничен (как вы уже догадались). Размер стека для каждого потока обычно составляет всего пару мегабайт или меньше (это можно указать при создании потока). Когда вы звоните "новый" чтобы создать объект, он не помещается в стек - вместо этого он помещается в кучу.

Размер стека ограничен. Обычно размер стека устанавливается при создании процесса. Каждый поток в этом процессе автоматически получает размер стека по умолчанию, если не указано иное в вызове CreateThread (). Итак, да: может быть несколько слотов стека, но каждый поток имеет только один. И они не могут быть разделены между потоками.

Если вы поместите в стек объекты, размер которых превышает оставшийся размер стека, вы получите переполнение стека, и ваше приложение вылетит.

Итак, если у вас есть очень большие объекты, разместите их в куче, а не в стеке. Куча ограничена только объемом виртуальной памяти (которая на величину больше стека).

У вас могут быть достаточно массивные (или достаточно многочисленные) объекты, чтобы не было смысла помещать их в стек. В этом случае вы можете поместить объект в кучу и поместить указатель на него в стек. Это разница между передачей по значению и передачей по ссылке.

Как вы определяете большой объект? мы говорим больше или меньше, чем размер выделенного стекового пространства?

например, если у вас есть что-то вроде этого:

void main() {
    int reallyreallybigobjectonthestack[1000000000];
}

В зависимости от вашей системы, вы, скорее всего, получите segfault, потому что просто не хватает места для хранения объекта. В противном случае он хранится как любой другой объект. Если вы разговариваете в реальной физической памяти, вам не нужно об этом беспокоиться, потому что виртуальная память на уровне операционной системы позаботится об этом.

Кроме того, размер стека, скорее всего, не равен размеру целого числа. Он полностью зависит от вашей операционной системы и макета приложений. Виртуальное адресное пространство .

Под «стеком понимается размер целого числа», вы имеете в виду «указатель стека является размером целого числа». Он указывает на вершину стека, который является огромной областью памяти. Ну, больше, чем целое число.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top