Проверка доступного размера стека в C

Я использую MinGW с GCC 3.4.5 (mingw-special vista r3).

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

Если нет, то какими еще способами вы бы обошли проблему потенциальной нехватки места в стеке?

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


person Paul Hargreaves    schedule 10.09.2008    source источник


Ответы (9)


У Раймонда Чена (The Old New Thing) есть хороший ответ на такой вопрос:

Если вам нужно спросить, вы, вероятно, делаете что-то не так.

Вот некоторые сведения о распределении стека в Win32: MSDN.

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

Что именно ты пытаешься сделать?

person Skizz    schedule 10.09.2008
comment
Пример (не самый лучший): void subroutine (int i) {char foo [20000]; я ++; if (i ‹1000) подпрограмма (i); } - person Paul Hargreaves; 10.09.2008
comment
Вы правы, это не очень хороший пример. Что я действительно хотел знать, так это то, что вы делали с массивом 20k. - person Skizz; 10.09.2008
comment
Хотя, если вы когда-либо пытались написать действительно, действительно переносимый код, вы узнаете, что вам всегда нужно спрашивать, и вы всегда делаете что-то не так, потому что не существует переносимой концепции использования стека, и все же программист обязан не использовать слишком много стека. Так что лучше всего просто присоединиться к заговору молчания, написать функциональный тест, который, как вы надеетесь, потребляет столько стека, сколько ваша программа когда-либо будет на практике, и оставить это на усмотрение интегратора платформы. - person Steve Jessop; 02.11.2009
comment
Вопрос не в том, следует ли мне проверять размер стека? это Как мне проверить размер стека? - person Justicle; 20.05.2018
comment
@Justicle: Да, это правда, но ответ, который я дал, все еще в силе, если вам нужно спросить, вы делаете что-то не так - этот сайт о попытках стать лучшими инженерами. Вместо этого OP должен искать другие, более переносимые решения, а не полагаться на непереносимое пространство стека - например, с использованием динамического распределения и хранения только указателей в стеке. При программировании всегда следует учитывать худший случай. Обработка ошибок при сбое динамического распределения намного проще, чем обработка ошибок вне стека. - person Skizz; 25.05.2018
comment
Я просто хочу запросить размер стека, чтобы ответить на вопрос, помещаются ли в стек массивы с файловой областью? Я предполагаю, что нет, но, создав две простые программы, которые запрашивают размер стека, одну с массивом с файловой областью, а другую без массива, я могу ответить на этот вопрос сам. - person twitchdotcom slash KANJICODER; 18.04.2020
comment
@twitchdotcomslashKANJICODER: вы можете использовать оператор адресации для переменных, выделенных стеком, чтобы ответить на этот вопрос. - person Skizz; 06.05.2021

Функция getrusage дает вам текущее использование. (см. man getrusage).

getrlimit в Linux поможет получить размер стека с параметром RLIMIT_STACK.

#include <sys/resource.h>
int main (void)
{
  struct rlimit limit;

  getrlimit (RLIMIT_STACK, &limit);
  printf ("\nStack Limit = %ld and %ld max\n", limit.rlim_cur, limit.rlim_max);
}

Взгляните на man getrlimit. Та же самая информация может быть получена строкой размера стека ulimit -s или ulimit -a. Также обратите внимание на функцию setrlimit, которая позволяет устанавливать ограничения. Но, как упоминалось в других ответах, если вам нужно настроить стек, возможно, вам следует пересмотреть свой дизайн. Если вам нужен большой массив, почему бы не взять память из кучи?

person phoxis    schedule 03.05.2011
comment
getrusage() не работает для размера стека в Linux. ru_isrss (unmaintained) This field is currently unused on Linux. (linux.die.net/man/2/getrusage). Не знаю, когда это произошло, но это верно для ядра 2.6.28. - person atzz; 13.01.2012
comment
@phoxis: getrlimit (RLIMIT_STACK, &limit) похоже, дает общий размер стека, а не размер оставшегося свободного стека. - person user2284570; 19.07.2016
comment
@ user2284570: Из man getrlimit я вижу, что написано Максимальный размер стека процесса в байтах. . Не могли бы вы уточнить, что заставляет вас думать, что это может быть оставшийся размер стека? - person phoxis; 21.07.2016
comment
@phoxis: Это то, что я говорю. Это общий размер стека. А в случае ᴏᴘ полезно получить только оставшееся. - person user2284570; 22.07.2016

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

size_t top_of_stack;

void Main()
{
  int x=0;
  top_of_stack = (size_t) &x;

  do_something_very_recursive(....)
}

size_t SizeOfStack()
{
  int x=0;
  return top_of_stack - (size_t) &x;
} 

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

person Rob Walker    schedule 10.09.2008
comment
Мне нравится этот ответ, но, не зная заранее размер стека, я не могу сказать, собираюсь ли я его взорвать. - person Paul Hargreaves; 10.09.2008
comment
В вашей системе будет размер стека по умолчанию для потоков. В Windows это 1 МБ адресного пространства. Вы можете контролировать это, если создаете свои собственные потоки. Хотя, как отмечает Скиз, было бы лучше не беспокоиться о точном лимите! - person Rob Walker; 10.09.2008
comment
Это может быть хорошо, в частности, для MinGW. В общем случае не гарантируется, что стек для программы будет непрерывным. Для реализации (например, без виртуальной памяти) допустимо выделять блоки стека по мере необходимости и связывать их вместе. Конечно, если ваша платформа делает это, тогда для программы может даже не быть максимального размера стека по умолчанию: вы можете просто продолжать, пока у вас не закончится свободная память. Но в любом случае хорошая причина установить ограничение - это предотвратить неконтролируемую рекурсию, приводящую к вылету всей системы за счет исчерпания памяти. - person Steve Jessop; 02.11.2009
comment
В Linux вы можете получить размер стека с помощью ulimit -a. - person Mark Lakata; 29.06.2018
comment
Предупреждения: некоторые платформы (особенно встроенные системы) не выделяют данные в стеке (в стеке хранятся только адреса возврата функций). В этом случае адреса локальных переменных не имеют смысла. - person Mark Lakata; 29.06.2018

проверьте, поддерживает ли ваш компилятор stackavail ()

person dmityugov    schedule 10.09.2008

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

person Nathan Fellman    schedule 10.09.2008

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

Вам нужно будет получить позицию и размер стека вне вашей программы (в Linux вы можете получить их из /proc/<pid>/maps). В своей программе вы должны каким-то образом проверить, где вы находитесь в стеке. Использование локальных переменных возможно, но нет реальной гарантии, что они действительно находятся в стеке. Вы также можете попытаться получить значение из регистра указателя стека с помощью некоторой сборки.

Итак, теперь у вас есть расположение стека, его размер и текущее положение, и вы предполагаете, что знаете, в каком направлении растет стек. Когда вы переходите в режим переполнения стека? Лучше не делать это ближе к концу, потому что ваша оценка (то есть адрес локальной переменной или значение из указателя стека), вероятно, слишком оптимистична; нередко адресация памяти выходит за пределы указателя стека. Кроме того, вы не имеете ни малейшего представления о том, сколько места в стеке требуется той или иной функции (и функциям, которые она вызывает). Так что в конце вам придется оставить немного места.

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

person mweerden    schedule 10.09.2008

возможно, это поможет только для платформы Windows:

в заголовке PE (IMAGE_NT_HEADERS) вашего exe есть такие записи, как:


typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

typedef struct _IMAGE_OPTIONAL_HEADER {
    ...
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    ...
}

Есть простой способ получить эти значения: использование GetModuleHandle (NULL) предоставит вам базу изображений (дескриптор) вашего модуля, адрес, по которому вы найдете структуру IMAGE_DOS_HEADER, которая поможет вам найти структуру IMAGE_NT_HEADERS (imagebase + IMAGE_DOS_HEADER. e_lfanew) -> IMAGE_NT_HEADERS, и там вы найдете эти поля: SizeOfStackReserve и SizeOfStackCommit.

Максимальный объем пространства, который ОС будет выделять для вашего стека, равен SizeOfStackReserve.

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

person botismarius    schedule 11.09.2008
comment
Значения в заголовке PE используются только для входящего потока. Любой другой поток может быть создан с выбором вызывающей стороны либо значения по умолчанию для всего процесса, либо любого другого размера. - person Ben Voigt; 15.04.2021

Для Windows: я сделал это перед использованием функции VirtualQuery из Kernel32.dll. У меня есть только пример на C #, но он демонстрирует технику:

public static class StackManagement
    {
        [StructLayout(LayoutKind.Sequential)]
        struct MEMORY_BASIC_INFORMATION
        {
            public UIntPtr BaseAddress;
            public UIntPtr AllocationBase;
            public uint AllocationProtect;
            public UIntPtr RegionSize;
            public uint State;
            public uint Protect;
            public uint Type;
        };

        private const long STACK_RESERVED_SPACE = 4096 * 16;

        public unsafe static bool CheckForSufficientStack(UInt64 bytes)
        {
            MEMORY_BASIC_INFORMATION stackInfo = new MEMORY_BASIC_INFORMATION();
            UIntPtr currentAddr = new UIntPtr(&stackInfo);
            VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION));

            UInt64 stackBytesLeft = currentAddr.ToUInt64() - stackInfo.AllocationBase.ToUInt64();

            return stackBytesLeft > (bytes + STACK_RESERVED_SPACE);
        }

        [DllImport("kernel32.dll")]
        private static extern int VirtualQuery(UIntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
    }

Кстати: этот код также можно найти в StackOverflow по другому вопросу, который я задал, когда пытался исправить ошибку в коде: Арифметическая операция привела к переполнению в небезопасном C # введите описание ссылки здесь

person Daniel James Bryars    schedule 03.01.2012

В Linux вы должны вызвать getrusage и проверить возвращенный член ru_isrss struct rusage (полный размер неразделенного стека).

Судя по отслеживанию исправлений на сайте MINGW и его сайте sourceforge, я вижу, что в мае 2008 года были внесены некоторые исправления для getrusage, и похоже, что они в целом поддерживаются довольно долгое время. Вам следует внимательно проверить наличие каких-либо оговорок в отношении того, какая часть типичных функций Linux поддерживается MinGW.

person Thomas Kammeyer    schedule 12.09.2008
comment
Это лучший способ сделать это, если только вы не делаете какое-то безумное статически распределенное сопоставление. Нельзя сказать, что все статически назначенные сопоставления безумны, но обычно это так :) - person Tim Post♦; 26.04.2011
comment
getrusage() не работает для размера стека в Linux. ru_isrss (unmaintained) This field is currently unused on Linux. (linux.die.net/man/2/getrusage). Не знаю, когда это произошло, но это верно для ядра 2.6.28. - person atzz; 13.01.2012