Как может произойти переполнение стека в C ++? - PullRequest
15 голосов
/ 01 июля 2011

Какой простой пример в C ++ вызывает переполнение стека в случае вызова и возврата из вызовов методов?

Я знаком с соглашением о вызовах, т. Е. thiscall, stdcall и cdecl, и тем, как они будут чистить стек. Не будет ли неполадка стека автоматически устраняться кодом, сгенерированным компилятором?

В каких ситуациях могут возникнуть проблемы с переполнением стека?

Ответы [ 4 ]

11 голосов
/ 02 июля 2011

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

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

См. здесь для получения подробной информации о соглашениях о вызовах.

5 голосов
/ 01 июля 2011

Я не уверен, говорите ли вы о стеке структуры данных и о неполадках в нем или о чем-то еще.Что касается проблемы stack(data structure) underflow, то здесь есть объяснение.

stack - это абстрактный тип данных LIFO и структура данных.Стек может иметь любой абстрактный тип данных в качестве элемента, но характеризуется только тремя основными операциями: push , pop и вершина стека .

Операция push добавляет новый элемент на вершину стека или инициализирует стек, если он пуст.Если стек заполнен и не содержит достаточно места для принятия данного элемента, то считается, что стек находится в состоянии переполнения.Операция pop удаляет элемент из верхней части стека.

A pop либо показывает ранее скрытые предметы, либо приводит к пустому стеку, но если стек пуст, то он переходит в underflowсостояние (это означает, что в стеке нет элементов для удаления).

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

Рассмотрим пример реализации стека:

template <class Item> class Stack 
{
public:
    bool isEmpty() const;
    size_t size() const;
    Item pop();
    void push(const Item& it);
private:

};

Теперь рассмотрим следующие выполняемые операциив этом стеке.

C++ command                      resulting stack
------------------------------------------------
Stack<int> S;
                                  _____ (empty stack of ints)



S.push(7);                            
                                  | 7 |  <-- top
                                  -----

S.push(2);                            
                                  | 2 |  <-- top 
                                  | 7 |
                                  -----

S.push(73);                           
                                  |73 |  <-- top 
                                  | 2 |
                                  | 7 |
                                  -----

S.pop();                           
                                  | 2 |  <-- top
                                  | 7 |                    -----
S.pop();      
                                  -----
S.pop();                           
                                  | 7 |  <-- top
                                  -----
S.pop();                           
                                  -----  (empty)

S.pop();                           
                    ERROR "stack underflow"
1 голос
/ 02 июля 2011

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

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

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

Однако при обращении к стеку адреса относятся к вершине. Таким образом, компилятор будет искать конкретный объект в [top of stack + 3], если он находится в трех байтах от вершины стека. Это все равно будет сделано, если стек окажется короче ожидаемого, и будет искать этот объект в неправильном месте. Предполагая, что объект даже все еще там ... он, возможно, уже случайно оторвался. Когда вы дойдете до конца любой функции, в которой вы находитесь, она может не найти правильный адрес возврата по той же причине, но даже если это произойдет, внезапное повреждение всех ваших объектов - довольно тяжелая ситуация .

Предостережение: все это основано на предположении, что современные системы ведут себя так же, как старые микроконтроллеры, с которыми я работал десять лет назад. Может быть, они умнее, чем сейчас.

1 голос
/ 01 июля 2011

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

...