Как изменить размер exe файла
В интернетах есть несколько старых статей (тыц, тыц) про то, как уменьшить размер exe-файла, который генерирует Visual C++. Я взялся проверить актуальность рецептов, которые там приводятся, для Visual Studio 2010.
Задачу я взял классическую: найти 10 самых популярных слов в текстовом файле. Мой исходный файл был размером 73728 байт.
Начало
Вот мое изначальное решение, написанное за 8 минут и заработавшее со второй компиляции:
int main()
try string s = "input.txt" ;
ifstream inf(s.c_str(), ios::binary);
if (inf.good()) string word;
map<string, int > wc;
while (inf >> word) ++wc[word];
>
inf.close();
vector< pair< int , string> > wcvec;
for ( auto i = wc.begin(), iend = wc.end(); i != iend; ++i) wcvec.push_back(make_pair(i->second, i->first));
>
sort(wcvec.begin(), wcvec.end());
for ( size_t i = wcvec.size() - 1 , j = 0 ; i >= 0 && j < 10 ; --i, ++j) cout << boost::format( "%6d %s \n " ) % wcvec[i].first % wcvec[i].second;
>
> else throw runtime_error( "No such file" );
>
> catch (exception& e) cerr << e.what() << endl;
> catch (. ) cerr << "Unknown error" << endl;
>
>
VC2010 в конфигурации Release выдал exe-файл размером 73728 байт. Посмотрим внутрь, что там столько жрет:
Заголовок | 0x400 | |
.text | 0xC400 = 50176 байт | В этой секции находится код |
.rdata | 0x3A00 | строки, манглированные имена |
.data | 0x0A00 | еще немного манглированных имен |
.rsrc | 0x0200 | манифест |
.reloc | 0x1200 | рассказ загрузчику как менять адреса в программе, если файл загружен не по тому адресу, по какому ожидал линкер |
Наш файл зависит от библиотек рантайма Visual C: msvcp100.dll, msvcr100.dll (10.0 здесь это версия, XP SP2 в стандартной поставке имеет только до 7.0), и, как и каждый исполнимый файл в системе, от kernel32.dll (тот, в свою очередь всегда тянет за собой ntdll.dll).
В памяти программа занимает максимум 2.5 мегабайта на входном файле в полмегабайта. В адресное пространство мапятся нужные dll, кодовые страницы и .nls, по странице (4 KB) жрут переменные окружения, параметры процесса, PEB и TIB, 574 страницы съедает heap и, вопреки опасениям, стек выделяется не весь сразу, а по мере надобности, в пике 3 страницы на сам стек и 1 на его guard page.
Сборка
Для начала попробуем уменьшить файл ключами компилятора и линкера. Я провел небольшое исследование, воспользовавшись, в том числе, утилитой, которая собирала программу, перебирая найденные ключи. Каких-то 8000 exe-файлов за ночь сборки, и я уже могу поделиться результатами :)
Отмечу, что с теми же опциями + /NODEFAULTLIB минимальный hello world с одним MessageBoxA занимает 640 байт (против 7186 байт в Release по дефолту и 28160 в Debug).
Воспользовавшись Function list в одном известном дизассемблере и самописным скриптом, взвесим различные смысловые части кода в изначальном файле:
Init and deinit | 889 |
main | 1832 |
Exceptions | 4537 |
RTTI | 82 |
Runtime checks | 527 |
PE functions | 332 |
Float arithmetics | 222 |
Memory allocation | 413 |
Locks | 12 |
8846 | |
std memory fns | 1524 |
std::allocator | 368 |
std::exception types | 152 |
std::iostream | 2022 |
Locale and facets | 700 |
std::fstream | 528 |
std bufs | 3142 |
std::map | 400 |
std::_Tree | 2992 |
std::vector | 1984 |
std::basic_string | 3607 |
std::char_traits | 128 |
std::pair | 400 |
std::sort | 4192 |
std::heap | 993 |
std comparisons | 160 |
23292 | |
boost::format | 4128 |
boost::io | 9216 |
boost::optional | 48 |
boost::base_from_member | 64 |
boost::exception | 4184 |
boost::detail | 234 |
17874 | |
Overall: | 50012 |
Легко видеть, что boost, который здесь почти ничего не делает (чего не смог бы сделать банальный printf ), занимает почти треть кода. Заменяем, 42544 → 19744 байт.
Сказал Э, скажи Ю. Заменим весь iostream на fopen, printf и K°. 10865 байт.
Уберем sort и vector, заменив на insertion sort в простом массиве размера 10. 8336 байт.
Заменим map на unordered_map. 8896 байт. Ой, я пошутил, откатимся обратно. Внезапно хэш-таблица, которую бы я писал, если бы фашисты отобрали у меня STL, оказалась толще красно-черного дерева.
Выпилим исключения, все равно они здесь почти ничего не делают, и заменим на обработку ошибок в старом мерзком си-стиле. 8112 байта.
Да, поддержку C++ исключений теперь можно и отключить. 7360 байт. Все, больше ничего не смог придумать :)
Теперь можно расслабиться и проверить энтропию. Популярнейший упаковщик исполнимых файлов UPX пожал исходный файл до 32256 байт, с исправленным не справился из-за /align; проставил /align:512, upx пожал до 6144 байт, но побил файл. 7z пожал исходный файл до 29591 байт, а исправленный до 3955 байт. Есть еще простор для сжатия :)
Напомню, исходный код был написан за 8 минут, а оптимизация ключей компилятора и линкера с последующим переписыванием в более компактный вид заняла 18 часов.
UPD: По совету xproger и wizzard0 попробовал Crinkler, хитрый линкер для интрописателей. Как линкеру, ему нужен .obj, который генерирует студия в одну из папок проекта. Для начала следует собрать проект с отключенной Whole program optimization (/GL, оно немного увеличит студийный exe), затем исполнить команду вида:
crinkler.exe /ENTRY:main /SUBSYSTEM:CONSOLE /COMPMODE:SLOW kernel32.lib user32.lib msvcrt.lib msvcprt.lib main.obj
Подумав минуту, Crinkler выплюнет exe размером 3907 байт, который вообще не сжимается 7z! Теперь, думаю, простор для сжатия закончился :)
Читайте также:
- Любой файл обрабатываемый с помощью приложений как называется
- Privacy filter не дает выключить компьютер
- Замена правого внутреннего привода форд фокус 2
- Компоненты вычислительной сети среда передачи данных по компьютерным сетям виды сред передачи данных
- Разрешить компьютеру переходить в режим отсутствия что это