C-- и игры в байтики
Мне нравится писать короткие программки. Мне нравится потратить время и сэкономить байтик то там, то там, в результате чего иногда набегает до килобайта выигрыша на размере программы до компрессии. Я решил поделиться применяемыми мной методами, часть из них уже используется а нашем сообществе. Так же освещу и некоторые другие хитрости не относящимися к сокращению размера. В принципе всё это можно прочитать в документации, но, авось, кому-нибудь пригодится. Что-то является обходными методами «багов» С--. Что-то может пригодиться и пишущим не на C--.
Инлайн функции
В программе и включаемых файлах может быть описано множество функций, многие из которых не будут вызываться в самой программе, так же часто авторы могут включать в код тестовые функции. После компиляции эти неиспользуемые функции будут давать лишний вес программе. Есть способ не компилировать эти функции в программу описывая их как динамические с помощью ”:” или ”inline” перед объявлением функции. Такая функция будет скомпилирована только если она вызывается из основной программы. Подробнее можно прочитать в документации о «inline-процедурах», «динамических процедурах», «макросах».
Что может пойти «не так»?
Если функция не вызывается напрямую, но в программе есть указатель на неё, то С—не скомпилирует функцию и указателю будет присвоено нулевое значение, чего вы можете не сразу заметить.
Синонимы инлайн функций объявленные через #define так же не будут работать. Я предпочитаю поступиться синонимами, но не включать лишнего в программу, вы можете решить по другому.
Инлайн переменные (осторожно с указателями)
Так же как и функции можно объявить динамические переменные, которые не будут компилироваться, если к ним нет обращения нигде в программе.
Что может пойти «не так»?
Так же как и с функциями обращение к подобной переменной по указателю может не сработать.
Инлайн функции со смешанными аргументами
При объявлении динамических функций со смешанными аргументами (переменные + регистры) надо предварительно объявить функцию где-нибудь выше по коду, иначе у вас пойдут ошибки компиляции. Можно собирать описания функций в отдельных заголовочных файлах. Я предпочитаю сделать это на одну строку выше описания функции, просто сдублировав строку описания функции и поставив «;» в конце первой строки. При переделке данным способом распространенных в сообществе h-- файлов можно достигнуть экономии 0,5-1,5 kb на скомпилированный файл.
Например:
void WriteText(dword x,y,byte fontType, dword color, EDX, ESI);
inline void WriteText(dword x,y,byte fontType, dword color, EDX, ESI)
Включение функций в код.
Функции описанные как inline могут быть включены в код вместо того чтобы вызываться, для этого перед вызовом функции ставится символ «@». Определение функции должно быть выше по коду, чем место вызова функции. Для коротких функций или при однократном вызове функции это даёт выигрыш в размере файла (не генерируется код вызова функции, не генерируется код возврата из функции).
Например:
@ ExitProcess();
Что может пойти «не так»?
У меня данный метод однозначно работает на вызовах функций без аргументов или на регистровых функциях. С другими функциями возможны сбои, будьте внимательны.
Макросы для коротких функций не возвращающих результата
Если функция короткая, то можно описать её как макрос, тогда она всегда будет включаться в место вызова как код и можно не писать «@» перед её вызовом.
Например:
#define ExitProcess() {EAX \= -1; $int 0x40}
Что может пойти «не так»?
Я использую данный метод для описания функций не возвращающих результат (void). С функциями с возвратом результата возможны баги.
Регистровые функции
Функции, которым в качестве аргументов передаются регистры, генерируют меньше кода.
Что может пойти «не так»?
Для использования функций со смешанными аргументами в качестве inline надо предварительно объявлять их. См. выше.
Используйте C-- код вместо asm
Позвольте C—самому генерировать код в зависимости от опций компиляции, чтобы он мог сам использовать способы оптимизации по скорости или по размеру. Да и позже будет проще разобраться с тем, что делает функция, если у вас не очень большой опыт asm кодирования (а почему бы вы иначе полезли в C—вместо простого asm кодирования?).
Например:
При оптимизации по размеру конструкция типа EAX \= 123; будет скомпилирована через push-pop, выдавая более короткий код, а $mov EAX 123 будет однозначно скомпилирована через mov вне зависимости от опций. Я использую $mov для обычно многократно вызываемых в цикле функций типа PutPixel, где скорость мне важнее размера.
Что может пойти «не так»?
Не всегда удаётся сократить генерируемый код при переписывании его в C--, иногда он, наоборот, возрастает. Решайте сами, что важнее, размер или читабельность.
Длинные и короткие операторы – if/IF
Одной из особенностей языка C—является наличие операторов типа if/IF генерирующих более короткий код чем при использовании традиционного оператора. При компиляции программы может генерироваться файл warnings.txt в котором указываются строки в которых стоит использовать короткие операторы. После правок код подсокращается и новый warnings.txt может содержать новые рекомендации по правкам и так несколько итераций. Выигрыш на данных правках часто достигает сотни байт, поэтому я рекомендую пользоваться ими. Лучше подобрать IDE в которой настраивается подсветка данных операторов, чем отказываться от возможности сокращения размера. Во всяком случае, включаемые через h-- файлы функции в которых редко производятся правки однозначно стоит переписать с использованием коротких операторов.
Что может пойти «не так»?
При дальнейших правках код некоторых кусков может увеличиваться и короткий вариант оператора начнёт выдавать ошибку при компиляции. По мне несложно изредка поправить оператор на длинный вариант, чем совсем отказываться от данного функционала.
Длинные и короткие функции (GetKey, LoadLib...)
Так как мы сами пишем и выбираем функции для использования в нашей программе, почему бы не иметь укороченных вариантов функций, генерирующих меньший код, если нам не требуется вся функциональность большого варианта.
Например:
Мы можем одновременно с длинным вариантом функции GetKeyEx иметь короткий
inline word GetKey(){
EAX \= 2; // just read the key from buffer
$int 0x40
return AH;
}
Если мне надо выйти из программы по нажатию клавиши Esc, и больше этой функцией я нигде в программе не воспользуюсь, я могу написать:
IF (@ GetKey() \== 27) ExitProcess();
Функционал по определению нажатия Shift-Ctrl-Alt, и т.п. мне потребуется. Зачем тратить на них байтики?
Пример 2:
У нас есть функция :int load_dll2(dword dllname, import_table, byte need_init) для загрузки функций из библиотек. Некоторые загружаемые функции требуют инициализации и мы вызываем load_dll2 с параметром need_init равным 1. Но могут быть ситуации в которых функции ни одной из используемых библиотек не требуют инициализации, тогда функционал инициализации становится ненужным. Копия функции load_dll2 с выкушенным need_init сгенерирует нам код примерно на 500 байт меньше. И, наоборот, если в программе хотя бы одна из импортируемых функций требует инициализации, то мы не должны использовать нашу урезанную функцию, чтобы в программу не лезли два варианта функции, увеличивая нам код.
Не законченные пункты
Картинки – одна картинка – несколько палитр
raw-gif-png
Глубина цвета
Слияние в одну – общая палитра
Генерация картинок (типа палитры)
Строки – не указывать длину, когда не надо
Строки – не присваивать при объявлении
Строки – не оканчивать нулём вручную
Куча строк известной фиксированной длины – не оканчивать нулём (menu)
Строки – ascii, win, unicode
Структуры с кучей 0 – не присваивать при объявлении
Вreak после ExitFunction()
Проверка единственной кнопки (закрытия)
#pragma option MEOS – дублирование заголовка
#define для констант
mcall
Magic numbers 65000 и т.п.
Отладка lst, map
Кодогенераторы – hiasm и т.п.
Открытые исходники – что стоит поправить?
Автор: lev