* C for Dummies * Aleph * 2000-08-16 * 007 * -----[00]----------[Quote of Day]----------------------------------------- An expert is a man who has made all the mistakes which can be made, in a narrow field. Niels Bohr -----[01]----------[Hot News]--------------------------------------------- 1. Компилятор TC 2.01 сейчас также можно взять здесь: http://members.xoom.com/c4dummies/files/tc201inst.zip (810,299) 2. Книга: Д. Кнут "Искусство программирования для ЭВМ", т. 3 (Сортировка и Поиск) http://www.dore.ru/library/theory/knuttom3.pdf (870,423) или здесь: http://members.xoom.com/c4dummies/files/knuth_v3.zip (812,950) Какому-то умнику пришло в голову закатать это в PDF, так что извлечь оттуда текст назад можно разве что FineReader'ом. -----[02]----------[Blah-Blah-Blah]--------------------------------------- В на редкость бездарной рассылке какой-то антивирусной фирмы внезапно оказался интересный линк. Url: http://www.dizet.com.ua/underground/drmad.html size: 38139 Про С там всего два слова, но тем, кто подобно мне, пересаживался с ЕС-1035 на СМ-4, а затем и на Искра-1030 (Turbo XT / 4.77 Mhz) прочитать все это будет и приятно и интересно. -----[03]----------[Topic]------------------------------------------------ Preprocessor. -----[04]----------[Body]------------------------------------------------- Preprocessor ------------ Как мы уже говорили, препроцессору поручается самая простая часть работы, которую компилятору "лень" делать самому. Тем не менее, это достаточно важная часть, позволяющая правильно организовать процесс компиляции. Обычно, директивы препроцессора размещаются в начале исходного кода, но допустимо их использование в любой точке программы. Директивы препроцессора всегда начинаются с символа '#' (pound sign). Любая строка, начинающаяся с символа '#', рассматривается как директива препроцессора, если только '#' не является частью символьной строки, символьной константы или частью комментария. Начальному символу '#' может предшествовать любое число пробелов и табуляций (но не символов новой строки). Директивы препроцессора НЕ должны заканчиваться символом ';'. Три основные функции, выполняемые препроцессором: - Включение файлов - Условная компиляция - Макроподстановка #include -------- Включение файлов задается директивой #include. #include <file_name> заменяется на содержимое файла с именем 'file_name'. Очевидно, 'file_name' должно быть правильным именем файла в данной OS. Если использована эта форма (#include <file_name>), то просматриваются только каталоги, заданные опцией -I командной строки. (Каталоги, перечисленные в меню по опции O/Environment/Include, эквивалентны маршруту, указанному с помощью опции -I pathname в командной строке). Второй вариант этой директивы #include "file_name" В этом случае, просмотр каталогов начинается с текущей директории. Если препроцессор не нашел требуемый файл в каталоге по умолчанию, тогда он переходит к просмотру каталогов, заданных опцией компилятора -I. Третий вариант является простым обобщением двух предыдущих: им включаемого файла задается через макропеременную или набор макропеременных. После выполнения всех макроподстановок в результате должно получиться <file_name> или "file_name". Обратите внимание, что часть строки, заключенная в кавычки или угловые скобки (angle brackets), не будет подвергнута макрорасширению. Макрорасширение должно создавать текст, который читается как нормальна #include директива. В директиве #include нельзя использовать объединение литерных строк и вставку лексем. Мы уже использовали директиву #include в нашей программе macro.c и видели результат ее обработки препроцессором. Условная компиляцию ------------------- Условная компиляция позволяет управлять включением части кода, в зависимости от условия, вычисляемого во время компиляции программы. Например, Ваш код может содержать различные версии для различных компиляторов или часть кода не должна компилироваться в Demo версии программы. Для условной компиляции используется пара директив #if и #endif и их варианты: #ifdef, #ifndef, а также, при необходимости, #else, #elif. Простой пример: #if condition /* do smth right */ #else /* do smth left */ #endif Вычисляется константное выражение condition. Это выражение не должно содержать ни одного оператора sizeof, приведения типа или enum-констант. Если оно имеет ненулевое значение, то будут включены все строки, вплоть до следующего #else (если присутствует) или #endif. Все директивы условной компиляции должны заканчиваться в исходной программе или включаемом файле, в котором они начались. Турбо Си поддерживает определение условной компиляции по K&R с помощью замены соответствующих строк на пустые. #ifdef является сокращенной записью для #if defined, а #ifndef - #if undefined. Вместо undefined можно использовать выражение !defined. Как Вы помните, оператор '!' означает логическое отрицание. Выражение defined name в #if есть 1, если 'name' было определено и 0 в противном случае. Syntax #if constant-expression-1 <section-1> <#elif constant-expression-2 newline section-2> . . . <#elif constant-expression-n newline section-n> <#else <newline> final-section> #endif Description Условные директивы #if, #elif, #else, and #endif работают подобно обычным условным операторам, о которых мы поговорим позднее. Директива #elif является сокращением для #else #if или #else if. Если результат вычисления constant-expression-1 (subject to macro expansion) ненулевой (true), то линии кода (возможно пустые) представленные в section-1, (это могут быть как директивы препроцессора, так и исходный С код) будут обработаны и переданы компилятору. Иначе, если результат вычисления constant-expression-1 нулевой (false), код в section-1 игнорируется (no macro expansion and no compilation). В случае true, после того, как section-1 была препроцессированна, управление передается на соответствующий #endif (который заканчивает это логическое выражение) и происходит переход к обработке следующей секции. with next-section. В случае false, управление передается следующему #elif (если есть) и вычисляется constant-expression-2. В случае true, section-2 препроцессируется, после чего управление передается на соответствующий #endif. Иначе, если результат вычисления константного выражения constant-expression-2 есть false, управление передается к следующему #elif, ид. пока не #else или #endif будет достигнут. Директива #else необязательна и может употребляться только внутри условного оператора совместно с директивами #if и #endif Директива #else используется в альтернативном условии, для которого все предыдущие тесты дали false. Директива #else включает код до заключающего #endif. Назначение и использование директивы #endif очевидно. #endif заканчивает логическую последовательность. Обрабатываемая секция сама может содержать вложенные условные операторы, любого уровня вложенности. Каждому #if должен соответствовать собственный закрывающий #endif. Как окончательный результат препроцессирования условного выражения, только одна секция (возможно пустая) будет передана компилятору. Пропущенные секции важны только для сохранения трека всех вложенных условий, так что каждому #if может быть корректно поставлен в соответствие его собственный #endif. Результатом вычисления тестируемого константного выражение должна быть константа целого типа. Директивы #ifdef и #ifndef. Использование этих директив совершенно аналогично разобранному выше случаю. Syntax #ifdef identifier #ifndef identifier Description Условные директивы #ifdef и #ifndef позволяют выяснить, является ли идентификатор в данный момент (в этой точке компиляции) определенным или нет. Идентификатор определен, если ранее для него была использована директива #define и это определение все еще в силе (не отменено). Строка #ifdef identifier имеет тот же самый эффект, как #if 1 если идентификатор в данный момент определен, и тот же самый эффект как #if 0 если идентификатор в данный момент НЕ определен. Директива #ifndef определяет истинность условия "not-defined", так что строка #ifndef identifier имеет тот же самый эффект, как #if 0 если идентификатор в данный момент определен, и тот же самый эффект как #if 1 если идентификатор в данный момент НЕ определен. Синтаксис этих директив полностью следует использованию директив #if, #elif, #else, and #endif. Note: идентификатор, определенный как NULL, считается определенным. Макроподстановка ---------------- Директива #define Syntax #define macro_identifier <token_sequence> При необходимости, запись может быть продолжена на следующей строке, при этом последним символом строки должен быть '\' (back slash) - признак продолжения. macro_identifier Идентификатор для данного макро. Каждое вхождение данного идентификатора в исходный код будет заменено (в соответствии с директивой #define) на token_sequence (с некоторыми исключениями). Такая замена называется макрорасширением. token_sequence Последовательность, заменяющая macro_identifier. token_sequence также называют телом макроса. Если эта последовательность пуста, то макро идентификатор удаляется из исходного кода. Description Директива #define определяет макро. Макро доставляет механизм для токенизированной замены с/без набором формальных, function-like параметров. Любое вхождение macro_identifier внутри символьной строки, символьной константы или комментария НЕ будет подвергнуто макрорасширению. Также, макро не будет расширено в момент собственного расширения. Так, #define A A не будет расширено бесконечно. Отмена макро. Syntax #undef macro_identifier Description #undef отменяет любое предыдущее определение макро и макро идентификатор делается неопределенным. Никаких макрорасширений внутри #undef строки не производится. Возможность быть определенным или неопределенным, важное свойство идентификатора, независимо от его текущего определения. Условные директивы #ifdef and #ifndef используются для выяснения того, определен в текущий момент идентификатор или нет, доставляя гибкий механизм управления многими аспектами компиляции. После того, как макроидентификатор был определен, он может быть в любой момент переопределен с той же самой или отличной token_sequence. Попытка переопределения уже определенного макроидентификатора, приведет к выдаче предупредительного сообщения (warning), если новое определение не совпадает в точности с существующим. Если макро определение должно существовать в другом H-файле, то предпочтительная стратегия выглядит так: #ifndef BLOCK_SIZE #define BLOCK_SIZE (512) #endif Средняя линия (#define) будет пропущена, если BLOCK_SIZE уже определен. Если же BLOCK_SIZE в данный момент не определен, то средняя линия будет препроцессированна и определит его. Директивы препроцессора НЕ должны заканчиваться символом ';'. Любой символ, появляющийся внутри token_sequence, включая ';', будет появляться в макрорасширении. Тело макроса, token_sequence заканчивается с появлением первого символа новой строки, которому НЕ предшествует '\' (back slash). Любая whitespace последовательность (пробел, табуляция, комментарий) будет замещена на одиночный пробел. Теперь, используя директивы препроцессора, мы можем улучшить программу macro.c, так чтобы она всегда компилировалась и работала для любой модели памяти (очень скоро мы выясним, что это такое). Создайте файл macro3.c и поместите в него следующий код: /* ****************************************************************** ** @@ MACRO - A program for testing Built-In PseudoVariables values ** ****************************************************************** */ #include <stdio.h> void main() { printf("\nCurrent Line: %5d",__LINE__); printf("\nCurrent File: %s", __FILE__); printf("\nStarted Date: %s",__DATE__); printf("\nStarted Time: %s",__TIME__); #ifdef __STDC__ printf("\nANSI Standard: %s",__STDC__); #endif #ifdef __TURBOC__ printf("\nTurbo C ver.: %04x",__TURBOC__); #endif #ifdef __PASCAL__ printf("\nPascal Call Convention: %d",__PASCAL__); #endif #ifdef __CDECL__ printf("\nC Call Convention: %d",__CDECL__); #endif #ifdef __MSDOS__ printf("\nMS DOS Platform: %d",__MSDOS__); #endif #ifdef __TINY__ printf("\n__TINY__ Memory Model: %d",__TINY__); #endif #ifdef __SMALL__ printf("\n__SMALL__ Memory Model: %d",__SMALL__); #endif #ifdef __MEDIUM printf("\n__MEDIUM Memory Model: %d",__MEDIUM __); #endif #ifdef __COMPACT__ printf("\n__COMPACT__ Memory Model: %d",__COMPACT__); #endif #ifdef __LARGE__ printf("\n__LARGE__ Memory Model: %d",__LARGE__); #endif #ifdef __HUGE__ printf("\n__HUGE__ Memory Model: %d",__HUGE__); #endif printf("\nCurrent Line: %5d",__LINE__); printf("\nStarted Date: %s",__DATE__); printf("\nStarted Time: %s",__TIME__); } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ OK. Сейчас это компилируется, и вот что я получил (надеюсь, Вы тоже): > Current Line: 9 > Current File: MACRO3.C > Started Date: Aug 14 2000 > Started Time: 21:24:01 > Turbo C ver.: 0201 > C Call Convention: 1 > MS DOS Platform: 1 > __SMALL__ Memory Nodel: 1 > Current Line: 59 > Started Date: Aug 14 2000 > Started Time: 21:24:01 Обратите внимание, что из всех строк, где были заданы модели памяти, выведена только одна: __SMALL__ Memory Model: 1, причем макро __SMALL__, заданное в строке дважды, в левой части осталось без изменений (так как находилось внутри символьной строки и не подверглось макрорасширению), а в правой было заменено препроцессором на единицу. Используйте утилиту cpp.exe, для того чтобы детально изучить вывод препроцессора. Обратите внимание, что часть строк была заменена на пустые. Пропущенные строки не удаляются, для того, чтобы сохранить правильную нумерацию, соответствующую исходному тексту. Эти номера строк могут быть затем использованы компилятором, при выдаче сообщений об ошибках и дебаггером, для правильного отображения исходного текста при отладке. Оставшиеся директивы препроцессора используются относительно редко. # character ----------- Символ # (pound sign) может быть использован как префикс формального макро аргумента, для преобразования действительного аргумента в символьную строку после макрорасширения. Напишем простенькую программу macro4.c /* ****************************************************************** ** @@ MACRO - A program for testing Built-In PseudoVariables values ** ****************************************************************** */ #include <stdio.h> #define LINE(Number) (#Number) void main() { clrscr(); printf("\n%s = %d\n",LINE(__LINE__),__LINE__); } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ Здесь Вы видите кое-что новенькое: директива #define определяет макро с параметром и, кроме того, используется вызов библиотечной функции clrscr() для очистки экрана (Clear Screen). Все самое интересное происходит внутри библиотечной функции форматной печати printf() - макро __LINE__ использовано дважды: первый раз в качестве аргумента другого макро LINE(), и второй раз в качестве третьего аргумента функции печати. Второй аргумент функции печати - это макро LINE(), а первый - строка формата, определяющая, что первый выводимый на печать параметр имеет формат символьной строки ("%s"), а второй - целого числа ("%d"). После всех этих долгих объяснений вывод программы покажется Вам предельно лаконичным: > 13 = 13 Гм. Ну и что? А вот ! В первый раз (до знака равенства) 13 напечатано как символьная строка "13", по формату "%s" а во второй раз - как целое десятичное число 13, по формату ("%d"). Почувствуйте разницу! Оператор '#' преобразовал макро в символьную строку. Это позволяет Вам "на лету" конструировать собственные имена и переменные. Изменим немного текст нашего примера: /* ****************************************************************** ** @@ MACRO - A program for testing Built-In PseudoVariables values ** ****************************************************************** */ #include <stdio.h> #define LINE(Number) (#Number) void main() { clrscr(); printf("\n%s = %d\n",LINE(__LINE__ * 77),__LINE__ + 988); } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ Как Вы думаете, что будет напечатано? У меня это выглядит так: > 13 * 77 = 1001 А вот выглядит вывод препроцессора (фрагмент): > macro4.c 9: void main() > macro4.c 10: { > macro4.c 11: clrscr(); > macro4.c 12: > macro4.c 13: printf("\n%s = %d\n",("13 * 77") ,13 + 988); > macro4.c 14: } Операнд макро LINE() - __LINE__ * 77 был подвергнут макрорасширению и превратился в 13 * 77, после чего оператором # был преобразован в символьную строку "13 * 77". Операнд __LINE__ + 988 был подвергнут макрорасширению и превратился в 13 + 988, сумма этих двух чисел и была напечатана. Обратите внимание на одну маленькую деталь - после ("13 * 77") и перед следующей за ним запятой препроцессор вставил пробел, которого не было в исходном тексте программы. Возможно, он не во всех случаях будет столь предусмотрителен, но побеспокоился об этом заранее и заключил свое макро в круглые скобки (round brackets). Важно: практически всегда, макро должно быть заключено в круглые скобки. Это позволит избежать нежелательного "слипания" текста после макрорасширения. Надо сказать, что есть и еще одно, ради совместимости со стандартом, использование символа #. Syntax # Description NULL директива препроцессора. Состоит из строки, содержащей одиночный символ #. Всегда игнорируется. ## character ------------ Оператор ## позволяет вставить или слить два токена вместе, разделив их символом '##' и (необязательно) whitespace с любой стороны. Препроцессор удалит whitespace и ##, скомбинировав два отдельных токена в один новый. Это также полезно для конструирования собственных имен и переменных. Создайте файл macro5.c и поместите в него следующий код: /* ****************************************************************** ** @@ MACRO - A program for testing Built-In PseudoVariables values ** ****************************************************************** */ #include <stdio.h> #define NEW_NAME(Prefix,Body,Suffix) ("Line #" ## \ #Prefix ## \ " ! " ## \ " Date: " ## \ Body ## \ " , " ## \ " Time: " ## \ Suffix) void main() { clrscr(); printf("\n%s\n",NEW_NAME(__LINE__,__DATE__,__TIME__)); printf("\n%s\n",NEW_NAME(__LINE__,__DATE__,__TIME__)); printf("\n%s\n",NEW_NAME(__LINE__,__DATE__,__TIME__)); } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ Обратите внимание на использование символа '\' (backslash) для продолжения длинного макроса на следующей строке. А вот результат работы программы: > Line #20 ! Date: Aug 14 2000 , Time: 23:27:53 > > Line #22 ! Date: Aug 14 2000 , Time: 23:27:53 > > Line #24 ! Date: Aug 14 2000 , Time: 23:27:53 Из трех отдельных переменных: Prefix, Body, Suffix, мы сконструировали одну новую - символьную строку и напечатали ее. Изучите внимательно вывод препроцессора, чтобы понять, как это произошло. TIP: Компилятор сливает вместе последовательно расположенные символьные строки. Например, запись "12345""67890" эквивалентна "1234567890". #error ------ Нетрудно догадаться, что эта директива предназначена для выдачи сообщений об ошибках. Syntax #error errmsg Description Директива #error генерирует сообщение: Error: filename line# : Error directive: errmsg Эта директива обычно используется в условном операторе. Если нежелательное условие оказалось истинным, выдается сообщение об ошибке и прекращается компиляция. Создайте файл macro6.c и поместите в него следующий код: /* ****************************************************************** ** @@ MACRO - A program for testing Built-In PseudoVariables values ** ****************************************************************** */ #include <conio.h> void main() { clrscr(); #error "Just a test for #error directive" } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ Обратите внимание, что в директиве #include на этот раз указан файл conio.h, в котором находится прототип библиотечной функции clrscr(). Возможно, Вам интересно, как я догадался, что он помещен именно там ? Очень просто: в окне редактора TC подведите курсор к слову clrscr и нажмите комбинацию клавиш CTRL + F1 - выскочит окошко контекстного Help'а: +---------------------- Help ----------------------+ | | | clrscr: clears text mode window | | | | void clrscr(void); | | | | Prototype in conio.h | | | | See also clreol | | delline | | window | | | +--------------------------------------------------+ А вот скомпилировать программу на этот раз нам не удастся - в точном соответствии с назначением директивы #error. И вот какой текст будет выведен: +---------------------------------- Message -----------------------------------+ | Compiling C:\TEST\MACRO6.C: | | Error C:\TEST\MACRO6.C 11: Error directive: "Just a test for #error directive| +------------------------------------------------------------------------------+ #line ----- Возможно, эта директива Вам никогда не потребуется - она предназначена для разработчиков компиляторов и других системных утилит и результат ее применения скрыт от прямого просмотра. Syntax #line integer_constant <"filename"> Description "filename" имеет тот же самый формат, как и директиве #include. Угловые скобки означают, в данном случае, что это необязательный элемент синтаксиса (может быть опущен). Директива #line сообщает препроцессору, что следующая строка имеет номер integer_constant и отсчет строк далее должен вестись от этого значения. Если указан параметр "filename", то препроцессор информируется о том, что этот код взят из файла с именем "filename" (проверка реального существования файла с таким именем не делается). Это может быть полезно для организации перекрестных ссылок и выдачи сообщений об ошибках. Если Ваша программа включает секции, заимствованные из другого программного файла, бывает полезно ссылаться на этот код, используя нумерацию строк оригинального файла, а не обычную последовательную нумерацию строк композитного файла. В этом случае, директива #line индицирует, что следующий исходный код в действительности взят из файла "filename" начиная со строки с номером integer_constant. Если файл "filename" уже был однократно определен, то в последующих директивах #line, относящихся к этому же файлу, аргумент "filename" может быть опущен. Для #line допустимы те же самые макрорасширения, что и в #include. Создайте файл macro7.c и поместите в него следующий код: /* ****************************************************************** ** @@ MACRO - A program for testing Built-In PseudoVariables values ** ****************************************************************** */ #include <stdio.h> void main() { clrscr(); printf("\n%d\n",__LINE__); /* #line 100 "c:\test\test.tst" */ printf("\n%d\n",__LINE__); } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ Директива #line преднамеренно закомментирована. Компилировать этот файл не требуется, достаточно пропустить его через препроцессор. Вот интересующий нас фрагмент (комментарий, понятно, исчез): > macro7.c 7: void main() > macro7.c 8: { > macro7.c 9: clrscr(); > macro7.c 10: > macro7.c 11: printf("\n%d\n",11); > macro7.c 12: > macro7.c 13: > macro7.c 14: > macro7.c 15: printf("\n%d\n",15); > macro7.c 16: > macro7.c 17: } А теперь уберем символы комментария из 13 строки, так чтобы директива #line была обработана препроцессором. Файл с именем "c:\test\test.tst" в действительности не существует, но сейчас это не имеет значения. Снова пропустим наш код через препроцессор: > macro7.c 7: void main() > macro7.c 8: { > macro7.c 9: clrscr(); > macro7.c 10: > macro7.c 11: printf("\n%d\n",11); > macro7.c 12: > macro7.c 13: > c:\test\test.tst 100: > c:\test\test.tst 101: printf("\n%d\n",101); > c:\test\test.tst 102: > c:\test\test.tst 103: } Сама строка 13 снова заменена на пустую, но в результате ее действия, следующая строка кода имеет теперь номер 100 и соответствует файлу "c:\test\test.tst". -----[05]----------[Homework]--------------------------------------------- Вопросы не обязательно основаны на материалах выпуска и, как это обычно и бывает в жизни, могут потребовать от Вас самостоятельного исследования. 1. Поскольку Вы являетесь ведущим экспертом фирмы Hytrosoft Inc. по препроцессорам, в группе занимающейся разработкой нового компилятора С, Вам поручено выяснить желательность расширения стандарта ANSI C и добавления новых директив препроцессора. Ваши предложения? 2. Если Вы имеете опыт программирования на таких упрощенных языках, как Basic или Pascal, можете ли Вы воспользоваться препроцессором С для расширения возможностей этих языков? 3. Для каких целей, связанных с подготовкой и обработкой текста (например, публикации WEB-страниц в Интернете), мог бы по Вашему мнению оказаться полезным препроцессор С или другая аналогичная программа? -----[06]----------[Info]------------------------------------------------- Вопросы и замечания принимаются по адресу: aleph@canada.com Предыдущие выпуски доступны со страницы архива: http://www.subscribe.ru/archive/comp.prog.c4dummies/ или (в упакованном виде): http://members.xoom.com/c4dummies/archive/000.zip . . . http://members.xoom.com/c4dummies/archive/006.zip Рассылка дублируется (только текстовый вариант) с сервера www.egroups.com Подписка: c4d-subscribe@egroups.com -----[--]----------[The End]----------------------------------------------

© Gazlan 2009 * gazlan@yandex.ru

Hit Counter