Ассемблер - экстремальная оптимизация

         

трюкаческий пример, портированный на 286+ процессоры


Несмотря на то, что 8086/8088 процессоры уже давно не встречаются в дикой природе (ну разве что в виде эмуляторов, да и то…), многие программы, написанные под них, актуальны и сегодня. Это касается как уже откомпилированного машинного кода, так и различных ассемблерных библиотек, переносимых под современные процессоры. Одна из причин, по которой они могут не работать — это и есть различие в логике обработке команды PUSH ESP.

Вообще же, динамическое выделение памяти посредством PUSH + фиктивный регистр — вполне законный примем, которым пользуются не только люди, но и компиляторы. Это намного компактнее, чем обращение к локальным/глобальным переменным, выделяемым классическим способом.

Естественно, большие объемы памяти лучше всего выделять с помощью SUB ESP, XXh, но при этом следует помнить как минимум о двух вещах. Первое и главное — Windows-системы выделяют стековую память динамически, используя для этого специальную "сторожевую" страницу памяти (page guard). Как только к ней происходит обращение — система выделяет еще одну или несколько страниц памяти, перемещая сторожевую страницу наверх (в сторону меньших адресов памяти). При последовательном "росте" стека все работает нормально, но если попытаться прыгнуть за сторожевую страницу, сразу же возникнет непредвиденное исключение — ведь никакой памяти по данному адресу еще нет — и работа программы завершается в аварийном режиме. То есть, если у нас есть к примеру 1 Мбайт стекового пространства, это еще не значит, что код SUB ESP, 10000h/MOV [ESP],EAX

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

Компиляторы делают это автоматически, а вот многие ассеблерщики о таком коварстве Windows зачастую даже и не подозревают, а потом упорно ищут бага в своей программе, не понимая почему она не работает!

main()

{

       char x[1024*1024];   // выделяем 1 Мбайт стековой памяти

       return *x;           // обращаемся к наиболее "дальней" стековой ячейке

}



Содержание раздела