Подтвердить что ты не робот

Как C и С++ хранят большие объекты в стеке?

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

4b9b3361

Ответ 1

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

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

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

03: par2 byte2
02: par2 byte1
01: par1

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

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

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

Ответ 2


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


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

Но это уже не 1985 год.

В эти дни я запускаю Linux!

Мой стекирование по умолчанию ограничено 10 МБ. Мое значение heapsize по умолчанию не ограничено. Это довольно тривиально, чтобы не ограничивать это. (* cough * [tcsh] unlimit stacksize * cough *. Или setrlimit().)

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

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

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

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

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

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


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

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


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

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

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




ИЗМЕНЕНЫ ДОБАВИТЬ:


Человек, я испорчен!

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

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

... Это будет долго. Медведь со мной...


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

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


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

Практически говоря, сочетание STACK + HEAP обычно ограничено где-то между 2-4 ГБ, поскольку другие вещи должны отображаться там.

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


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

Это различие (в tcsh) "предела" против "limit -h". Или (в bash) "ulimit -Sa" против "ulimit -Ha". Или, программно, rlim_cur vs rlim_max в struct rlimit.


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

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

Конечно, его код не будет работать на его Mac по умолчанию. Но он будет работать отлично, если он сначала вызовет "unlimit stacksize" (tcsh) или "ulimit -Ss unlimited" (bash).


СЕРДЦЕ МАТЕРИИ:


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

Таким образом, ограничений по размеру STACK и HEAP в Linux не существует, кроме искусственных...


НО:


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


Обычно вы считаете, что идеализированная система имеет STACK на одном конце адресного пространства памяти, HEAP на другом, и они строятся друг к другу, Когда они встречаются, у вас не хватает памяти.

Маки, похоже, придерживаются своих общих системных библиотек между ними с фиксированным смещением, ограничивающим обе стороны. Вы все еще можете запустить Мартин Йорк код с "unlimit stacksize", поскольку он выделяет только что-то вроде 8 MiB (< 64 MiB) данных. Но у него закончится STACK задолго до того, как у него закончится HEAP.

Я нахожусь в Linux. Я не буду. Извините, малыш. Здесь Никель. Пойдите, чтобы получить лучшую ОС.

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

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


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


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

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

Напротив, поскольку HEAP распределяется и освобождается, вы заканчиваете с неиспользуемыми отверстиями памяти. Они могут постепенно привести к увеличению объема памяти с течением времени. Не то, что мы обычно подразумеваем под основной утечкой, но результаты схожи.

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


Что вызывает SWAP THRASHING:


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

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

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

С другой стороны, если бы эти небольшие выделения были сделаны на STACK, все они были бы расположены в непрерывном участке памяти. Необходимо будет загружать меньше страниц памяти VM. (4 + 8 +... < 2k для победы.)

Sidenote: Моя причина привлечь внимание к этому связана с определенным инженером-электриком, которого я знал, кто настаивал на том, чтобы все массивы были выделены на HEAP. Мы делали матричную математику для графики. A * LOT * из 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 ) );

Что касается рамок Мартина Йорка:


Возможно, мы с тобой думаем о разных вещах?

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

Я никогда не видел никаких ограничений на размер фрейма стека. Есть ограничения на STACK в целом, но все комбинации стека объединены.

Там есть хорошая диаграмма и обсуждение стековых фреймов на Wiki.


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


В Linux/Unix/MacOS/Darwin/BSD можно программно изменить максимальные ограничения размера STACK, а также ограничение (tcsh) или ulimit (bash):

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

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


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



Ответ 3

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

Ответ 4

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

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

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

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

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

Ответ 5

Всякий раз, когда вы вводите функцию, стек растет, чтобы соответствовать локальным переменным в этой функции. Учитывая класс 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 также имеет несколько хороших диаграмм для нескольких различных конвекций вызовов, Пример кода и итоговые диаграммы стека.

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

В разделе "stack is size of integer" вы имеете в виду "указатель стека - это целое число". Он указывает на вершину стека, которая является огромной областью памяти. Ну, больше, чем целое число.

Ответ 10

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

Ответ 11

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

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

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

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

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