Я использую MinGW с GCC 3.4.5 (mingw-special vista r3).
В моем приложении C используется много стека, поэтому мне было интересно, могу ли я программно сказать, сколько стека осталось, чтобы я мог аккуратно справиться с ситуацией, если я обнаружу, что у меня скоро закончится.
Если нет, то какими другими способами вы могли бы обойти проблему потенциальной нехватки места в стеке?
Я понятия не имею, с какого размера стека я начну, поэтому мне нужно будет определить это также программно.
Предполагая, что вы знаете размер полного стека, вы, вероятно, могли бы добавить некоторый ассемблерный код для чтения ESP.
Если вы прочитаете ESP и сохраните его в стороне в основной функции, вы можете сравнить текущий ESP с ESP, который у вас есть в main, и увидеть, насколько изменился ESP. Это даст вам представление о том, сколько стека вы использовали.
Удаление адреса локальной переменной из стека подойдет. Затем в более вложенном вызове вы можете вычесть адрес другого локального пользователя, чтобы найти разницу между ними.
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 для каждого потока.
В вашей системе будет размер стека по умолчанию для потоков. В Windows это 1 МБ адресного пространства. Вы можете контролировать это, если создаете свои собственные потоки. Хотя, как указывает Скиз, было бы лучше не беспокоиться о точном лимите!
Это может быть хорошо, в частности, для MinGW. В общем случае не гарантируется, что стек для программы будет непрерывным. Для реализации (например, без виртуальной памяти) допустимо выделять блоки стека по мере необходимости и связывать их вместе. Конечно, если ваша платформа делает это, тогда для программы может даже не быть максимального размера стека по умолчанию: вы можете просто продолжать, пока у вас не закончится свободная память. Но в любом случае хорошая причина установить ограничение - это не дать неконтролируемой рекурсии вывести из строя всю систему за счет исчерпания памяти.
В Linux вы можете получить размер стека с помощью ulimit -a
.
Предупреждения: некоторые платформы (особенно встроенные системы) не выделяют данные в стеке (в стеке хранятся только адреса возврата функций). В этом случае адреса локальных переменных не имеют смысла.
У Раймонда Чена (Старая новая вещь) есть хороший ответ на такого рода вопросы:
If you have to ask, you're probably doing something wrong.
Вот некоторые подробности Win32 о распределении стека: MSDN.
Если вы думаете, что можете быть ограничены пространством стека, вы почти наверняка будете ограничены доступной виртуальной памятью, и в этом случае вам нужно будет найти другое решение.
Что именно ты пытаешься сделать?
Пример (не самый лучший): void subroutine (int i) {char foo [20000]; i ++; if (i <1000) подпрограмма (i); }
Вы правы, это не очень хороший пример. Что я действительно хотел знать, так это то, что вы делали с массивом 20k.
Хотя, если вы когда-либо пытались написать действительно, действительно переносимый код, вы узнаете, что «вы всегда должны спрашивать, и вы всегда делаете что-то не так, потому что не существует переносимой концепции« использования стека », и все же это ответственность не использовать слишком много стека. Так что лучше просто присоединиться к заговору молчания, написать функциональный тест, который, как вы надеетесь, потребляет столько стека, сколько ваша программа когда-либо будет на практике, и оставьте это на усмотрение интегратора платформы ".
Вопрос не в том, нужно ли мне проверять размер стека? это «Как мне проверить размер стека?»
@Justicle: Да, это правда, но ответ, который я дал, все еще в силе, если вам нужно спросить, вы делаете что-то не так - этот сайт о попытках стать лучшими инженерами. Вместо этого OP должен искать другие, более переносимые решения, а не полагаться на непереносимое пространство стека - например, с использованием динамического распределения и хранения только указателей в стеке. При программировании всегда следует учитывать худший случай. Обработка ошибок при сбое динамического распределения намного проще, чем обработка ошибок вне стека.
Я просто хочу запросить размер стека, чтобы ответить на вопрос «помещаются ли в стек массивы с областью видимости файла?» Я предполагаю, что нет, но, создав две простые программы, которые запрашивают размер стека, одну с массивом с файловой областью, а другую без массива, я могу ответить на этот вопрос сам.
Это проблема, от которой я отказался. С помощью множества взломов и (в основном) молитв вы можете получить решение, которое будет работать в данный момент на данной машине. Но в целом, похоже, нет достойного способа сделать это.
Вам нужно будет получить позицию и размер стека вне вашей программы (в Linux вы можете получить это из /proc/<pid>/maps
). В своей программе вы должны каким-то образом проверить, где вы находитесь в стеке. Использование локальных переменных возможно, но нет реальной гарантии, что они действительно находятся в стеке. Вы также можете попытаться получить значение из регистра указателя стека с помощью некоторой сборки.
Итак, теперь у вас есть расположение стека, его размер и текущее положение, и вы предполагаете, что знаете, в каком направлении растет стек. Когда вы переходите в режим переполнения стека? Лучше не делать это ближе к концу, потому что ваша оценка (то есть адрес локальной переменной или значение из указателя стека), вероятно, слишком оптимистична; нередко адресация памяти выходит за пределы указателя стека. Кроме того, вы не имеете ни малейшего представления о том, сколько места в стеке требуется той или иной функции (и функциям, которые она вызывает). Так что в конце вам придется оставить немного места.
Я могу только посоветовать вам не ввязываться в эту неразбериху и стараться избегать очень глубокой рекурсии. Вы также можете увеличить размер стека; Я считаю, что в Windows вам нужно скомпилировать это в исполняемый файл.
проверьте, поддерживает ли ваш компилятор stackavail ()
возможно, это поможет только для платформы 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.
Если вы рассматриваете возможность попробовать это, дайте мне знать, и я помогу вам. Есть способ получить размер стека, используемого в определенной точке.
В Linux вы должны вызвать getrusage и проверить возвращаемый struct rusage член ru_isrss (размер целого неразделенного стека).
Судя по отслеживанию патчей на сайте MINGW и его сайте sourceforge, я вижу, что в мае 2008 года были внесены некоторые исправления, связанные с getrusage, и похоже, что оно в целом поддерживалось довольно долгое время. Вам следует внимательно проверить наличие каких-либо оговорок в отношении того, какая часть типичных функций Linux поддерживается MinGW.
Это лучший способ сделать это, если только вы не делаете какое-то безумное статически распределенное сопоставление. Нельзя сказать, что все статически назначенные сопоставления безумны, но обычно это так :)
getrusage()
не работает с размером стека в Linux. «ru_isrss (unmaintained) This field is currently unused on Linux.
» (linux.die.net/man/2/getrusage). Не знаю, когда это произошло, но это верно для ядра 2.6.28.
Функция 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
, которая позволяет устанавливать ограничения.
Но, как упоминалось в других ответах, если вам нужно настроить стек, возможно, вам следует пересмотреть свой дизайн. Если вам нужен большой массив, почему бы не взять память из кучи?
getrusage()
не работает с размером стека в Linux. «ru_isrss (unmaintained) This field is currently unused on Linux.
» (linux.die.net/man/2/getrusage). Не знаю, когда это произошло, но это верно для ядра 2.6.28.
@phoxis: getrlimit (RLIMIT_STACK, &limit)
, похоже, дает общий размер стека, а не размер оставшегося свободного стека.
@ user2284570: Из man getrlimit
я вижу, что написано «Максимальный размер стека процесса в байтах». . Можете ли вы уточнить, что заставляет вас думать, что это может быть оставшийся размер стека?
@phoxis: Это то, что я говорю. Это общий размер стека. А в случае ᴏᴘ полезно получить только оставшееся.
Для 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#введите описание ссылки здесь
Мне нравится этот ответ, но, не зная заранее размер стека, я не могу сказать, собираюсь ли я его взорвать.