![]()
SAMInside
Версия 2.7.0.0 (05.02.2012)
Скачать PasswordsPro Версия 3.1.2.0 (01.05.2012)
Скачать Extreme GPU Bruteforcer Купить программу
Форум
Сообщений: 86969
Словари Файлов: 89
Rainbow-таблицы Таблиц: 132
Библиотека Документов: 501
Генератор хэшей Алгоритмов: 345
Подписка на новости
Подписчиков: 1528
Посетителей сегодня: 5875 Всего посещений: 7628008 |
С-шные трюки от мыщъх'а (выпуск 1Fh)Автор: (c)Крис Касперски ака мыщъх Язык Си не предоставляет никаких средств для временного отключения блоков кода и большинство программистов делают это с помощью комментариев. Казалось бы - что может быть проще и о каких трюках тут вообще можно говорить? На самом же деле, комментарии - не только не единственный, но и едва ли не самый худший прием среди прочих, о которых мы сейчас и поговорим! Трюк 1 - комментарии, ремарки и помаркиСистемы контроля версий как раз и создавались для того, чтобы обеспечить легкий, прозрачный и непротиворечивый механизм безопасной правки исходных текстов, инвариантный по отношению к самому языку. Однако на практике системы контроля версий используются только для организации совместной работы над проектом, да и то не всегда. Уж слишком много телодвижений приходится совершать всякий раз, а программисты - люди ленивые. Если нам необходимо временно отключить блок кода, намного проще закомментировать его, а потом удалить комментарии, подключая его обратно. Быстро. Дешево. Сердито. Но увы... потенциально небезопасно с точки зрения внесения новых ошибок и развала уже отлаженной программы, чего допускать ни в коем случае нельзя. А потому прежде, чем идти дальше, сформулируем перечень требований, предъявляемый к механизмам отключения кода:
Удовлетворяют ли комментарии указанным требованиям?! А вот и нет! Комментарии в стиле Си (/* */) очень удобны, поскольку позволяют отключать огромные блоки кода нажатием всего четырех клавиш, к тому же они могут располагаться в любом месте строки, а не только в ее начале. Однако отсутствие поддержки вложенности создает серьезные проблемы. Например:
/* <- ошибка! закомментированный блок уже содержит /* */
for (a = 0; a < N; a++) { /* for (b = 0; b < M; b++) if (!strcmp(name_array[a], vip_array[b])) continue; */ // DeleteFile(name_array[a]); pritnf("%d %s\n", a, name_array[a]); } */ Листинг 1. Демонстрация некорректного использования комментариев /* */ для временного отключения блоков кода. Попытка выключить цикл for (a,,) ведет к ошибке компиляции - комментарии /* */ не могут быть вложенными и в таких случаях программисты используют альтернативу в виде "//" допускающую вложенность, но, увы, вручную проставляемую в начале каждой строки, что очень утомительно и совершенно непроизводительно, если, конечно, не использовать макросы, поддерживаемые средой разработки (а практически все среды разработки их поддерживают). Аналогичным образом осуществляется и снятие комментариев. И все было бы хорошо, да вот неоднозначности с уровнем вложенности делают отключение блоков небезопасным. В нашем случае мы имеем три раздельных отключаемых блока кода. Во-первых, это заблокированная проверка принадлежности удаляемого файла к vip_array, во-вторых, это, собственно, само удаление файла (заблокированное и замененное отладочной печатью через printf) и в-третьих, комментарий, пытающийся отключить цикл for(a,,) со всем, что в нем находится. Отключаются блоки кода очень просто, а вот обратное утверждение уже неверно. Никаким автоматизмом тут уже и не пахнет, в результате чего нам приходится разбираться с назначением каждого блока самостоятельно. Однако если немного поколдовать над комментариями... Пусть следом за "//" идет цифра (или буква), указывающая принадлежность текущей комментируемой строки к блоку кода. Продвинутые среды разработки типа Microsoft Visual Studio поддерживают развитый макроязык, позволяющий выполнять лексический анализ, удаляя только те комментарии, за которыми идет заданная буква/цифра. Это может выглядеть, например, так:
//3 for (a = 0; a < N; a++)
//3 { //3 //2 //3 //2 for (b = 0; b < M; b++) //3 //2 if (!strcmp(name_array[a], vip_array[b])) continue; //3 //2 //3 //1 // DeleteFile(name_array[a]); //3 pritnf("%d %s\n", a, name_array[a]); //3 } Листинг 2. Имитация многоуровневой структуры отключаемых блоков исходного кода посредством комментариев. Проблема вложенности решена на 100%, проблема многоварианости - на 50% (после удаления комментария //1 мы также должны удалить, а точнее - временно заблокировать следующую за ним строку с отладочной печатью), однако в целом предложенная техника намного более удобна и единственный серьезный недостаток - привязка программиста к конкретной среде с набором пользовательских макросов. Менее серьезный недостаток - ассемблерные вставки, как правило, не поддерживают Си/Си++ комментариев и потому должны обрабатываться отдельно, усложняя реализацию нашего макродвижка и сводя его преимущества на "нет". Трюк 2 - директивы условной трансляцииРазработанные для поддержки многовариантного кода директивы условной трансляции оказались практически невостребованными (речь, разумеется, идет только о временном выключении кода), что очень странно - директивы условной трансляции намного более эффективны, чем комментарии и пример, приведенный ниже, доказывает этот тезис.
#define _D1_ // блок _D1_ включен
//#define _D2_ // блок _D2_ выключен #define _D3_ // блок _D3_ включен #ifdef _D1_ for (a = 0; a < N; a++) { #ifdef _D2_ for (b = 0; b < M; b++) if (!strcmp(name_array[a], vip_array[b])) continue; #endif #ifdef _D3_ DeleteFile(name_array[a]); #else pritnf("%d %s\n", a, name_array[a]); #endif } #endif Листинг 3. Директивы препроцессора, отключающие блоки кода. Проблема вложенности решается сама собой, многовариантность поддерживается очень хорошо, позволяя нам включать/выключать определенные блоки, не затрагивая остальных, причем при подключении "DeleteFile(name_array[a])" автоматически отключается отладочная печать и наоборот. В результате чего риск развала программы уменьшается до нуля. Самое интересное, что директивы условной трансляции ничуть не хуже работают и с ассемблерными вставками!
__asm {
xor eax,eax #ifdef _D1_ PUSH file_name CALL DeleteFile #endif } Листинг 4. Директивы препроцессора, отключающие ассемблерные инструкции внутри ассемблерных вставок. Конечно, писать "#if def _Dx_" намного длиннее, чем "//" или "/* */", однако это не проблема - а клавиатурные макросы на что?! Хотя, про нежелание связываться с макросами мы уже говорили. Ну, макросы - это ладно. Хуже всего, что отключенные блоки кода не попадают в релиз и если у конечного пользователя программа начнет дико глючить, у нас не будет никакой возможности отключить их без перекомпиляции всего кода. Трюк 3 - ветвленияФинальный прием устраняет основные недостатки предыдущего трюка, добавляя к нему свои собственные достоинства, а достоинств у него... Короче, намного больше одного. Идея заключается в использовании конструкции if (_Dx_), а при необходимости и if (_Dx_) else. Оператор "if", стоящий перед одиночным блоком кода, не требует замыкающего "#endif", что ускоряет процесс программирования и не так сильно загромождает листинг. Но это мелочь. Гораздо важнее, что если _Dx_ - константа (например, "1"), то оптимизирующий компилятор выбрасывает вызов if, удаляя лишний оверхид. Если же _Dx_ - переменная (глобальная, конечно), то компилятор оставляет ветвление "как есть", давая нам возможность управлять поведением программы - если у пользователей возникнут проблемы из-за ошибки в плохо отлаженном блоке кода, то этот блок можно отключить (естественно, если значения флагов вынесены в конфигурационный файл или доступны через пользовательский интерфейс, но это уже несущественные детали реализации). Пример использования ветвлений для отключения блоков кода приведен ниже:
#define _D1_ 0 // блок _D1_ выключен (ветвление в релиз не попадает)
#define _D3_ 1 // блок _D3_ включен (ветвление в релиз не попадает) int _D2_ 1 // блок _D2_ включен (ветвление попадает в релиз!) if (_D1_) for (a = 0; a < N; a++) { if (_D2_) for (b = 0; b < M; b++) if (!strcmp(name_array[a], vip_array[b])) continue; if (_D3_) DeleteFile(name_array[a]); else pritnf("%d %s\n", a, name_array[a]); } Листинг 5. Использование ветвлений для выключения блоков кода. Как мы видим, листинг 5 намного компактнее и нагляднее листинга 4, так что при всем уважении к директивам условной трансляции, они идут лесом. А вот ветвления можно использовать для выключения блока ассемблерных вставок (о чем, кстати говоря, умалчивает штатная документация, но следующий пример компилируется вполне нормально):
#define _D1_ 0
if (_D1_) __asm{ INT 03 } Листинг 6. Использование ветвлений для выключения ассемблерных вставок. Ветвления, конечно, тоже не лишены недостатков, однако для временного выключения блоков кода они намного лучше, удобнее и продуктивнее, чем комментарии. Естественно, существуют и другие средства. Взять хотя бы "return", позволяющий одним движением руки погасить блок кода до самого конца функции. Критикуемый GOTO - отличная штука, но только в малых дозах. Иначе программа превращается в настоящее спагетти, которое практически невозможно распутать. |