автоматическое / статическое выделение памяти

Может быть, наивный вопрос, но ...

Подтвердить или отклонить:

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

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


person Armen Tsirunyan    schedule 07.12.2010    source источник
comment
Хотя это можно подтвердить во время компиляции для статических объектов, это невозможно сделать для автоматических, потому что это зависит от логики программы.   -  person ruslik    schedule 07.12.2010
comment
Формулировка этого вопроса наверняка звучит как вопрос о домашнем задании / экзамене, но я не могу поверить, что кто-то с 8k репутацией просил бы SO сделать его домашнее задание. :) Мне любопытно, откуда это взялось.   -  person R.. GitHub STOP HELPING ICE    schedule 07.12.2010
comment
@R .. Это получилось так: когда вы пытаетесь выделить слишком большой автоматический массив, вы получаете ошибку компиляции, но когда вы пытаетесь выделить большой динамический массив, вы получаете ошибку времени выполнения. Итак, я подумал, возможно ли, что я получу ошибку времени выполнения для первого случая, даже если компиляция завершится успешно :)   -  person Armen Tsirunyan    schedule 07.12.2010
comment
@Armen: ожидаете ли вы ошибки компиляции для этого: int f(){int t=f();};?   -  person ruslik    schedule 07.12.2010
comment
@ruslik: Нет, конечно, нет. Хотя, скорее всего, я получу предупреждение   -  person Armen Tsirunyan    schedule 07.12.2010
comment
@Armen: ну, в простых случаях компилятор умеет избегать рекурсивных вызовов. Но когда глубина рекурсии неизвестна во время компиляции, он не сможет вас предупредить. Кроме того, во многих случаях компилятор даже не знает размер стека.   -  person ruslik    schedule 07.12.2010


Ответы (6)


Два слова: Stack Overflow. :П

person Prasoon Saurav    schedule 07.12.2010
comment
Должен сказать, я не мог поверить, что этот ответ получил два положительных голоса. А потом у меня получилось три. - person Billy ONeal; 07.12.2010
comment
@Prasoon: КАК я мог отказаться от этого ?! :) - person Armen Tsirunyan; 07.12.2010
comment
@Prasoon: извините за неправильное написание. Я назначу встречу, снимут ногу соо рта на следующей неделе :) - person Billy ONeal; 07.12.2010

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

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

person Michael Burr    schedule 07.12.2010
comment
Обнаружение сбоя автоматического выделения и обработка его, включая то, что вы не можете его предсказать переносимо. Программа int main() { int a = 1; int b = 2; return a + b;} не будет строго соответствовать, поскольку она полагается на поведение, которое может различаться между реализациями (в частности, на то, что имеется достаточно стека для двух целых чисел), за исключением того, что формально это поведение не меняется, поскольку в стандарте не упоминается вопрос вообще. Напротив, это выходит за рамки стандарта, работает ли какая-либо программа, прерывается каким-либо образом во время выполнения или удаляет кучу. - person Steve Jessop; 07.12.2010

В системах с избыточной фиксацией (например, Linux в конфигурации по умолчанию) для объектов со статической продолжительностью хранения возможно даже сбой во время выполнения. При запуске программы эти объекты будут существовать либо в нулевых страницах для копирования при записи (если они не инициализированы), либо в сопоставлениях «копирование при записи» исполняемого файла на диске. При первой попытке записи в них произойдет сбой страницы, и ядро ​​сделает локальную модифицируемую копию для вашего процесса. Если ядро ​​было небрежно и не зарезервировало столько памяти, сколько было выделено для процесса, это может потерпеть неудачу, и результатом станет страшный убийца OOM.

Этой проблемы нет ни в одной надежной системе, и поведение Linux можно исправить следующим образом:

echo "2" > /proc/sys/vm/overcommit_memory
person R.. GitHub STOP HELPING ICE    schedule 07.12.2010
comment
+1: Нестандартное поведение, принесенное вам Linux! Спасибо за то, что сделали невозможным создание соответствующей реализации C с использованием настроек по умолчанию. * Билл устанавливает BSD. - person Billy ONeal; 07.12.2010
comment
Я почти уверен, что overcommit - тоже традиционное поведение BSD, но могу ошибаться. Я просто недостаточно хорошо знаю различные BSD, чтобы комментировать, поэтому я не стал их упоминать. - person R.. GitHub STOP HELPING ICE; 07.12.2010

Не правда. Автоматическое выделение может вызвать переполнение стека, что вызывает мгновенное завершение процесса на большинстве известных мне архитектур / платформ.

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

person Billy ONeal    schedule 07.12.2010
comment
Если вам повезет, это вызовет мгновенное прекращение. Если вам не повезло, он уничтожает другие части памяти и дает злоумышленнику root-доступ. :-) К счастью, последнее случается только на практике, если вы размещаете в стеке безумно большие (много килобайт) объекты, но небезопасное использование C99 VLA может привести к этому. - person R.. GitHub STOP HELPING ICE; 07.12.2010
comment
@Р. Хм ... не знаю, как с этим справляются другие платформы. По крайней мере, в Win32 процесс завершается. На других коробках может и не быть. - person Billy ONeal; 07.12.2010
comment
@Billy: если каждый вызов увеличивает использование стека только на несколько килобайт, вы попадете на страницу защиты, и процесс будет остановлен. Но что, если за один вызов стек увеличится на 2 ГБ? Если компилятор не сгенерирует специальный код для проверки этого (и не замедлит все нормальные функции, которые в нем не нуждаются), указатель стека просто окажется в середине какой-то другой памяти, и ваша функция с радостью забьет эту память. - person R.. GitHub STOP HELPING ICE; 07.12.2010
comment
@R ..: Я думаю, что технически это должно было бы только замедлить функции, которые в целом перемещают указатель стека больше, чем размер защитной области (при условии, что компилятор знает это, или, возможно, размер страницы как минимум) , или которые используют VLA, alloca или аналогичные. Кто-то может возразить, что такие функции изначально не совсем разумны, или, в любом случае, снижение производительности этих вещей (по умолчанию может быть отключено параметром компилятора) того стоит, учитывая, насколько сложно их безопасно использовать. - person Steve Jessop; 07.12.2010
comment
Верно. Я думаю, что лучший способ: всякий раз, перемещая указатель стека более чем на PAGE_SIZE, перемещайте его с шагом PAGE_SIZE и записывайте одно слово после каждого шага. Тогда вы гарантированно попадете на страничку. - person R.. GitHub STOP HELPING ICE; 07.12.2010
comment
Хотя, если подумать, это тоже зависит от соглашения о вызовах. Если вы можете выполнить вызов функции, не касаясь стека, и не касаясь стека в функции, но перемещая sp, то каждый из серии таких вызовов может перемещать sp на небольшую величину, в сумме достаточную для выхода из охранять регион, затем, наконец, вызвать что-то, что идет бум. Таким образом, может также потребоваться снижение производительности функций, которые перемещают sp и вызывают что-то еще, вообще не касаясь стека, если это возможно в данной системе. - person Steve Jessop; 07.12.2010
comment
@R ..: договорились о методике его реализации. Альтернативой было бы наличие некоторых внутренних средств для компилятора, чтобы напрямую проверять состояние стека (так что, если он знает, что доступно полмиегабайт, не нужно нажимать каждые несколько килобайт), но это едва ли стоит того, если вы не делаете действительно большие суммы и будут иметь цену за все, если у вас еще нет какой-то глобально доступной структуры, которая может хранить указатель на конец стека (данные потока ядра или еще много чего). - person Steve Jessop; 07.12.2010
comment
Пока вызовы функций помещают адрес возврата в стек, проблема с суммированием небольших сумм не является проблемой. И я не понимаю, как какая-либо реализация могла бы обрабатывать больше, чем небольшой конечный (то есть 1 шаг) уровень вызовов вложенных функций, не касаясь стека. Что касается компилятора, генерирующего код для проверки состояния стека, мне действительно не нравится ненужная взаимозависимость между компилятором / компоновщиком / crt / libc / kernel / и т. Д., Особенно когда предполагаемые преимущества являются маргинальными и приносят пользу только кодам сомнительного качества. - person R.. GitHub STOP HELPING ICE; 07.12.2010
comment
@Р. Вы попадете на страничку, независимо от того, как пострадали память. Я не понимаю, какое отношение имеет фактический вызов функции к чему-либо. - person Billy ONeal; 07.12.2010
comment
@R ..: предполагается, что все функции хранят свой адрес возврата в одном и том же месте относительно локальных переменных. Если одна функция хранит свой регистр ссылок над локальным массивом, а затем вызывает функцию, которая хранит локальный массив над всем, чего она действительно касается (включая свой собственный регистр ссылок, если он выполняет дальнейшие вызовы), тогда, возможно, каждая функция может перемещать стек только на половина размера защитной области (+ дельта), но ничего в защитной области не будет затронуто, поскольку защитная область содержит только два массива. Итак, моя серия звонков - 2. - person Steve Jessop; 09.12.2010
comment
Моя альтернатива была основана на конкретной системе, которая не имела виртуальной памяти и, следовательно, вообще не использовала защитные страницы, но компилятор выдавал код при входе в функцию, чтобы выполнить проверку стека и при необходимости вызвать ядро ​​для расширения стека. Стек представлял собой связанный список блоков, поэтому расширение стека означает переход к новому блоку. Необычный случай, я согласен с тем, что он не предпочтителен и, возможно, не актуален, поскольку, конечно, при наличии виртуальной памяти обычно ядро ​​перехватывает аппаратное исключение в конце области стека, отображаемой в данный момент, и отображает больше физическая оперативная память. - person Steve Jessop; 09.12.2010

Простой контрпример:

#include <string.h>

int main()
{
    int huge[0x1FFFFFFF]; // Specific size doesn't matter;
                          // it just has to be bigger than the stack.

    memset(huge, 0, sizeof(huge) / sizeof(int));

    return 0;
}
person nmichaels    schedule 07.12.2010
comment
Размер кадра стека обычно не ограничен. Однако общий размер самого стека обычно является ограничивающим фактором. - person Billy ONeal; 07.12.2010
comment
@Armen: Он должен скомпилироваться, но во время выполнения будет сильно ошибаться. (Хотя возможно, что компилятор посмотрел на это и сказал: «Вы чокнутый?»). Если вы попытаетесь скомпилировать это на 32-битной машине, это не удастся, потому что 2 миллиарда целых чисел больше, чем все пространство памяти машины (примерно в 2 раза). - person Billy ONeal; 07.12.2010
comment
@Armen: Ну вот, теперь он сам скомпилируется. - person nmichaels; 07.12.2010
comment
@ Билли: Эй, ты прав. Я забыл, что использовал 64-битную машину. - person nmichaels; 07.12.2010
comment
@R: Ой, ты прав. Конечно, никаких изменений в результатах. Вот и проблема с написанием некорректных программ. - person nmichaels; 07.12.2010

Пример:

#include <iostream>

using namespace std;

class A
{
public:
    A() { p = new int[0xFFFFFFFF]; }

private:
    int* p;
};

static A g_a;

int main()
{
    cout << "Why do I never get called?" << endl;
}
person Zac Howland    schedule 07.12.2010
comment
Вы пропустили последнее предложение вопроса ОП. Естественно, когда конструктор автоматического объекта выполняет динамическое выделение, и такое выделение не удается, мы считаем это ошибкой при динамическом выделении, а не автоматическим. РЕДАКТИРОВАТЬ: И using namespace std; нужно умереть! :П - person Billy ONeal; 07.12.2010
comment
using namespace std; в простых примерах вряд ли проблема. Это делает пример более читабельным, чем наличие std:: повсюду. Конечно, в этом примере это не имеет особого значения, поскольку я использую только 1 оператор cout (и тот, который никогда не запускается, так как сбой произошел до него). Кроме того, его вопрос касался создания статических переменных. Я использовал new, но вы могли бы так же легко создать переменную-член, как int p[0x7FFFFFFF], чтобы получить тот же эффект. В любом случае это приводит к переполнению стека. - person Zac Howland; 07.12.2010
comment
@Zac: 1. Я думаю, это делает код менее читабельным. Когда вы вызываете стандартную библиотеку, это должно быть очевидно на месте вызова. 2. Нет, ни переменная-член, ни вызов new не приводят к переполнению стека. В первом случае, вероятно, произойдет сбой, потому что статическое пространство для хранения будет исчерпано. Вызов new завершается неудачно, потому что куча исчерпана. Ни одно из выделений не касается стека. Всегда. - person Billy ONeal; 07.12.2010
comment
@Billy 1. Это религиозная дискуссия, которая может продолжаться до Судного Дня, поэтому достаточно сказать каждому свое. 2. Вы забыли полный текст ошибки переполнения стека: стек переполняется кучей. То есть, если вы выделяете слишком много места в куче, оно будет пересекать пространство вашего стека (то же самое со статическим пространством хранения), таким образом, в любом случае это приведет к одной и той же проблеме: вы выделяете слишком много памяти в любом заданном горшке памяти. и портят другие горшки. - person Zac Howland; 08.12.2010
comment
@Zac: Это просто неправда. Стек упрощения растет в одну сторону, а куча растет в другую сторону. Это неверно для большинства реальных машин, потому что большинство реальных машин имеют виртуальную память. Да, у вас есть переполнение границ памяти, но это не приводит к переполнению стека. В стеке хранятся только автоматические переменные, и у вас там нет автоматических переменных. - person Billy ONeal; 08.12.2010
comment
Кроме того, w.r.t. using namespace std;: parashift.com/c++-faq-lite/ coding-standard.html # faq-27.5 и stackoverflow.com/questions/1452721/ - person Billy ONeal; 08.12.2010
comment
@Billy: Как я уже сказал, директива using namespace std; в файлах реализации - это религиозный аргумент, и, честно говоря, я не настолько забочусь о нем, чтобы беспокоиться об этом. Когда я даю простые примеры, я считаю полезным использовать его вместо того, чтобы везде набирать std::. В статье Parashift говорится, что это лучше всего. ИМО: просто помните, что вы являетесь частью команды, поэтому убедитесь, что вы используете подход, который согласуется с остальной частью вашей организации. - person Zac Howland; 08.12.2010