* C for Dummies * Aleph * 2000-08-06 * 006 * -----[01]----------[Quote of Day]----------------------------------------- Закон Мэрфи: Если какая-либо неприятность может случиться, она случится. Следствия: 1. Все не так легко, как кажется. 2. Всякая работа требует больше времени, чем вы думаете. 3. Из всех неприятностей произойдет именно та, ущерб от которой больше. 4. Если четыре причины возможных неприятностей заранее устранены, то всегда найдется пятая. 5. Предоставленные самим себе, события имеют тенденцию развиваться от плохого к худшему. 6. Как только вы принимаетесь делать какую-то работу, находится другая, которую надо сделать еще раньше. 7. Всякое решение плодит новые проблемы. -----[02]----------[Blah-Blah-Blah]--------------------------------------- В рассылке "Исторические анекдоты от Старого Ворчуна" за пятницу 28.07 (http://subscribe.ru/archive/funny.abhoc) попалась мне забавная заметка на тему трансляторов: > Айседора Дункан в Москве > > Айседора Дункан увлеклась коммунистической идеологией, и в 1921 году > приехала в Москву, где она открыла школу пластики для пролетарских > детей. > > При первой встрече с бедно одетыми детьми Айседора произнесла > по-английски следующую речь: > > "Дети, я не собираюсь учить вас танцам: вы будете танцевать, когда > захотите, те танцы, которые подскажет вам ваше желание. Я просто хочу > научить вас летать, как птицы, гнуться, как юные деревца под ветром, > радоваться, как радуется майское утро, бабочка, лягушонок в росе, > дышать свободно, как облака, прыгать легко и бесшумно, как серая > кошка..." > > После чего обращается к переводчику: "Переведите". > > Тот переводит: > > "Детки! Товарищ Изидора вовсе не собирается обучать вас танцам, потому > что танцульки являются пережитком гниющей Европы. Товарищ Изидора > научит вас махать руками, как птицы, ластиться вроде кошки, прыгать > по-лягушиному, то есть, в общем и целом, подражать жестикуляции > зверей..." -----[03]----------[Topic]------------------------------------------------ Comments. Built-In PseudoVariables. -----[04]----------[Body]------------------------------------------------- Comments -------- Программы создаются для компьютеров, но пишутся, читаются и исправляются людьми, поэтому легкость сопровождения - одна из важнейших характеристик программы. В самом деле, если программа на что-то пригодна (а Вы, надеюсь, собираетесь писать именно такие программы), то ее жизненный цикл часто оказывается много дольше, изначально предполагавшегося. Свежая иллюстрация - деньги и силы потраченные на исправление "BUG-2000". Самое большое зло в программировании - небрежный и грязный код. Никто не захочет в нем разбираться и, обычно, такой код просто выбрасывается (хорошо, если вместе с автором) и пишется заново. Правильный стиль вырабатывается годами. На эту тему существует множество книг и еще больше внутрифирменных стандартов. Ведутся почти религиозные споры о правилах расстановки скобок, способах именования переменных и числе пробелов табуляции. Я не буду придерживаться рекомендаций какой-то конкретной школы (бесспорных не так много), а буду исходить из требований ясности текста и удобства отладки. На мой взгляд, не так важно, какого именно стиля придерживается данный конкретный программист, как важно, чтобы этот стиль строго соблюдался на всем протяжении программы. Сейчас мы поговорим только о комментариях. Язык С допускает только один вид комментариев: все что заключено между парами символов '/*' и '*/'считается комментарием (включая сами эти пары символов) и игнорируется компилятором (точнее сказать, просто выбрасывается препроцессором). Комментарий может быть помещен в любом месте программы, где по синтаксису допустим пробел. Правила "хорошего" программирования требуют сначала писать комментарии, и лишь затем заполнять промежутки между ними кодом программы. По сути, это лишь означает, что принимаясь за кодирование, Вы уже полностью представляете, что нужно делать. Если это не так - Вы неверно выбрали профессию. Что нужно комментировать и что НЕ нужно комментировать? Это слишком сложный вопрос, чтобы можно было дать на него исчерпывающий ответ, но некоторые простые правила смогут помочь. Программирование - это творчество в его самом чистом рафинированном виде, сравнимое только с ремеслом писателя (Creative Writing). Вас не ограничивает ничего, кроме правил языка и собственной фантазии. Подходите к написанию программы, как писатель подходит к своему произведению. Опишите время и место действия, опишите персонажей и удалите все, не относящееся к предмету разговора. Воспользуйтесь бритвой Оккама - в программе не должно быть лишних сущностей. В идеале, Ваш комментарий должен сжато и четко описывать постановку задачи, обоснование выбора данного метода решения, особенности реализации, ожидаемые результаты и способы проверки их правильности. Этап кодирования сводится, таким образом, просто к переводу Вашего комментария на выбранный язык программирования. Разумеется, необходимо пояснить все неочевидные решения, неявные особенности и меры, принятые для оптимизации кода. Комментарии должны быть написаны не так, чтобы их можно было понять, а так, чтобы их нельзя было не понять. Другой важный аспект, о котором позднее мы поговорим отдельно - форматирование текста. Так же, как книга, Ваш текст должен содержать логически связные части - главы и параграфы. В программах, пробел и перевод строки играют такую же важную роль, как и в печатном тесте. Присоединение к компьютеру печатной машинки открыло двери для несчастных со скверным почерком. В те времена, когда программы печатались на перфокартах, на дверях вычислительных центров обычно висело объявление, что неразборчиво написанные задания на перфорацию не принимаются. Бедняга с плохим почерком почти не имел шансов стать программистом. К сожалению, прогресс имеет и оборотную сторону - мир заполнен плохим неразборчивым кодом. Запомните: Прежде всего, Ваша программа должна хорошо читаться (даже если это ее единственное достоинство). В ясно написанной и правильно форматированной программе легче понять идею и проще искать ошибки. Существует немало программ автоматического форматирования С-кода, и если Вы патологически неспособны к правильному форматированию текста, Вам придется воспользоваться одной из них. Built-In PseudoVariables ------------------------ Turbo C содержит заранее определенный набор псевдопеременных, полезных при организации условной компиляции и/или отладке. Стандарт ANSI С определяет пять встроенных макросов, каждый из которых начинается и оканчивается символами '__' (double underline). __LINE__ Целое десятичное число, являющееся номером строки в файле с исходным текстом программы. Строки нумеруются начиная с 1. __FILE__ Символьная строка, содержащая имя обрабатываемого исходного файла. __DATE__ Символьная строка, содержащая дату начала обработки текущего исходного файла. Дата имеет формат mmmddyyyy, где mmm - месяц (Jan, Feb и т.д.), dd - число текущего месяца (1...31; в 1-ой из двух позиций dd ставится пробел, если число меньше 10), yyyy - год (1999, 2000 итд.). __TIME__ Символьная строка, содержащая время начала обработки текущего исходного файла. Время имеет формат hh:mm:ss, где hh - час (00...23), mm - минуты (00...59), ss - секунды (00...59). __STDC__ Константа, равная 1, если Вы компилируете с флагом -a, который устанавливает совместимость с ANSI-стандартом (ANSI keywords only ... ON); иначе макроопределение не определено. В дополнение к стандарту, в Turbo C определены дополнительные макросы, также начинающиеся и оканчивающиеся символами '__' (double underline). __TURBOC__ Шестнадцатеричная константа, равная номеру текущей версии Turbo C (0x0201). __PASCAL__ Целая константа. Устанавливается в 1 если используется флаг -p, иначе не определено. __MSDOS__ Целая константа, равная 1. __CDECL__ Целая константа. Устанавливается в 1 если НЕ используется флаг -p, иначе не определено. Следующие 6 макросов зависят от выбранной для компиляции модели памяти. Во время компиляции всегда определен (установлен в 1) только один из них. __TINY__ Использована модель памяти TINY (крошечная) __SMALL__ Использована модель памяти SMALL (малая) __MEDIUM__ Использована модель памяти MEDIUM (средняя) __COMPACT__ Использована модель памяти COMPACT (компактная) __LARGE__ Использована модель памяти LARGE (большая) __HUGE__ Использована модель памяти HUGE (огромная) О том, что такое модели памяти, зачем они нужны и как выбираются, мы поговорим немного позднее, а пока напишем и запустим небольшую тестовую программу. Начиная с этого момента, я предполагаю, что в корневом каталоге диска C:, у Вас имеется директория TEST и все наши программные файлы находятся здесь: C:\TEST Создайте файл macro.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__); printf("\nANSI Standard: %s",__STDC__); printf("\nTurbo C ver.: %04x",__TURBOC__); printf("\nPascal Call Convention: %d",__PASCAL__); printf("\nC Call Convention: %d",__CDECL__); printf("\nMS DOS Platform: %d",__MSDOS__); printf("\nTiny Memory Model: %d",__TINY__); printf("\nSmall Memory Model: %d",__SMALL__); printf("\nMedium Memory Model: %d",__MEDIUM __); printf("\nCompact Memory Model: %d",__COMPACT__); printf("\nLarge Memory Model: %d",__LARGE__); printf("\nHuge Memory Model: %d",__HUGE__); printf("\nCurrent Line: %5d",__LINE__); printf("\nStarted Date: %s",__DATE__); printf("\nStarted Time: %s",__TIME__); } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ Возможно, Вам не все понятно в этой программе, но я не сторонник "сухого плавания" - бросайтесь в воду - и да не утонете ! А пока только несколько слов в пояснение: Все, что помещено между символами '/*' и '*/' - комментарий. Символ '@' я привык использовать как метку для GREP (помните утилиту grep.com в каталоге C:\TC ? Запустите ее с ключом ?, чтобы получить Help). Если, например, издать команду: grep "@" macro.c > comment.txt То мы получим файл comment.txt следующего содержания: > File MACRO.C: > @@ MACRO - A program for testing Built-In PseudoVariables values > @@ The End В данном случае, не очень много, но в большом проекте из многих файлов со многими функциями в каждом, полученный листинг может быть очень полезен. #include <stdio.h> Это директива препроцессора, означающая, что в данном месте необходимо включить (include) весь тест из файла, имя которого задано в угловых скобках. void main() Вызов главной (main) функции программы. В нашем случае она же и единственная. В языке С нет процедур, только функции и выполнение программы всегда начинается с функции main. Слово функция используется здесь в его прямом математическом смысле. Функция всегда возвращает одно и только одно значение и может быть использована в любом месте выражения, где по синтаксису допустимо число. Но именно в данном случае, я никак не собираюсь использовать возвращаемое функцией значение, поэтому прямо указал компилятору, что функция должна вернуть ничего (void). Обратите внимание, что я написал не просто main, а main() Мы уже говорили, что компилятор руководствуется очень простыми правилами при анализе текста: начинается с цифры - число, начинается с буквы - имя итд. Так вот, признаком функции служит пара круглых скобок (round brackets). Если функция имеет параметры, они помещаются внутрь скобок через запятую. В данном случае, параметров нет. printf() Как Вы уже, наверное, догадались по названию, это функция печати. Суффикс 'f' означает, что это функция форматированной печати, то есть выводящей значения по определенному формату. В отличие от многих других языков, С не имеет никаких встроенных средств ввода/вывода и поставка библиотечных функций для этих целей, обычно, лежит на разработчике компилятора. Такое решение связано с переносимостью языка С. Низкоуровневые функции, которые, обычно, пишутся на языке ассемблера данной машины не входят в сам язык С, хотя их минимальный состав и интерфейс стандартизованы. Функция printf() весьма необычна - она способна принимать переменное число параметров. Обычно, число параметров функции фиксировано, но при желании Вы можете написать и собственную функцию с переменным числом параметров. Так, в данном случае: printf("\nCurrent Line: %5d",__LINE__); функция printf() принимает два параметра (разделенных запятой) - форматную строку "\nCurrent Line: %5d", определяющую формат, по которому будет выведено значение, и переменную (в нашем случае, макро) __LINE__, значение которой будет выведено. Скомпилируем файл. Для этого издадим команду tc macro и файл macro.c будет загружен в окно текстового редактора IDE Turbo C 2.0 Теперь нажмем клавишу F9 (Make) и ... Oops ! Не компилируется ... Well ... Закомментируем те строки, которые компилятор считает ошибочными - этот простой и грубый метод очень часто используется при отладке в качестве первого приближения - разберемся сначала с тем, что компилируется и работает, затем двинемся дальше. Файл macro2.c /* ****************************************************************** ** @@ TEST - 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__); /* printf("\nANSI Standard: %s",__STDC__); */ printf("\nTurbo C ver.: %04x",__TURBOC__); /* printf("\nPascal Call Convention: %d",__PASCAL__); */ printf("\nC Call Convention: %d",__CDECL__); printf("\nMS DOS Platform: %d",__MSDOS__); /* printf("\nTiny Memory Model: %d",__TINY__); */ printf("\nSmall Memory Model: %d",__SMALL__); /* printf("\nMedium Memory Model: %d",__MEDIUM __); */ /* printf("\nCompact Memory Model: %d",__COMPACT__); */ /* printf("\nLarge Memory Model: %d",__LARGE__); */ /* printf("\nHuge Memory Model: %d",__HUGE__); */ printf("\nCurrent Line: %5d",__LINE__); printf("\nStarted Date: %s",__DATE__); printf("\nStarted Time: %s",__TIME__); } /* ****************************************************************** ** @@ The End ** ****************************************************************** */ Получаем результирующее сообщение компилятора: > +--------------- Linking ---------------+ > ¦ ¦ > ¦ EXE file : MACRO2.EXE ¦ > ¦ Linking : \TC\LIB\CS.LIB ¦ > ¦ ¦ > ¦ Total Link ¦ > ¦ Lines compiled: 254 PASS 2 ¦ > ¦ Warnings: 0 0 ¦ > ¦ Errors: 0 0 ¦ > ¦ ¦ > ¦ Available memory: 194K ¦ > ¦ Success : Press any key ¦ > +---------------------------------------+ Вас может удивить, откуда взялись 254 строки - в файле их всего 40 - вот тут самое время вспомнить про препроцессор и директиву #include. Просто препроцессор заменил директиву #include <stdio.h> на текст, содержащийся в файле заголовка stdio.h Теперь, в каталоге c:\test появилась наша первая программа macro2.exe, которую можно запустить на выполнение. Сделаем это прямо из IDE - нажмем (или выберем из меню) CTRL-F9 (Run) Гм. Экран "моргнул" и все. А как посмотреть результаты работы? Нажмите (или выберите из меню) ALT-F5 (User Screen) - окошко редактора исчезнет и Вам откроется экран с выведенным текстом. Нажатие на любую клавишу вернет Вас в IDE. И вот, что у нас получилось: > Current Line: 9 > Current File: MACRO2.C > Started Date: Aug 05 2000 > Started Time: 15:37:21 > Turbo C ver.: 0201 > C Call Convention: 1 > MS DOS Platform: 1 > Small Memory Model: 1 > Current Line: 31 > Started Date: Aug 05 2000 > Started Time: 15:37:21 Обратите внимание: результат использования псевдопеременной __LINE__, зависит от ее позиции в файле, но макросы __DATE__ и __TIME__ всегда выдают время начала компиляции, так что даже если компиляция началась до полуночи и закончилась после, расставленные в разных местах файла макросы __DATE__ и __TIME__ выдадут те же самые значения. Пожалуй, следует еще узнать, а чем занимался препроцессор. Издадим команду cpp macro2 В результате его работы будет создан файл macro2.i Сам этот файл слишком большой, чтобы приводить его полностью, и ниже показана только часть, корреспондирующая нашей программе: > macro2.c 6: > macro2.c 7: void main() > macro2.c 8: { > macro2.c 9: printf("\nCurrent Line: %5d",9); > macro2.c 10: printf("\nCurrent File: %s", "macro2.c"); > macro2.c 11: > macro2.c 12: printf("\nStarted Date: %s","Aug 05 2000"); > macro2.c 13: printf("\nStarted Time: %s","15:59:44"); > macro2.c 14: > macro2.c 15: > macro2.c 16: > macro2.c 17: printf("\nTurbo C ver.: %04x",0x0201 ); > macro2.c 18: > macro2.c 19: > macro2.c 20: printf("\nC Call Convention: %d",1 ); > macro2.c 21: > macro2.c 22: printf("\nMS DOS Platform: %d",1 ); > macro2.c 23: > macro2.c 24: > macro2.c 25: printf("\nSmall Memory Model: %d",1 ); > macro2.c 26: > macro2.c 27: > macro2.c 28: > macro2.c 29: > macro2.c 30: > macro2.c 31: printf("\nCurrent Line: %5d",31); > macro2.c 32: > macro2.c 33: printf("\nStarted Date: %s","Aug 05 2000"); > macro2.c 34: printf("\nStarted Time: %s","15:59:44"); > macro2.c 35: } > macro2.c 36: > macro2.c 37: > macro2.c 38: > macro2.c 39: > macro2.c 40: > macro2.c 41: Wow ! Все макросы исчезли - вместо них подставлены числа и строки. Теперь понятно, почему макросы __DATE__ и __TIME__ всегда выдают время начала компиляции - они заменяются на строковые значения препроцессором, еще до начала фактической компиляции, так что просто не могут иметь никакого другого значения. Попробуем теперь разобраться, почему не компилировался наш самый первый вариант программы. Издадим команду cpp macro В результате его работы будет создан файл macro.i Как и в прошлый раз, ниже показана только часть, корреспондирующая нашей программе: > macro.c 6: > macro.c 7: void main() > macro.c 8: { > macro.c 9: printf("\nCurrent Line: %5d",9); > macro.c 10: printf("\nCurrent File: %s", "macro.c"); > macro.c 11: > macro.c 12: printf("\nStarted Date: %s","Aug 05 2000"); > macro.c 13: printf("\nStarted Time: %s","16:12:47"); > macro.c 14: > macro.c 15: printf("\nANSI Standard: %s",__STDC__); > macro.c 16: > macro.c 17: printf("\nTurbo C ver.: %04x",0x0201 ); > macro.c 18: > macro.c 19: printf("\nPascal Call Convention: %d",__PASCAL__); > macro.c 20: printf("\nC Call Convention: %d",1 ); > macro.c 21: > macro.c 22: printf("\nMS DOS Platform: %d",1 ); > macro.c 23: > macro.c 24: printf("\nTiny Memory Model: %d",__TINY__); > macro.c 25: printf("\nSmall Memory Model: %d",1 ); > macro.c 26: printf("\nMedium Memory Model: %d",__MEDIUM __); > macro.c 27: printf("\nCompact Memory Model: %d",__COMPACT__); > macro.c 28: printf("\nLarge Memory Model: %d",__LARGE__); > macro.c 29: printf("\nHuge Memory Model: %d",__HUGE__); > macro.c 30: > macro.c 31: printf("\nCurrent Line: %5d",31); > macro.c 32: > macro.c 33: printf("\nStarted Date: %s","Aug 05 2000"); > macro.c 34: printf("\nStarted Time: %s","16:12:47"); > macro.c 35: } > macro.c 36: > macro.c 37: > macro.c 38: > macro.c 39: > macro.c 40: > macro.c 41: Agrr..rh ! Неизвестные препроцессору (на момент компиляции) макросы остались незамещенными, а компилятор, естественно, ничего про них не знает и сообщает об ошибке. Но почему из всех моделей памяти препроцессору известна именно __SMALL__ ? Если мы заглянем в меню настроек: Options | Compiler | Model File Edit Run Compile Project Options Debug Break/watch +------------------------------------ Edit+------------------+------------- ¦ Line 1 Col 1 Insert Indent Ta¦ Compiler ¦NONAME.C ¦ ¦ +---------------------------+ ¦ ¦ ¦ Model Small ¦ ¦ ¦ ¦ Defines +---------+ ¦ ¦ ¦ Code generation ¦ Tiny ¦ ¦ ¦ ¦ Optimization ¦ Small ¦ ¦ ¦ ¦ Source ¦ Medium ¦ ¦ +-¦ Errors ¦ Compact ¦ ¦ ¦ Names ¦ Large ¦ ¦ +------------------¦ Huge ¦ ¦ +---------+ то мы увидим, что выбрана (дефолтно) именно модель Small. Если Вы были внимательны при просмотре листинга препроцессора, то могли обратить внимание на прототип функции printf(), находящийся в файле stdio.h. Собственно говоря, именно ради этого прототипа мы и использовали опцию #include <stdio.h> C:\TC\INCLUDE\stdio.h 130: int cdecl printf(const char *format,...); Многоточие '...' (ellipsis) на месте второго параметра и означает, что в этом месте может стоять любое число параметров - от нуля до ... Заметьте, в файле stdio.h ничего не сказано о том как устроена функция printf() или где она находится, а только определено ее название (printf), параметры (const char *format,...), возвращаемое значение (int) и соглашение о вызове (cdecl). Иными словами, определен интерфейс с этой функцией, тогда как сама реализация оставлена до другого раза. Назначение H-файла (от Header) - дать компилятору информацию о том, с чем он имеет дело. Таким образом, уже на этапе компиляции можно проверить правильность вызова функции printf() - она должна иметь не менее одного параметра типа символьная строка и возвращать значение целого типа. Описатель cdecl указывает компилятору на необходимость размещения параметров начиная с последнего (именно это и обеспечивает простоту передачи переменного числа параметров). До сих мы пользовались вызовом компилятора С через IDE, но это можно сделать и напрямую, вызвав взамен tc (интегрированная оболочка) tcc (command line compiler). Вызовите его без параметров, чтобы посмотреть список ключей. В данный момент, нас интересует только один из них '-S' - Produce assembly output (case sensitive). Издадим команду: tcc -S macro2 В результате будет сгенерирован ассемблерный файл macro2.asm Вот небольшая часть ассемблерного листинга, соответствующая только сегменту кода: Строки, начинающиеся с ';' являются комментариями. > _TEXT segment byte public 'CODE' > ; ?debug L 7 > _main proc near > ; ?debug L 9 > mov ax,9 > push ax > mov ax,offset DGROUP:s@ > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 10 > mov ax,offset DGROUP:s@+37 > push ax > mov ax,offset DGROUP:s@+19 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 12 > mov ax,offset DGROUP:s@+64 > push ax > mov ax,offset DGROUP:s@+46 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 13 > mov ax,offset DGROUP:s@+94 > push ax > mov ax,offset DGROUP:s@+76 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 17 > mov ax,513 > push ax > mov ax,offset DGROUP:s@+103 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 20 > mov ax,1 > push ax > mov ax,offset DGROUP:s@+123 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 22 > mov ax,1 > push ax > mov ax,offset DGROUP:s@+151 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 25 > mov ax,1 > push ax > mov ax,offset DGROUP:s@+172 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 31 > mov ax,31 > push ax > mov ax,offset DGROUP:s@+198 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 33 > mov ax,offset DGROUP:s@+235 > push ax > mov ax,offset DGROUP:s@+217 > push ax > call near ptr _printf > pop cx > pop cx > ; ?debug L 34 > mov ax,offset DGROUP:s@+265 > push ax > mov ax,offset DGROUP:s@+247 > push ax > call near ptr _printf > pop cx > pop cx > @1: > ; ?debug L 35 > ret > _main endp > _TEXT ends Даже если Вы никогда раньше не слышали про Ассемблер x86, Вы все еще можете разглядеть в листинге ссылки на строки исходного текста (вида: ; ?debug L nn) и вызовы функции printf(). Обратите внимание, что компилятор добавил символ '_' к имени printf и что сам код функции printf() нигде не появляется в листинге - это библиотечная функция и ее вызовы будут разрешены при линковании с библиотекой стандартных функций. -----[05]----------[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/005.zip Рассылка дублируется (только текстовый вариант) с сервера www.egroups.com Подписка: c4d-subscribe@egroups.com -----[--]----------[The End]----------------------------------------------

© Gazlan 2009 * gazlan@yandex.ru

Hit Counter