Управление стеком, crt
Исследования начинаются с передачи структур по значению в языках высокого уровня.
Работаю с отладчиком. Наблюдаю выделение памяти на стеке.
Прежде чем задать вопрос, я хотел бы привести опорный пример (для винды) и некоторые соображения о механизме работы стека. Если какие-либо утверждения неверны, то прошу поправить.
Итак. Имеем некий процесс, порожденный PE.
- При его инициализации, операционная система резервирует (reserve) некоторое количество страниц памяти под стек.
- Не более этого объёма она связывает (commit) с физическими страницами.
- Эту информацию она берёт из полей `SizeOfStackReserve` и `SizeOfStackCommit` заголовка PE файла.
- Это означает, что размер стека никогда не превысит значение `SizeOfStackReserve`, если явно этого не затребовать уже в рантайме.
- Это означает, что после выхода (попытка доступа) стека за границы `SizeOfStackCommit` будет сгенерировано исключение `PageNotPresent` и в причинное место будет спроецирована физическая страница, а в случае нехватки физической памяти произойдёт fail и процесс будет терминирован с соответствующим кодом ошибки или будет использован своп.
- Это означает, что после выхода за `SizeOfStackCommit` процесс будет иметь известные проблемы, если не попадёт в heap, что приводит к другим известным проблемам.
Проблема в том, что во всей этой схеме я не вижу место функции _alloca (_malloca - более `secure` :facepalm:). Хотя нет, - вижу укромный уголок: превращение слова на букву `Ж` в слово на букву `И`, где второе означает исключение, что по-идее может позволить огромному корпоративному приложению со всеми копирайтами не упасть вместе с многочасовыми несохранёнными наработками, а сберечь и тихо лечь (о как срифмовал:D).
Более того, хотелось бы знать, зачем может понадобиться вызывать (самому, а не кодом, который сгенерирован компилером) _alloca из кода на С/С++ или других подобных.
Ах да, переменный размер выделяемого блока.
А теперь вопросы: как быть с текущим стеком функции?
Ведь если распределённый блок заканчивается (начинается конец :facepalm:) восемью байтами ниже (адрес возврата из _alloca + аргумент-размер выделяемого блока) текущего esp, то это просто ту-ту (сами понимаете).
Значит нужно помещать адрес начала блока в esp и оттуда уже использовать "свой" стек (который может понадобиться внутри текущей функции для других распределений на стеке или вложенных вызовов).
И более фундаментальный вопрос: на кой чёрт нужна такая защита от переполнения, если ручное распределение по-прежнему необходимо и по-прежнему потенциально падабельно?
Возникает сопутствующий вопрос:
[COLOR="paleturquoise"]_alloca allocates size bytes from the program stack.[/COLOR] The allocated space is automatically freed when the calling function exits (not when the allocation merely passes out of scope). Therefore, do not pass the pointer value returned by _alloca as an argument to free.
Как это возможно, если я прав по тем пунктам, что выделены bb-кодом списка?
Тут вот обсуждали подтекание inline функций, что наводит на некоторые мысли.
Тут под номером 3.14 есть вопрос, но ответ не проливает свет (второй раз уже).
P.S. Надеюсь целевую аудиторию не отпугнёт многабукав?
2. предназначен для выделения небольших блоков в автоматической памяти в пределах функции (пример: локальный массив элементов неизвестного заранее размера)
3. вспомните, что например в архитектуре IA-32 представляют из себя пролог и эпилог ф-ции и какую роль там играет ebp. В результате вы получите объяснение того, как освобождается память и почему несчастный получил в релизнутой версии срабатывание собственноручно и с любовью внесенного паттерна "детонатор".
1. alloca не является кросс-платформенным.
Не отрицаю.
Без проблем. Другая платформа - другое решение.
2. Предназначен для выделения небольших блоков в автоматической памяти в пределах функции (пример: локальный массив элементов неизвестного заранее размера)
Знаем.
3. Вспомните, что например в архитектуре IA-32 представляют из себя пролог и эпилог ф-ции и какую роль там играет ebp. В результате вы получите объяснение того, как освобождается память и почему несчастный получил в релизнутой версии срабатывание собственноручно и с любовью внесенного паттерна "детонатор".
Знакомо, - ebp сохраняет изначальный указатель стека для его восстановления перед выходом из функции.
Разумеется, если выделение памяти на стеке выполняется простым и элегантным:
то после
pop ebp
всё будет выглядеть так, как будто ничего и не было.
Итак, Вы согласны с bb-пунктами?
Некоторые вопросы, что я поднял не получили ответа, Вы согласны?
Итак, Вы согласны с bb-пунктами?
Некоторые вопросы, что я поднял не получили ответа, Вы согласны?
Желаете ответы - уточните интересующие вас вопросы.
Ок.
[COLOR="lightblue"]The _freea function deallocates a memory block (memblock) that was previously allocated by a call to _malloca. _freea checks to see if the memory was allocated on the heap or the stack.[/COLOR] If it was allocated on the stack, _freea does nothing.
Это подводит нас к выводу о том, что alloca ничего не делает, кроме как уменьшает esp на необходимое число байт?
Если да, - зачем она нужна в таком случае?
2. Предназначен для выделения небольших блоков в автоматической памяти в пределах функции (пример: локальный массив элементов неизвестного заранее размера)
На счет "небольшести" блоков тоже вопрос:
Я видел код, который генерирует gcc для выделения памяти под структуру:
{
bool a;
int b,c,d,e;
long long f,g,h,i,j,k,l;
};
и
{
bool a;
int b,c,d,e;
long long f,g,h,i,j,k,l;
char m[10240];
};
В первом случае она использует банальное:
А во втором:
call 0x405870 <_alloca>
Должно же этому быть рациональное объяснение.
Да и зачем в crt функция, которая делает то, что быстрее и короче сделать без неё?
Это подводит нас к выводу о том, что alloca ничего не делает, кроме как уменьшает esp на необходимое число байт?
Истинно.
Если да, - зачем она нужна в таком случае?
...
2. предназначен для выделения небольших блоков в автоматической памяти в пределах функции (пример: локальный массив элементов неизвестного заранее размера)
...
На счет "небольшести" блоков тоже вопрос:
Я видел код, который генерирует gcc для выделения памяти под структуру:
{
bool a;
int b,c,d,e;
long long f,g,h,i,j,k,l;
};
и
{
bool a;
int b,c,d,e;
long long f,g,h,i,j,k,l;
char m[10240];
};
В первом случае она использует банальное:
А во втором:
call 0x405870 <_alloca>
Должно же этому быть рациональное объяснение.
Да и зачем в crt функция, которая делает то, что быстрее и короче сделать без неё?
Компилятор оптимизировал естественный случай, а попытку суицида отдал на откуп CRT.
PS: К тому же в данном случае размер структуры известен заранее, а это потенциальная возможность оптимизации особенно при небольшом ее размере.
О как! Я в ауте! :)
Должен признать, даже не думал об этом в такой плоскости.
Но это же ересь, Вам не кажется?
У меня есть более-менее сносное предположение, не отрицающее, при этом, Ваш вариант.:cool:
Вот псевдокод, соответствующий коду, что я увидел в отладчике:
ecx = [esp+8]
while( eax > 0x1000 )
{
ecx -= 0x1000;
[ecx] |= 0;
eax -= 0x1000;
}
ecx -= eax;
[ecx] |= 0;
eax = esp;
esp = ecx;
ecx = [eax];
eax = [eax+4];
push eax
Вы только посмотрите на этот, на первый взгляд, бред:
Это же гениально. Вызов alloca имеет смысл: она беспокоится о том, чтобы не залезть в heap, а получить access violation вместо этого в большинстве случаев. (ровно как и в случае выхода за `SizeOfStackReserve`).
Кстати НЕ сработает она только в редком случае, - когда heap и стек сомкнутся.
PS:Касательно стека и кучи, которые якобы двигаются навстречу друг другу. Сие утверждение перестало быть однозначно истинным с момента появления многопоточности, а многопоточность появилась ой как давно...
Как правило в конце стека расположена сторожевая страница, которая не даст вылезти стеку за свои пределы.
Вот читаю:
По мере разрастания дерева вызовов (одновременного обращения ко всё большему числу функций) потоку, естественно, требуется и больший объем стека. Как только поток обращается к следующей странице (а она является сторожевой), то процессор возбуждает аппаратное исключение "доступ к сторожевой странице" (это не механизм, специфичный для стека, а вообще для любой страницы с PAGE_GUARD-атрибутом). Система видит это исключение и расширяет стек - та страница, что была сторожевой, становится обычной read-write страницей, а следующая за ней (в сторону младших адресов, т.к. стек растёт в обратную сторону) становится новой сторожевой страницей. Благодаря такому механизму работы, объем памяти, занимаемой стеком, увеличивается только по мере необходимости.
Так стек расширяется за пределы `SizeOfStackReserve` или сторожевая страница находится только внутри этой области?
PS:Касательно стека и кучи, которые якобы двигаются навстречу друг другу. Сие утверждение перестало быть однозначно истинным с момента появления многопоточности, а многопоточность появилась ой как давно...
У каждой нити свой стек, но они-то в одном виртуальном а.п.. Значит в разных областях. А куча-то общая!
Вы об этом?
Так стек расширяется за пределы `SizeOfStackReserve` или сторожевая страница находится только внутри этой области?
Стек за пределы `SizeOfStackReserve` не выходит.
У каждой нити свой стек, но они-то в одном виртуальном а.п.. Значит в разных областях. А куча-то общая!
Вы об этом?
Как то так.
Стек за пределы `SizeOfStackReserve` не выходит.
Вообще-то, было бы удивительно, если бы не так. Хотя бывает ещё и не такое.
P.S. Thx!