Как инициализируется стек?
Когда процесс запрашивает память, а операционная система предоставляет некоторые новые страницы для процесса, ядро должно инициализировать страницы (например, нулями), чтобы избежать отображения потенциально уверенных данных, которые использовали другой процесс. То же самое, когда процесс запускается и получает некоторую память, например сегмент стека.
Когда я запускаю следующий код в Linux, результатом является то, что большая часть выделенной памяти действительно равна 0, но что-то около 3-4 кБ в нижней части стека (последние элементы массива, самые высокие адреса) содержит случайные числа.
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
int * a = (int*)alloca(sizeof(int)*2000000);
for(int i = 0; i< 2000000; ++i)
cout << a[i] << endl;
return 0;
}
- Почему он тоже не установлен на ноль?
- Может быть, потому, что этот процесс повторно используется?
- Если да, может ли это быть код инициализации, который ранее использовал эти 3-4 кбайта памяти?
Ответы
Ответ 1
Я уверен, что когда ОС запустит ваш процесс, стек будет только нулями. Я думаю, что вы наблюдаете еще одно явление. Кажется, вы скомпилировали свою программу как С++. С++ делает много кода (конструкторы и тому подобное) перед началом main
. Итак, вы видите, что вы оставили значения своего собственного исполнения.
Если вы скомпилируете свой код как C (смените на "stdio.h" и т.д.), вы, вероятно, увидите значительно уменьшенное "загрязнение", если даже его вообще не будет. В частности, если вы связали свою программу статически с минималистской версией библиотеки C.
Ответ 2
Операционная система не гарантирует нулевую память, только то, что вы ее обладаете. Вероятно, это даст вам страницы памяти, которые раньше использовались (или никогда не использовались ранее, но не равны нулю). Если приложение хранит потенциально-чувствительные данные, ожидается, что он будет равен нулю перед free() 'ing.
Он не установлен на ноль, потому что это будет выполнять ненужную работу. Если вы выделяете 20 мегабайт для хранения текстуры или нескольких кадров видео, почему OS записывает нули во всю эту память, чтобы вы могли перезаписать их, как самую следующую вещь, которую вы делаете.
Как правило, операционные системы не делают ничего, что им не нужно.
edit: чтобы немного расширить, когда вы "распределяете" блок памяти, все, что делает ОС, это переназначение страниц памяти (блоков из 4096 байтов, обычно) для вашего процесса из пула un- выделенные страницы. Вы также можете иметь общую память, и в этом случае ОС "назначает" их нескольким процессам. Это все выделение равно.
Ответ 3
Когда вы получаете новую память в свой процесс с помощью brk()
, sbrk()
или mmap()
, тогда гарантируется ее обнуление.
Но стек процесса уже выделен для вашего процесса. Функция alloca()
не получает новое пространство стека, она просто возвращает указатель текущего стека и перемещает указатель в конец нового блока.
Таким образом, блок памяти, возвращенный alloca()
, ранее использовался вашим процессом. Даже если у вас нет функций перед вашим alloca()
в основном, библиотеки C и динамический загрузчик используют стек.
Ответ 4
В alloca
документации нет ничего, в котором говорится, что память инициализирована, поэтому вы просто получаете там всякий мусор.
Если вы хотите, чтобы память была инициализирована нулями, вы можете сделать очевидное: выделить и вручную инициализировать ее с помощью memset
. Или вы можете использовать calloc
, который гарантирует, что память инициализирована нулем.
Ответ 5
В верхней части стека содержатся определения переменных среды, а ниже приведены аргументы командной строки и массивы окружения и argv.
На x86_64 простой код запуска под Linux может выглядеть так:
asm(
" .text\n"
" .align 16\n"
" .globl _start\n"
" .type _start,@function\n"
"_start:\n"
" xor %rbp, %rbp\n" // Clear the link register.
" mov (%rsp), %rdi\n" // Get argc...
" lea 8(%rsp), %rsi\n" // ... and argv ...
" mov %rax, %rbx\n" // ... copy argc ...
" inc %rbx\n" // ... argc + 1 ...
" lea (%rsi, %rbx, 8), %rdx\n"// ... and compute environ.
" andq $~15, %rsp\n" // Align the stack on a 16 byte boundry.
" call _estart\n" // Let go!
" jmp .\n" // Never gets here.
" .size _start, .-_start\n"
);
Edit:
Я полностью неправильно понял вопрос. Материал в верхней части стека в вашем коде, вероятно, является результатом кода запуска, который вызывается до ввода main().