Что стандарт C ++ говорит о переполнении стека?

Я взглянул на черновик стандарта C ++ 0x, и, насколько я могу судить, там нет ничего о переполнении стека. Поиск «переполнения стека» не дал результатов, а поиск «стека» я получил только ссылки на раскручивание стека и std :: stack. Означает ли это, что не может быть соответствующей реализации стандарта C ++, поскольку не существует механизма обработки ошибки, когда память исчерпывается локальным объектом, таким как огромный локальный массив?

Ответы на этот вопрос показывают, что по крайней мере Стандарт C не упоминает о переполнении стека.

Чтобы конкретизировать вопрос, рассмотрим эту программу

// Program A
int identity(int a) {
  if (a == 0)
    return 0;
  char hugeArray[1024 * 1024 * 1024]; // 1 GB
  return identity(a - 1) + 1;
}
int main() {
  return f(1024 * 1024 * 1024);
}

и эта программа

// program B
int main() {
  return 1024 * 1024 * 1024;
}

Я думаю, что стандарт C ++ не позволяет какой-либо реализации C ++ делать что-то заметно отличающееся в этих двух программах. На самом деле программа A не будет работать ни на одной современной машине, поскольку она выделяет эксабайт памяти в стеке (представьте, что функция фактически использовала огромный массив, поэтому компилятор не может удалить его молча, чтобы не повредить). Допускает ли стандарт C ++ ошибку программы A?

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


person Bjarke H. Roune    schedule 05.07.2011    source источник
comment
В стандарте нет понятия физического компьютера с конечной памятью. В принципе, ни один компилятор не может быть совместимым со стандартами, потому что вы всегда можете написать очень большую программу, которая не может вписаться в какое-либо существующее оборудование и не может быть скомпилирована, хотя она состоит из действительного стандартного кода, но компилятор не смог его скомпилировать.   -  person Kerrek SB    schedule 06.07.2011
comment
@Kerrek Хорошее замечание. Однако в стандарте действительно есть понятие памяти как конечного ресурса с точки зрения нового броска std :: bad_alloc. Если бы я выделил огромный массив с помощью new, стандартный допустил бы сбой программы A. Мне интересно, существует ли подобное понятие, которое позволяет программе A терпеть неудачу как есть.   -  person Bjarke H. Roune    schedule 06.07.2011
comment
Конечно, можно ожидать, что new char[HUGE] потерпит неудачу. Но что, если ваш исходный код содержит миллиарды миллиардов функций? Все полностью валидный стандартный C ++ ...   -  person Kerrek SB    schedule 06.07.2011
comment
Куча на каком-то уровне выделяется явно; стек выделяется автоматически, и единственным доступным элементом управления часто является зонд стека, заставляющий его расширяться. Это означает, что вы не можете заранее проверить и обычно нет способа обработать или даже распознать случай, когда стек попадает в кучу. (Подумайте об этом: это слишком большое выделение стека или это дикий указатель кучи? Программист может понять это, проверив код (надеюсь), но ни ядро, ни системная библиотека не могут.)   -  person geekosaur    schedule 06.07.2011
comment
Стек - это деталь реализации, которую ни стандарты C, ни C ++ не коснутся трехметровым шестом. Это не совсем редкость, языки виртуальных машин тоже это запутывают. Это универсально подходит для категории «дерьмо, давайте не будем делать этого снова». Если у вас закончился стек, значит, вы на порядок превосходит разумные.   -  person Hans Passant    schedule 06.07.2011
comment
@Hans: Если у вас закончился стек, то вы на порядок больше, чем разумно - это зависит от реализации, некоторые реализации (особенно если у них нет виртуальной памяти) по умолчанию не предоставляют процессу / потоку на порядок больше стек, чем это разумно. На ПК современная операционная система, бла-бла, конечно есть.   -  person Steve Jessop    schedule 06.07.2011
comment
@Steven: Вы пробовали писать для старой PalmOS 5.4? Я думаю, у него был стек 12кБ или что-то в этом роде ...   -  person Kerrek SB    schedule 06.07.2011
comment
@geekosaur Вы уверены, что переполнение стека может происходить без обнаружения в современных системах? Я уверен, что это может быть обнаружено, если компилятор и ОС будут взаимодействовать, но я также вижу, что это может быть связано с некоторыми накладными расходами, поэтому этого нельзя сделать. Если так, это меня пугает.   -  person Bjarke H. Roune    schedule 06.07.2011
comment
@Kerreck: Symbian 8, такая же базовая сделка. Я думаю, что размер стека по умолчанию составлял 4 КБ, хотя, конечно, оперативной памяти было намного больше, чем было доступно, так что вы могли бы увеличить ее, если бы поняли, что вам нужно.   -  person Steve Jessop    schedule 06.07.2011
comment
@Bjarke: работайте над этим. Вы - обработчик ошибок страницы ОС, а процесс только что вызвал ошибку страницы. Как вы определяете, что это произошло из-за чрезмерного выделения стека? Как определить, что это неинициализированный указатель кучи? Насколько надежно это определение, когда стек и куча находятся рядом друг с другом? В конечном счете, единственный способ быть уверенным - это понять намерение кода, который его запустил, что выходит далеко за рамки возможностей любой ОС или компилятора.   -  person geekosaur    schedule 06.07.2011
comment
@Bjarke: Я думаю, что одна из квалификационных характеристик современной операционной системы, по крайней мере, в мире полнофункциональных устройств, - это обнаружение переполнения стека. Обычная процедура заключается в том, чтобы вставить защитную страницу в конец стека, и, действительно, страницу в большем количестве стека по мере необходимости, а не заранее выделять много. Тогда единственная реальная проблема, связанная с производительностью, заключается в том, что если вы устанавливаете фрейм, который больше, чем страница защиты, выполняете ли вы тестовый доступ с интервалами размера страницы, чтобы убедиться, что вы не перешли на страницу защиты.   -  person Steve Jessop    schedule 06.07.2011
comment
Как говорит geekosaur, в принципе вы можете попасть на эту защитную страницу по какой-то причине, отличной от переполнения стека, но любая другая причина - это UB, поэтому не имеет значения, обрабатывает ли ОС ее так же, как SO - страница в большем стеке если возможно / разрешено, иначе завершите процесс или сделайте все, что в руководстве является результатом SO. Таким образом, могут быть ложные срабатывания, но не нужно слишком много усилий, чтобы гарантировать отсутствие ложных срабатываний.   -  person Steve Jessop    schedule 06.07.2011
comment
@Steve: последний как раз винт; вы можете иметь защитные страницы с обеих сторон, если хотите (пока они не столкнутся друг с другом), но чем дальше вы удаляетесь от фактического конца выделения, тем более неопределенным становится источник ошибки страницы.   -  person geekosaur    schedule 06.07.2011
comment
@geekosaur: но вы все равно можете избежать ложноотрицательных результатов, поэтому вы всегда можете обнаружить SO. Вы можете ошибочно утверждать, что некоторые вещи ТАК, хотя на самом деле это не так, но это ложные срабатывания, это не означает, что ТАК не обнаруживается, когда это действительно происходит. Фактически, еще одной общей характеристикой современных операционных систем является то, что стек никогда не приближается к куче - чудеса больших виртуальных адресных пространств. Конечно, программа может имитировать SO, просто запустив дико с конца любого автоматически выделенного объекта, без необходимости задействовать кучу.   -  person Steve Jessop    schedule 06.07.2011
comment
@Steve: возьмите близкий к этому примеру 1 эксабайт, выделение стека 2 ГБ или 3 ГБ. Он окажется в центре кучи в большинстве современных систем. Требуется ли затем компилятор выполнять постраничную проверку для выделения стека, и если да, то как долго вы готовы ждать выделения локальной переменной стека, пока настройка кадра стека или alloca() циклы вызовов проверяют множество страниц? И даже если вы готовы заплатить эту цену (поскольку педантично это означает, что вы можете, даже если это невозможно дорого), у вас все равно нет абсолютной уверенности относительно страницы, расположенной непосредственно над кучей.   -  person geekosaur    schedule 06.07.2011
comment
@geekosaur: вы, кажется, на самом деле не интересуетесь тем фактом, что, разрешив неопределенность в пользу предположения, что это является ТАК, вы можете полностью избежать ложноотрицательных результатов. Как вы говорите, производительность проверки 2 ГБ с шагом 4 КБ может быть проблемой, но это только наихудший сценарий для компилятора, который знает, что ОС разместит защитную страницу, но не может напрямую получить доступ к информации о стеке потока для вычислить, превышает ли кадр текущий зафиксированный стек и насколько он это делает. На практике компиляторы в любом случае могут наложить ограничение на кадр менее 2 ГБ.   -  person Steve Jessop    schedule 06.07.2011
comment
Предполагая, что он предполагает, что это так, а не обнаруживает его. Я полагаю, вы можете просто назвать это семантикой и положить конец этому, поскольку это в любом случае не имеет значения.   -  person geekosaur    schedule 06.07.2011
comment
@geekosaur: обнаружить это означает, если это произойдет, сказать, что это произойдет. Ложные срабатывания - это ситуации, когда вы говорите, что это произошло, но этого не произошло. Принято говорить об обнаружении с возможностью ложных срабатываний, поскольку только ложноотрицательные срабатывания не могут быть обнаружены, а ложные срабатывания - нет. Я бы назвал это простым значением терминов, а не семантикой, но, конечно, простое, очевидное значение - один из примеров семантики ;-)   -  person Steve Jessop    schedule 06.07.2011


Ответы (4)


Я не уверен, что это то, что вы ищете, но в Приложении B стандарта C ++ 03 ISO есть следующее примечание:

  1. Поскольку компьютеры конечны, реализации C ++ неизбежно ограничены в размере программ, которые они могут успешно обрабатывать. Каждая реализация должна документировать эти ограничения, если они известны. В этой документации могут быть указаны фиксированные ограничения там, где они существуют, указано, как вычислить пределы переменных в зависимости от доступных ресурсов, или указано, что фиксированные ограничения не существуют или неизвестны.
  2. Пределы могут ограничивать количества, которые включают описанные ниже или другие.

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

person templatetypedef    schedule 05.07.2011
comment
Если это все, что стандарт может сказать по данному вопросу, то да, это именно то, что я ищу. Спасибо. - person Bjarke H. Roune; 06.07.2011
comment
Кстати, тот факт, что в стандарте нет понятия переполнения стека, позволяет компилятору выполнять агрессивную оптимизацию хвостового вызова. (см., например, stackoverflow.com/questions/5493688/ < / а>) - person Matteo Italia; 06.07.2011
comment
Если вы прочитаете дальше, в этом разделе в стеке ничего нет. Это немного дешево, поскольку в стандарте не упоминается стек. Однако Приложение B не накладывает ограничений на количество рекурсивных вызовов или объем памяти, который может быть выделен автоматическими переменными. Это хорошо, потому что такие ограничения не зависят от компилятора. Они созданы ОС и злыми системными администраторами, которые установили слишком низкий предел стека. - person David Hammen; 06.07.2011
comment
В стандарте стек не упоминается, потому что стек является такой же частью стандарта, как и vtables (или ... как минимум). Стандарт определяет функции, которые компилятор должен каким-то образом реализовать, например, переменные, очищаемые при выходе за пределы области видимости, и функции, возвращающиеся, возможно, после рекурсии, туда, где они были вызваны. Случайно бывает, что стек хорошо подходит для этих вещей (например, vtables случайно хорошо подходят для виртуального наследования), и поэтому все известные компиляторы используют стеки, но это просто совпадение , и (неважная) деталь реализации. - person Damon; 06.07.2011

Поведение не определено, потому что Стандарт не определяет, что происходит с программой, превышающей лимит ресурсов. Обратите внимание, что рекомендуемые пределы указаны в Приложении B спецификации. Однако это приложение не является нормативным, и реализация может игнорировать это приложение, включая ограничения, отличные от указанных там. В версии 1.4 [intro.compliance] в спецификации говорится

Если программа не содержит нарушений правил настоящего международного стандарта, соответствующая реализация должна в пределах своих ресурсов принять и правильно выполнить эту программу.

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

person Johannes Schaub - litb    schedule 06.07.2011

Переполнение стека нарушает механизм защиты, установленный в операционной системе. Это не особенность языка, так как весь исполняемый машинный код будет иметь такую ​​же защиту.

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

Не уверен насчет Windows, OSX или мобильных устройств.

person Mark    schedule 05.07.2011
comment
Помимо того, что это функция ОС, она также влияет на наблюдаемое поведение программы на C ++. Так что это связано как с C ++, так и с ОС. В качестве примера у меня возникло исключение в Java при переполнении стека, поэтому некоторые другие языки действительно определяют переполнение стека. - person Bjarke H. Roune; 06.07.2011
comment
@Bjarke: Я не думаю, что это влияет на наблюдаемое поведение программы. Я имею в виду, очевидно, что если программа неожиданно завершается, вы можете заметить, что это произошло, вы точно не заметите остальную часть вывода. Но мы не говорим, что диспетчер задач Windows отображает реализацию несоответствующей, потому что вы можете убить запущенный процесс, и я не думаю, что произвольные действия монитора стека действительно сильно отличаются от произвольных действий пользователя. , что касается стандарта. Это выходит за рамки стандарта, когда и почему ОС завершает процесс. - person Steve Jessop; 06.07.2011

То, что происходит при переполнении стека, чрезвычайно зависит от системы (и ЦП, и ОС, а иногда и компилятор, потому что компилятор должен вставлять зонды стека и другие механизмы для безопасного расширения стека), поэтому невозможно указать конкретный ответ; Лучшее, что можно было бы сделать, - это предложить ответы, которые были бы предпочтительнее, если это позволяет целевая платформа. Большинство этого не делают; хотя есть разумный способ справиться с переполнением кучи, обработчик переполнения стека (a), вероятно, будет вызван, когда стек находится в несогласованном состоянии, с частично построенным фреймом стека на нем, и (b), вероятно, будет включать вызов обработчик ... которому требуется пространство стека для кадра прерывания. POSIX определяет механизм sigaltstack(), но он также имеет ограничения, и ANSI C C / C ++ не может разумно зависеть от соответствия POSIX.

person geekosaur    schedule 05.07.2011