Почему моя программа не переполняет стек, когда я выделяю массив размером 11 МБ, а верхний предел стека составляет 10 МБ? - PullRequest
8 голосов
/ 15 августа 2011

У меня есть две простые программы на C ++ и два вопроса. Я работаю в CentOS 5.2, и моя среда разработки выглядит следующим образом:

  • g ++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)
  • Вывод «ulimit -s»: 10240 (кбайт), то есть 10MB

Программа №1:

main.cpp:

int main(int argc, char * argv[])
{
    char buf[1024*1024*11] = {0};
    return 0;
}

(скомпилировано с "g ++ -g main.cpp")

Программа выделяет 1024 *1024* 11 байт (то есть 11 МБ) в стеке, но не вылетает. После того, как я изменил размер выделения на 1024 *1024* 12 (то есть 12 МБ), программа вылетает. Я думаю, что это должно быть вызвано переполнением стека. Но почему не происходит сбой программы, когда размер выделения составляет 11 МБ, что также превышает верхний предел 10 МБ ??

Программа № 2:

main.cpp:

#include <iostream>

int main(int argc, char * argv[])
{
    char buf[1024*1024*11] = {0};

    std::cout << "*** separation ***" << std::endl;

    char buf2[1024*1024] = {0};

    return 0;
}

(скомпилировано с "g ++ -g main.cpp")

Эта программа приведет к сбою программы, поскольку она выделяет 12 МБ байтов в стеке. Однако, согласно файлу дампа ядра (см. Ниже), сбой происходит на buf, но не на buf2. Не должно ли произойти сбой buf2, потому что мы знаем из программы # 1, что распределение char buf [1024 *1024* 11] в порядке, поэтому после того, как мы выделим еще 1024 * 1024 байта, стек переполнится?

Я думаю, что должны быть некоторые довольно фундаментальные понятия, которые я не сформировал для понимания Но что они ??

Приложение: информация о дампе ядра, сгенерированная программой # 2:

Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
[New process 16433]
#0  0x08048715 in main () at main.cpp:5
5           char buf[1024*1024*11] = {0};

Ответы [ 4 ]

6 голосов
/ 15 августа 2011

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

Кроме того, ошибка возникает, как только вы записываете в память, а не когда она выделяется впервые.Скорее всего, buf находится в стеке перед buf2, поэтому переполнение происходит в buf, а не в buf2.

4 голосов
/ 16 августа 2011

Чтобы проанализировать подобные загадки, всегда полезно взглянуть на сгенерированный код. Я предполагаю, что ваша конкретная версия компилятора делает что-то другое, потому что мой segfaults с -O0, но не с -O1.

Из вашей программы # 1: g ++ a.c -g -O0 , а затем objdump -S a.out

int main(int argc, char * argv[])
{
 8048484:       55                      push   %ebp
 8048485:       89 e5                   mov    %esp,%ebp

Это стандартный кадр стека. Здесь нечего видеть.

 8048487:       83 e4 f0                and    $0xfffffff0,%esp

Выровняйте стек на кратное 16, на всякий случай.

 804848a:       81 ec 30 00 b0 00       sub    $0xb00030,%esp

Выделите 0xB00030 байт стекового пространства. Это 1024 *1024* 11 + 48 байтов. Нет доступа к памяти еще, поэтому не исключение. Дополнительные 48 байтов предназначены для внутреннего использования компилятором.

 8048490:       8b 45 0c                mov    0xc(%ebp),%eax
 8048493:       89 44 24 1c             mov    %eax,0x1c(%esp)   <--- SEGFAULTS

При первом обращении к стеку выходит за пределы ulimit, поэтому он вызывает ошибки.

 8048497:       65 a1 14 00 00 00       mov    %gs:0x14,%eax

Thiis является стековым защитником .

 804849d:       89 84 24 2c 00 b0 00    mov    %eax,0xb0002c(%esp)
 80484a4:       31 c0                   xor    %eax,%eax
    char buf[1024*1024*11] = {0};
 80484a6:       8d 44 24 2c             lea    0x2c(%esp),%eax
 80484aa:       ba 00 00 b0 00          mov    $0xb00000,%edx
 80484af:       89 54 24 08             mov    %edx,0x8(%esp)
 80484b3:       c7 44 24 04 00 00 00    movl   $0x0,0x4(%esp)
 80484ba:       00 
 80484bb:       89 04 24                mov    %eax,(%esp)
 80484be:       e8 d1 fe ff ff          call   8048394 <memset@plt>

Инициализировать массив, вызывая memset

    return 0;
 80484c3:       b8 00 00 00 00          mov    $0x0,%eax
}

Как вы можете видеть, segfault происходит, когда к внутренним переменным обращаются, потому что они оказываются ниже большого массива (они должны быть, потому что есть защитник стека, чтобы обнаружить разрушение стека).

Если вы компилируете с оптимизацией, компилятор замечает, что вы ничего не делаете с массивом, и оптимизирует его. Так что нет сигсег.

Возможно, ваша версия GCC немного перегружена в режиме без оптимизации и удаляет массив. Мы можем проанализировать его дальше, если вы разместите вывод objdump -S a.out .

3 голосов
/ 15 августа 2011

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

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

0 голосов
/ 12 июля 2016

Обе ваши программы в идеале должны давать segfault.

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

Нулевой уровень оптимизации, указанный во время компиляции как -O0 , указывает на отсутствие оптимизации вообще. Это также уровень оптимизации по умолчанию, с которым код компилируется. Вышеупомянутые программы при компиляции с -O0 , дают segfault.

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

...