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