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

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

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

Если бы не другие способы решения проблемы потенциального истечения пространства стека?

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

Ответ 1

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

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

Здесь некоторые данные Win32 о распределении стека: MSDN.

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

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

Ответ 2

Функция 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, которая позволит установить ограничения. Но, как упомянуто в других ответах, если вам нужно настроить стек, то, вероятно, вам стоит подумать о своем дизайне. Если вам нужен большой массив, почему бы не взять память из кучи?

Ответ 3

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

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 по каждому потоку.

Ответ 4

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

Ответ 5

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

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);
    }

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

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

возможно, это поможет только для платформы 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.

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

Ответ 9

В Linux вы будете называть getrusage и проверять возвращаемые struct rusage's ru_isrss member (интегральный размер разделенного стека).

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