Существуют определенные условия, которые могут вызвать переполнение стека в системе Linux x86:
struct my_big_object[HUGE_NUMBER] в стеке. Прохождение через него в конечном итоге вызывает SIGSEGV.alloca() (похожа на malloc(), но использует стек, автоматически освобождается, а также взрывается с помощью SIGSEGV, если он слишком большой). Обновление: alloca () формально не устарела, как я изначально заявил; это просто не рекомендуется.Есть ли способ программно определить, достаточно ли велик локальный стек для данного объекта? Я знаю, что размер стека регулируется с помощью ulimit, поэтому я надеюсь, что есть способ (каким бы непереносимым он ни был). В идеале я бы хотел сделать что-то вроде этого:
int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
char *foo = alloca(object_size);
do_stuff(foo);
}
else
{
char *foo = malloc(object_size);
do_stuff(foo);
free(foo);
}
Да, alloca () изначально была в функции, вызываемой из многих потоков.





The deprecated alloca() routine (like malloc(), but uses the stack, automatically frees itself, and also blows up with SIGSEGV if it's too big).
Почему alloca устарела?
Во всяком случае, насколько быстрее в вашем случае alloca vs malloc? (Стоит ли оно того?)
И разве вы не вернете null из alloca, если не хватит места? (так же, как malloc?)
А когда происходит сбой вашего кода, где он падает? это в alloca или в doStuff ()?
/ Йохан
(1) на странице руководства GNU сказано не использовать его. (2) alloca выполняется в постоянное время, тогда как malloc не является детерминированным и может включать системный вызов и блокировку потоков. (2) Если alloca вызывает переполнение стека, поведение не определено (происходит ошибка при использовании, а не при использовании alloca).
однако возвращение null, обеспечиваемое malloc, часто является лишь ложной защитой: malloc в linux for действительно возвращает ненулевое значение и выдает сбой при использовании памяти. вам сначала нужно переключить некоторые биты в ядре, чтобы это изменить (см. man malloc)
Приносим извинения, если это констатирует очевидное, но вы можете легко написать функцию для проверки определенного размера выделения стека, просто попробовав alloca (такого размера) и перехватив исключение переполнения стека. Если вы хотите, вы можете поместить его в функцию с некоторой заранее определенной математикой для накладных расходов стека функций. Например:
bool CanFitOnStack( size_t num_bytes )
{
int stack_offset_for_function = 4; // <- Determine this
try
{
alloca( num_bytes - stack_offset_for_function );
}
catch ( ... )
{
return false;
}
return true;
}
И даже если бы это был C++, не существует стандартного независимого от платформы механизма для запуска исключения при переполнении стека.
На самом деле это было бы выполнимо - не так, как вы описываете, а осторожно используя обработчик SIGSEGV очень.
Хорошие моменты; Я пропустил, что это C. Мне просто пришло в голову, что использование самого обработчика исключений может быть самым простым способом, так сказать, из точки A в точку B. :)
alloca () будет возвращать NULL в случае сбоя, я считаю, что поведение alloca (0) не определено и является вариантом платформы. Если вы проверите это до do_something (), вы никогда не попадете под удар SEGV.
У меня есть пара вопросов:
Вопрос интересный, но поднимает бровь. Он поднимает стрелку моего квадратного колышка-круглого отверстия-о-метра.
(1) Размер стека на машинах, на которые я смотрю, настроен на гораздо меньший, чем 8 МБ. (2) Обмен страниц определенно вызывает беспокойство, хотя теперь, когда вы упомянули об этом, может быть, мне просто лучше предварительно выделить и mlock () ing.
alloca вызывает неопределенное поведение при переполнении стека. он не возвращает 0 в соответствии с его справочной страницей
Сам alloca () зависит от платформы. :)
Вы можете определить пространство стека, доступное процессу, найдя размер пространства стека процесса, а затем вычтя использованное количество.
ulimit -s
показывает размер стека в системе Linux. Для программного подхода см. getrlimit (). Затем, чтобы определить текущую глубину стека, вычтите указатель на верх стека из единицы в нижнюю. Например (непроверенный код):
unsigned char *bottom_of_stack_ptr;
void call_function(int argc, char *argv) {
unsigned char top_of_stack;
unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ?
&top_of_stack-bottom_of_stack_ptr :
bottom_of_stack_ptr-&top_of_stack;
if ( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
...
}
}
int main(int argc, char *argv) {
unsigned char bottom_of_stack;
bottom_of_stack_ptr = &bottom_of_stack;
my_function();
return 0;
}
Это правильно? Bottom_of_stack не может быть реальным концом стека, верно? Разве глобальные переменные не помещаются в стек плюс другой мусор, который, по мнению компилятора, ему нужен?
ulimit -s и getrlimit (RLIMIT_STACK) сообщают вам только размер начального потока. Он ничего вам не скажет, если вы не знаете, что работаете в начальном потоке.
У глобалов обычно есть собственное пространство. Код запуска может добавить глубину стека, поэтому приведенный выше код добавляет к глубине хороший фактор выдумки на всякий случай. Да, RLIMIT_STACK применяется только к начальному стеку, однако pthread позволяет получить и установить размер стека.
Даже если это не прямой ответ на ваш вопрос, я надеюсь, вы знаете о существовании Valgrind - замечательного инструмента для обнаружения таких проблем во время выполнения в Linux.
Что касается проблемы стека, вы можете попытаться динамически выделить объекты из фиксированного пула, который обнаруживает эти переполнения. С помощью простого мастера макросов вы можете выполнить этот запуск во время отладки, с реальным кодом, работающим во время выпуска, и, таким образом, знать (по крайней мере, для сценариев, которые вы выполняете), что вы не берете слишком много. Вот дополнительная информация и ссылка к примерной реализации.
Я знаю valgrind, и это мне не помогает в этом вопросе.
Функция alloca устарела. Однако его нет в POSIX, и он также зависит от машины и компилятора. На странице руководства Linux по alloca отмечается, что «для некоторых приложений его использование может повысить эффективность по сравнению с использованием malloc, а в некоторых случаях оно также может упростить освобождение памяти в приложениях, которые используют longjmp () или siglongjmp (). В противном случае, его использование не рекомендуется ".
В справочной странице также говорится, что «нет индикации ошибки, если кадр стека не может быть расширен. Однако после неудачного распределения программа, скорее всего, получит SIGSEGV».
Производительность malloc фактически упоминалась в Подкаст Stackoverflow # 36.
(Я знаю, что это неправильный ответ на ваш вопрос, но я все равно подумал, что это может быть полезно.)
Спасибо, я проверю этот подкаст.
Не уверен, что это применимо к Linux, но в Windows можно столкнуться с нарушениями доступа с большими выделениями стека даже если им это удастся!
Это связано с тем, что по умолчанию VMM Windows фактически отмечает только несколько верхних (не знаю, сколько именно) 4096-байтовых страниц ОЗУ стека как выгружаемые (т. Е. Поддерживаемые файлом подкачки), поскольку он считает, что доступ к стеку обычно идет вниз от вершина; по мере того, как доступ становится все ближе и ближе к текущей «границе», нижняя и нижняя страницы помечаются как листаемые. Но это означает, что раннее чтение / запись в память намного ниже вершины стека вызовет нарушение прав доступа, поскольку эта память фактически еще не выделена!
Linux тоже делает то же самое. Вы можете malloc () использовать много больших фрагментов, и у вас не закончится место, пока вы не начнете использовать всю эту память.
Убийца OOM? Связанные, но разные, я думаю. По умолчанию Linux позволяет распределению куча успешно возвращаться, когда своп исчерпан; Я считаю, что в этой ситуации Windows VMM рано или поздно выйдет из строя. Я нахожу сомнительным поведение Windows куча ... :)
Вы имеете в виду, что OOM killer можно отключить? Я не знаю, как отключить поведение стека Windows ... Может быть, есть переключатель, который вы можете поставить во время компоновки?
Я не могу придумать ничего хорошего. Может быть, это возможно с помощью getrlimit () (предложенного ранее) и некоторой арифметики указателей? Но сначала спросите себя, действительно ли вы этого хотите.
void *closeToBase;
main () {
int closeToBase;
stackTop = &closeToBase;
}
int stackHasRoomFor(int bytes) {
int currentTop;
return getrlimit(...) - (¤tTop - closeToBase) > bytes + SomeExtra;
}
Лично я бы этого не сделал. Размещайте большие вещи в куче, стек не предназначен для этого.
Вы не очень много говорите о том, почему вы хотите выделять память в стеке, но если вам нравится модель памяти стека, вы также можете реализовать выделение стека в куче. Выделите большой кусок памяти в начале программы и сохраните стек указателей на него, который будет соответствовать кадрам в обычном стеке. Вам просто нужно не забыть вывести указатель личного стека при возврате функции.
Я хочу избежать выделения кучи (что может быть дорогостоящим). Я считаю, что предварительное выделение статического буфера для каждого потока будет работать так же хорошо.
Некоторые компиляторы, например Откройте Watcom C / C++, поддерживают функцию stackavail (), которая позволяет вам делать именно это.
Вы можете использовать GNU libsigsegv для ручка ошибки страницы, включая случаи, когда происходит переполнение стека (с его веб-сайта):
In some applications, the stack overflow handler performs some cleanup or notifies the user and then immediately terminates the application. In other applications, the stack overflow handler longjmps back to a central point in the application. This library supports both uses. In the second case, the handler must ensure to restore the normal signal mask (because many signals are blocked while the handler is executed), and must also call sigsegv_leave_handler() to transfer control; then only it can longjmp away.
Читая страницу libsigsegv, я смущен тем, что на ней не упоминается, казалось бы, глубокая невозможность быть уверенным, что программа может продолжаться осмысленно после переполнения стека. Что, если переполнение произошло внутри чего-то вроде malloc () в середине работы с кучей? Что, если переполнение произошло во внутренней функции поддержки, внедренной компилятором, где вы даже не видите вызова функции? Помимо попытки продолжить бег, я также скептически относился бы к тому, что немного дало результат и завершилось - что в этом проверенном наборе «маленьких» вещей, которые вам обещают, что вы можете сделать? : - /
@Hostile Я не помню справочную страницу после стольких лет, но я не понимаю, почему вы не могли продолжить то, что делали, если страница, которая не была отображена до ошибки, затем стала доступной. При записи в память после разветвления (копирование при записи) все время происходят небольшие сбои, и это работает довольно хорошо.
Но IIRC, в настоящее время вы можете вызвать userfaultfd в Linux, чтобы «создать файловый дескриптор для обработки ошибок страниц в пользовательском пространстве», что казалось бы более чистым, чем подключение к обработчику сигналов.
Я использовал копирование при записи с MMF, но это кажется другим. Механически невозможно расширить стек C и продолжить работу. Итак, если у вас был void *malloc(size_t size) { /* fiddle heap */ helper() /* finish fiddling heap */ return p; }, и переполнение происходит во время helper() ... все, что вы можете сделать, это предоставить небольшой объем памяти, используемый для стека во время обработчика сигнала, который должен завершаться или longjmp. Ничто не запустит финализацию, поэтому куча может быть повреждена. Компиляторы иногда реализуют «примитивы» с вспомогательными функциями, поэтому это кажется рискованным, даже когда это «весь ваш собственный код». Верно?
@hostile Я понимаю, о чем вы сейчас говорите. Я согласен. Если стек попадает в кучу, и вы не можете знать, какая функция может быть активной и что она делает, это фатально.
Конец области стека определяется ОС динамически. Хотя вы можете найти «статические» границы стека, просмотрев области виртуальной памяти (VMA) в зависимости от ОС (см. Файлы stackvma * в libsigsegv / src /), вам дополнительно придется учитывать
Это случайно происходит в ветке? Я попытался воспроизвести segfault, но не получил ничего, кроме NULL из alloca () при попытке очень больших размеров.