Как организована память стека?

Я просто хочу убедиться, что все правильно. Представьте, что размер стека для любой программы на моей машине равен 800 байт (просто пример). Затем, следующий код:

void f()
{
char x[300];
char y[300];
char z[300];
char t[300];
}

должен переполнять стек вправо (потому что 1200 > 800)?

Теперь мой вопрос заключается в следующем подходе способ Ok победить вышеупомянутую проблему?

void f()
{
   f1();
   f2();
}

где:

void f1()
{
    char x[300];
    char y[300];
}

void f2()
{
    char z[300];
    char t[300];
}

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

пс. Я не имею в виду "другие" данные, которые могут занимать место в стеке для этого примера.

ПФС. Другими словами, так ли это, что каждый раз каждая функция f(), f1() и f2() получает свой "экземпляр" стека?

Ответ 1

ПФС. Другими словами, так ли это, что каждый раз каждая функция f(), f1() и f2() получает свой "экземпляр" стека?

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

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

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

Edit

Следует отметить, что вышеприведенное описание заключается в том, как компиляторы обычно реализуют локальные переменные. В стандарте C не указывается, что стеки должны использоваться, но это лучшее решение (до сих пор) для сред, в которых неизвестное количество процессов/потоков должно делиться ограниченным объемом памяти.

Изменить 2

Разделение f на f1 и f2 является одним из способов уменьшения потребления памяти стека. Другими способами являются выделение памяти в куче (с помощью malloc/free) или статически (статическое распределение не является потокобезопасным, поэтому оно снижает переносимость и повторное использование и должно использоваться только в качестве последнего средства).

Скажем, у меня была одна локальная переменная, определенная в f(), что произойдет с ней, когда программа введена внутри f1()?

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

Ответ 2

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

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

Стандарт C очень специфичен в том, что он не обращается к таким вещам, оставляя его для реализации. Найдите стандарт C99, и вы найдете слово "стек" ровно в нулевое время. Это не случайно.

Тем не менее, если бы вы сами разрабатывали простую реализацию, то рассмотрение того, что вас интересует, будет иметь первостепенное значение в дизайне, но если вы не идете по этой дороге (или ядро ​​/драйвер/встроенная боковая улица, где вы уже знаете ответ на платформе), просто нет конкретного способа сказать "он работает таким образом...". Ваша реализация, скорее всего, просто будет делать, как вы описали, стек пресловутой пластины в буфете. Но это никоим образом не может быть использовано для завершения всех (или даже большинства) реализаций. До тех пор, пока они соблюдают правила, установленные стандартом относительно автоматических переменных, остальное буквально является дьяволом в деталях.

Ответ 3

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

Никаких данных не будет (вы сохранили в char x [300] char y [300]) после того, как функция f1() завершает выполнение

Ответ 4

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

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

main

поэтому, когда он пытается запустить f1(), для которого требуется выделение памяти, которое не превышает доступную память, поэтому оно будет успешно выполнено.

f1

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

f2

Итак, ваша программа будет успешно выполнена.