Мне необходим исходный текст программы, которая осуществляет поиск
заданной строки.
О: Ищите библиотеку regesp (поставляется со многими UNIX - системами)
или достаньте пакет regexp Генри Спенсера (Henry Spencer)
(cs.toronto.edu директорий pub/regexp.shar.Z).
См. также вопрос 17.12.
12.16 Как разбить командную строку на разделенные пробельными литерами
аргументы (что-то вроде argc и argv в main)?
О: В большинстве компиляторов имеется функция strtok, хотя она
требует хитроумного обращения, а ее возможности могут Вас не
удовлетворить (например, работа в случае кавычек).
Смотри: ANSI Разд. 4.11.5.8; K&R II Разд.B3 c. 250; H&S
Разд. 15.7; PCS c. 178.
13. Lint.
13.1 Вот я написал программу, а она ведет себя странно. Что в ней не так?
О: Попробуйте сначала запустить lint (возможно, с ключами -a, -c, -h,
-p). Многие компиляторы С выполняют на самом деле только половину
задачи, не сообщая о тех подозрительных местах в тексте программы,
которые не препятствуют генерации кода.
13.2 Как мне подавить сообщение "warning: possible pointer alignment
problem" ("предупреждение: возможна проблема с выравниванием
указателя"), которое выдает lint после каждого вызова malloc?
О: Проблема состоит в том, что lint обычно не знает, и нет возможности
ему об этом сообщить, что malloc "возвращает указатель на область
памяти, которая должным образом выравнена для хранения объекта любого
типа". Возможна псевдореализация malloc с помощью #define внутри
#ifdef lint, которая удалит это сообщение, но слишком прямолинейное
применение #define может подавить и другие осмысленные сообщения о
действительно некорректных вызовах. Возможно, будет проще игнорировать
эти сообщения, может быть, делать это автоматически с помощью grep -v.
13.3 Где найти ANSI-совместимый lint?
О: Программа, которая называется FlexeLint (в виде исходного текста с
удаленными комментариями и переименованными переменными, пригодная
для компиляции на "почти любой" системе) может быть заказана по
адресу
Gimpel Software
3207 Hogarth Lane
Collegeville, PA 19426 USA
(+1) 610 584 4261
gimpel@netaxs.com
Lint для System V release 4 ANSI-совместим и может быть получен
(вместе с другими C утилитами) от UNIX Support Labs или от
дилеров System V.
Другой ANSI-совместимый LINT (способный также выполнять формальную
верификацию высокого уровня) называется LCLint и доступен через
ftp: larch.lcs.mit.edu://pub/Larch/lclint/ .
Ничего страшного, если программы lint нет. Многие современные
компиляторы почти столь же эфффективны в выявлении ошибок и
подозрительных мест, как и lint.
14. Стиль.
14.1 Может ли простой и приятный трюк
if(!strcmp(s1, s2))
служить образцом хорошего стиля?
О: Стиль не особенно хороший, хотя такая конструкция весьма популярна.
Тест удачен в случае равенства строк, хотя по виду условия можно
подумать, что это тест на неравенство.
Есть альтернативный прием, связанный с использованием макроса
#define Streq(s1, s2) (strcmp((s1), (s2)) == 0)
Вопросы стиля программирования, как и проблемы веры, могут
обсуждаться бесконечно. К хорошему стилю стоит стремиться, он легко
узнаваем, но не определим.
14.2 Каков наилучший стиль внешнего оформления программы?
О: K&R, приводя пример, которому чаще всего следуют, снабдили его
примечанием, предоставляющим Вам окончательный выбор:
Положение скобок менее важно, хотя люди склонны проявлять
фанатизм в таких вопросах. Мы выбрали один из нескольких
популярных стилей. Выберите тот стиль, который Вам больше
подходит, и точно ему следуйте.
Не так важно, чтобы стиль был "идеален". Важнее, чтобы он применялся
последовательно и был совместим (со стилем коллег или общедоступных
программ). Если требования к программированию (местные правила или
требования фирмы) не касаются стиля, и Вы не испытываете желания
изобрести свой собственный стиль, то просто следуйте K&R.
(Давайте не будем повторять бесконечные споры о преимуществах и
недостатках того или иного расположения отступов и скобок. См. также
Indian Hill Style Guide).
Так трудно определимое понятие "хороший стиль" включает в себя
гораздо больше, чем просто внешнее оформление программы; не тратьте
слишком много времени на отступы и скобки в ущерб более существенным
слагаемым качества.
Смотри: K&R Разд. 1.2 c. 10.
14.3 Где достать "Indian Hill Style Guide" и другие рекомендации по стилю
программирования?
О: Различные документы доступны через ftp:
Сервер: Файл или директорий:
cs.washington.edu ~ftp/pub/cstyle.tar.Z
(128.95.1.4) (переработанный Indian Hill guide)
cs.toronto.edu doc/programming
ftp.cs.umd.edu pub/style-guide
15. Операции с плавающей точкой.
15.1 У меня операции с плавающей точкой выполняются странно, и на разных
машинах получаются различные результаты.
О: Сначала убедитесь, что подключен головной файл <math.h> и правильно
объявлены другие функции, возвращающие тип double.
Если дело не в этом, вспомните, что большинство компьютеров используют
форматы с плавающей точкой, которые хотя и похоже, но вовсе не
идеально имитируют операции с действительными числами. Потеря
значимости, накопление ошибок и другие свойственные ЭВМ особенности
вычислений могут быть весьма болезненными.
Не нужно предполагать, что результаты операций с плавающей точкой
будут точными, в особенности не стоит проверять на равенство два числа
с плавающей точкой. (Следует избегать любых ненужных случайных
факторов.)
Все эти проблемы одинаково свойственны как С, так и другим языкам
программирования. Семантика операций с плавающей точкой определяется
обычно так, "как это выполняет процессор"; иначе компилятор вынужден
бы был заниматься непомерно дорогостоящей эмуляцией "правильной"
модели вычислений.
В этих вопросах и ответах нет возможности даже бегло перечислить все
затруднения при вычислениях с плавающей точкой и способы их
преодоления. Хорошая книга по программированию должна содержать
введение в эту область.
Смотри: EoPS Разд. 6 c. 115-8.
15.2 Я пытаюсь проделать кое-какие вычисления, связанные с тригонометрией,
включаю <math.h>, но все равно получаю сообщение: "undefined: _sin"
во время компиляции.
О: Убедитесь в том, что компоновщику известна библиотека, в которой
собраны математические функции. Например, в операционной системе
UNIX часто необходим ключ -lm в самом конце командной строки.
См. также вопрос 12.14.
15.3 Почему в языке С нет оператора возведения в степень?
О: Потому что немногие процессоры имеют такую инструкцию. Вместо этого
можно, включив головной файл <math.h>, использовать функцию pow(),
хотя часто при небольших целых порядках явное умножение
предпочтительней.
Смотри: ANSI Разд. 4.5.5.1 .
15.4 Как округлять числа?
А: Вот самый простой и честный способ:
(int)(x + 0.5)
Хотя для отрицательных чисел это не годится.
15.5 Как выявить специальное значение IEEE NaN и другие специальные
значения?
О: Многие компиляторы с высококачественной реализацией стандарта IEEE
операций с плавающей точкой обеспечивают возможность (например, макрос
isnan()) явной работы с такими значениями, а Numerical C Extensions
Group (NCEG) занимается стандартизацией таких средств. Примером
грубого, но обычно эффектиного способа проверки на NaN служит макрос
#define isnan(x) ((x) != (x))
хотя не знающие об IEEE компиляторы могут выбросить проверку в
процессе оптимизации.
15.6 У меня проблемы с компилятором Turbo C. Программа аварийно
завершается, выдавая нечто вроде "floating point formats not linked."
О: Некоторые компиляторы для мини-эвм, включая Turbo C (а также
компилятор Денниса Ритчи для PDP-11), не включают поддержку операций
с плавающей точкой, когда им кажется, что это не понадобится.
В особенности это касается версий printf и scanf, когда для экономии
места не включается поддержка %e, %f, и %g. Бывает так, что
эвристической процедуры Turbo C, которая определяет - использует
программа операции с плавающей точкой или нет, оказывается
недостаточно, и программист должен лишний раз вызвать функцию,
использующую операции с плавающей точкой, чтобы заставить компилятор
включить поддержку таких операций.
16. Интерфейс с операционной системой.
16.1 Как прочитать с клавиатуры один символ, не дожидаясь новой строки?
О: Вопреки популярному убеждению и желанию многих, этот вопрос (как и
родственные вопросы, связанные с дублированием символов) не относится
к языку С. Передача символов с "клавиатуры" программе, написанной на
С, осуществляется операционной системой, эта операция не
стандартизирована языком С. Некоторые версии библиотеки curses
содержат функцию cbreak(), которая делает как раз то, что нужно.
Если Вы пытаетесь прочитать пароль с клавиатуры без вывода его на
экран, попробуйте getpass(). В операционной системе UNIX используйте
ioctl для смены режима работы драйвера терминала(CBREAK или RAW для
"классических" версий; ICANON, c_cc[VMIN] и с_сс[VTIME] для System V
или Posix). В системе MS-DOS используйте getch(). В системе VMS
попробуйте функции управления экраном (SMG$) или curses, или
используйте низкоуровневые команды $QIO с кодами IO$_READVBLK (и,
может быть, IO$M_NOECHO) для приема одного символа за раз. В других
операционных системах выкручивайтесь сами. Помните, что в некоторых
операционных системах сделать нечто подобное невозможно, так как
работа с символами осуществляется вспомогательными процессорами и
не находится под контролем центрального процессора.
Вопросы, ответы на которые зависят от операционной системы,
неуместны в comp.lang.c. Ответы на многие вопросы можно найти в FAQ
таких групп как comp.unix.questions и comp.os.msdos.programmer.
Имейте в виду, что ответы могут отличаться даже в случае разных
вариантов одной и той же операционной системы. Если вопрос касается
специфики операционной системы, помните, что ответ, пригодный в Вашей
системе, может быть бесполезен всем остальным.
Смотри: PCS Разд. 10 c. 128-9, Разд. 10.1 c. 130-1.
16.2 Как определить - есть ли символы для чтения (и если есть, то сколько?)
И наоборот, как сделать, чтобы выполнение программы не блокировалось,
когда нет символов для чтения?
О: Ответ на эти вопросы также целиком зависит от операционной системы.
В некоторых версиях curses есть функция nodelay(). В зависимости
от операционной системы Вы сможете использовать "неблокирующий ввод/
вывод" или системный вызов "select" или ioctl FIONREAD, или kbhit(),
или rdchk(), или опцию O_NDELAY функций open() или fcntl().
16.3 Как очистить экран? Как выводить на экран негативное изображение?
О: Это зависит от типа терминала (или дисплея). Можете использовать
такую библиотеку как termcap или curses, или какие-то другие
функциии, пригодные для данной операционной системы.
16.4 Как узнать состояние мыши?
О: Посмотрите в системной документации, или поместите вопрос
(предварительно посмотрев их FAQ) в одну из групп, обсуждающих
определенную операционную систему. Работа с мышью совершенно непохожа
в X window, MS-DOS, Macintosh, и, возможно, в любой другой
операционной системе.
16.5 Как программа может определить полный путь к месту, из которого она
была вызвана?
О: argv[0] может содержать весь путь, часть его или ничего не содержать.
Если имя файла в argv[0] имеется, но информация не полна, возможно
повторение логики поиска исполнимого файла, используемой
интерпретатором командного языка. Гарантированных или мобильных
решений, однако, не существует.
16.6 Как процесс может изменить переменную окружения родительского
процесса?
О: В общем, никак. Различные операционные системы обеспечивают сходную с
UNIX возможность задания пары имя/значение. Может ли программа с
пользой для себя поменять окружение, и если да, то как - все это
зависит от операционной системы.
В системе UNIX процесс может модифицировать свое окружение
(в некоторых системах есть для этого функции setenv() и/или putenv())
и модифицированное окружение обычно передается дочерним процессам
но _не_ распространяется на родительский процесс.
16.7 Как проверить, существует ли файл? Мне необходимо спрашивать
пользователя перед тем как переписывать существующие файлы.
О: В UNIX-подобных операционных системах можно попробовать функцию
access(), хотя имеются кое-какие проблемы. (Применение access() может
сказаться на последующих действиях, кроме того, возможны особенности
исполнения в setuid-программах). Другое (возможно, лучшее) решение
- вызвать stat(), указав имя файла. Единственный универсальный,
гарантирующий мобильность способ состоит в попытке открыть файл.
16.8 Как определить размер файла до его чтения?
О: Если "размер файла" - это количество литер, которое можно прочитать,
то, вообще говоря, это количество заранее неизвестно. В операционной
системе Unix вызов функции stat дает точный ответ, и многие
операционные системы поддерживают похожую функцию, которая дает
приблизительный ответ. Можно c помощью fseek переместиться в конец
файла, а затем вызвать ftell, но такой прием немобилен (дает точный
ответ только в системе Unix, в других же случаях ответ почти точен
лишь для определенных стандартом ANSI "двоичных" файлов).
В некоторых системах имеются подпрограммы filesize или filelength.
И вообще, так ли нужно заранее знать размер файла? Ведь самый точный
способ определения его размера в С программе заключается в открытии и
чтении. Может быть, можно изменить программу так, что размер файла
будет получен в процессе чтения?
16.9 Как укоротить файл без уничтожения или переписывания?
О: В системах BSD есть функция ftruncate(), несколько других систем
поддерживают chsize(), в некоторых имеется (возможно,
недокументированный) параметр fcntl F_FREESP. В системе MS-DOS можно
иногда использовать write(fd, "", 0). Однако, полностью мобильного
решения не существует.
16.10 Как реализовать задержку или определить время реакции пользователя,
чтобы погрешность была меньше секунды?
О: У этой задачи нет, к несчастью, мобильных решений. Unix V7 и ее
производные имели весьма полезную функцию ftime() c точностью до
миллисекунды, но она исчезла в System V и Posix. Поищите такие
функции: nap(), setitimer(), msleep(), usleep(), clock(), и
gettimeofday(). Вызовы select() и poll() (если эти функции доступны)
могут быть добавлены к сервисным функциям для создания простых
задержек. В системе MS-DOS возможно перепрограммирование системного
таймера и прерываний таймера.
16.11 Как прочитать объектный файл и передать управление на одну из его
функций?
О: Необходим динамический компоновщик и/или загрузчик. Возможно выделить
память с помощью malloc и читать объектные файлы, но нужны обширные
познания в форматах объектных файлов, модификации адресов и пр.
В системе BSD Unix можно использовать system() и ld -A для
динамической компоновки. Mногие (большинство? ) версии SunOS и
System V имеют библиотеку -ldl, позволяющую динамически загружать
объектные модули. Есть еще GNU пакет, который называется "dld".
См. также вопрос 7.6.
16.12 Как выполнить из программы команду операционной системы?
О: Используйте system().
Смотри: K&R II Разд. B6 c. 253; ANSI Разд. 4.10.4.5; H&S
Разд. 21.2; PCS Разд. 11 c. 179;
16.13 Как перехватить то, что выдает команда операционной системы?
О: Unix и некоторые другие операционные системы имеют функцию popen(),
которая переназначает поток stdio каналу, связанному с процессом,
запустившим команду, что позволяет прочитать выходные данные (или
передать входные). А можно просто (см. вопрос 16.12) перенаправить
выход команды в файл, затем открыть его и прочесть.
Смотри: PCS Разд. 11 c. 169 .
16.14 Как получить содержимое директория в С программе?
О: Выясните, нельзя ли использовать функции opendir() и readdir(),
доступные в большинстве систем Unix. Реалиции этих функций известны
для MS-DOS, VMS и других систем. (MS-DOS имеет также функции
findfirst и findnext, которые делают в точности то же самое).
16.15 Как работать с последовательными (COM) портами?
О: Это зависит от операционной системы. В системе Unix обычно
осуществляются операции открытия, чтения и записи во внешнее
устройство и используются возможности терминального драйвера для
настройки характеристик. В системе MS-DOS можно либо использовать
прерывания BIOSa, либо (если требуется приличная скорость) один из
управляемых прерываниями пакетов для работы с последовательными
портами.
17. Разное.
17.1 Что можно с уверенностью сказать о начальных значениях переменных,
которые явным образом не инициализированы? Если глобальные переменные
имеют нулевое начальное значение, то правильно ли нулевое значение
присваивается указателям и переменным с плавающей точкой?
А: "Cтатические" переменные (то есть объявленные вне функций и те, что
объявлены как принадлежащие классу stаtic) всегда инициализируются
(прямо при старте программы) нулем, как будто программист написал
"=0". Значит, переменные будут инициализированы как нулевые указатели
(соответствующего типа; см. раздел 1), если они объявлены указателями,
или значениями 0.0, если были объявлены переменные с плавающей точкой.
Переменные автоматического класса (т.е. локальные переменные без
спецификации static), если они явно не определены, первоначально
содержат "мусор". Никаких полезных предсказаний относительно мусора
сделать нельзя.
Память, динамически выделяемая с помощью malloc и realloc также
будет содержать мусор и должна быть инициализирована, если это
необходимо, вызывающей программой. Память, выделенная с помощью
calloc, зануляет все биты, что не всегда годится для указателей или
переменных с плавающей точкой (см. вопрос 3.13 и раздел 1).
17.2 Этот текст взят прямо из книги, но он не компилируется.
f()
{
char a[] = "Hello, world!";
}
О: Возможно, Ваш компилятор создан до принятия стандарта ANSI и
еще не поддерживает инициализацию "автоматических агрегатов"
(то есть нестатических локальных массивов и структур).
Чтобы выкрутиться из этой ситуации, сделайте массив статическим или
глобальным, или инициализируйте его с помощью strcpy, когда
вызывается f(). (Всегда можно инициализировать автоматическую
переменную char * стрингом литер, но см. вопрос 17.20). См. также
вопросы 5.16, 5.17.
17.3 Как писать данные в файл, чтобы их можно было читать на машинах
с другим размером слова, порядком байтов или другим форматом чисел
с плавающей точкой?
О: Лучшее решение - использовать текстовые файлы (обычно ASCII),
c данными, записанными fprintf. Читать данные лучше всего с
помощью fscanf или чего-то подобного. (Такой же совет применим
для сетевых протоколов). К мнениям, что текстовые файлы слишком
велики и могут долго обрабатываться, относитесь скептически.
Помимо того, что эффективность таких операций может быть на практике
приемлемой, способность манипулировать данными с помощью стандартных
средств может иметь решающее значение.
Если необходимо использовать двоичный формат, переносимость данных
можно улучшить (или получить выгоду от использования готовых
библиотек ввода/вывода), если использовать стандартные форматы данных,
такие как XDR (RFC 1014) (Sun), ASN.1(OSI), X.409 (CCITT), или ISO
8825 "Основные правила кодирования" См. также вопрос 9.11.
17.4 Как вставить или удалить строку (или запись) в середине файла?
О: Придется, видимо, переписать файл. См. вопрос 16.9.
17.5 Как возвратить из функции несколько значений?
О: Или передайте указатель на то место, которое будет заполнено функцией,
или пусть функция возвращает структуру, содержащую желаемые значения,
или подумайте о глобальных переменных (если их немного). См. также
вопросы 2.17, 3.4, и 9.2.
17.6 Если есть указатель (char *) на имя функции в виде стринга, то как
эту функцию вызвать?
О: Наиболее прямолинейный путь - создание таблицы имен и соответствующих
им указателей:
int function1(), function2();
struct {char *name; int (*funcptr)(); } symtab[] =
{
"function1", function1,
"function2", function2,
};
Ну а теперь нужно поискать в таблице нужное имя и вызвать функцию,
используя связанный с именем указатель. См. также вопросы 9.9 и 16.11.
17.7 У меня, кажется, нет головного файла <sgtty.h>. Пришлите мне его,
пожалуйста.
О: Стандартные головные файлы существуют в том смысле, что содержат
информацию, необходимую компилятору, операционной системе и
процессору. "Чужой" головной файл подойдет лишь тогда, когда взят из
идентичного окружения. Поинтересуйтесь у продавца компилятора, почему
отсутствует головной файл, или попросите прислать новый взамен
потерянного.
17.8 Как вызвать процедуры, написанные на языке FORTRAN (C++,BASIC,Pascal,
Ada, Lisp) из С (и наоборот).
О: Ответ полностью зависит от машины и от специфики передачи параметров
различными компиляторами. Решения вообще может не быть. Внимательно
читайте руководство по компилятору. Иногда в документации имеется
"Руководство по смешанному программированию", хотя техника передачи
аргументов и обеспечения правильного входа в функцию зачастую весьма
таинственна. Дополнительная информация находится в файле FORT.gz
Глена Гирса, (Glenn Geers) который можно получить с помощью ftp
suphys.physics.su.oz.au в директории src.
Головной файл cfortran.h упрощает взаимодействие C/FORTRAN на многих
популярных машинах. cfortran.h можно получит через ftp
zebra.desy.de (131.169.2.244).
В C++ модификатор "C" внешней функции показывает, что функция будет
вызываться с использованием соглашения о передаче параметров языка
С.
17.9 Кто-нибудь знает о программах, переводящих Pascal или FORTRAN
(или LISP, Ada, awk, "старый" С) в С?
О: Есть несколько общедоступных программ:
p2c Переводчик с Паскаля на С, написанный Дейвом Гиллеспи,
(Dave Gillespie) помещен в comp.sources.unix в Марте 1990
(Volume 21); доступен также через ftp csvax.cs.caltech.edu,
файл pub/p2c-1.20.tar.Z .
ptoc Другой переводчик с Паскаля на С, написан на Паскале
(comp.sources.unix, Volume 10, поправки в vol. 13?)
f2c Переводчик с фортрана на С совместно разработанный
Bell Labs, Bellcore, and Carnegie Mellon. Подробности
можно получить, послав электронной почтой сообщение
"send index from f2c" по адресу netlib@research.att.com
или research!netlib.
(Эти подробности можно получить и через ftp
netlib.att.com, в директории netlib/f2c.)
Составитель этого списка вопросов и ответов имеет список других
коммерческих трансляторов, среди них трансляторы для менее известных
языков. См. также вопрос 5.3.
17.10 Правда ли, что C++ - надмножество С. Можно ли использовать
компилятор C++ для трансляции C программ?
О: С++ вырос из С и в большой степени базируется на нем, но некоторые
правильные конструкции С недопустимы в C++. (Многие С программы,
будут, тем не менее, правильно транслироваться компилятором С++).
17.11 Мне нужен: О: Ищи программы, которые
называются (см. также вопрос 17.12)
генератор перекрестных cflow, calls, cscope
ссылок С
C форматизатор cb, indent
17.12 Где найти все эти общедоступные программы?
О: Если у Вас есть доступ к Usenet, смотрите периодически помещаемые
сообщения в comp.sources.unix и comp.sources.misc, которые описывают
некоторые детали ведения архивов и подсказывают, как получить те
или иные файлы. Обычно используется ftp и/или uucp c центральным,
ориентированным на пользователей сервером, таким как uunet
(ftp.uu.net, 192.48.96.9). Однако, в этих вопросах и ответах
невозможно исследовать или перечислить все архивные серверы и
рассказать о доступе к ним.
Ай Ша (Ajay Shah) поддерживает список общедоступных программ в
области численного анализа, который периодически публикуется,
и его можно найти там же, где и данные вопросы и ответы (см.
вопрос 17.33). Группа Usenet comp.archives содержит многочисленные
объявления о том, что доступно на различных ftp. Почтовый сервер
"archie" может подсказать, на каком ftp имеются те или иные программы.
Пошлите почтовое сообщение "help" по адресу
archie@quiche.cs.mcgill.ca для получения дальнейших инструкций.
Наконец, группа comp.sources.wanted - обычно самое подходящее место,
где можно поместить соответствующий запрос, но посмотрите прежде _их_
список вопросов и ответов (FAQ) "Как найти источники".
17.13 Где состоятся следующие Соревнования по Непонятному С
Программированию (International Obfuscated C Code Contest - IOCCC)?
Как получить программы, победившие в текущем и прошлых конкурсах?
О: Соревнования обычно проходят с начала марта до середины мая. Для
получения правил и рекомендаций, касающихся участия в конкурсе,
пошлите электронной почтой письмо по адресу:
{apple,pyramid,sun,uunet}!hoptoad!judges или
judges@toad.com
,в разделе Subject которого напишите "send rules".
Имейте в виду, что это _не_ адрес, куда нужно посылать конкурсные
программы.
Победители конкурса сначала объявляются на летней конференции
Usenix (Summer Usenix Conference) в середине июня и становятся
доступными в сети где-то в июле-августе. Программы-победители
прошлых конкурсов (начиная с 1984 г.) заархивированы в uunet (см.
вопрос 17.12) в директории ~/pub/ioccc.
В крайнем случае программы-победители прошлых конкурсов можно
получить, послав по указанному выше адресу письмо в графе Subject
которого нужно указать: "send YEAR winners", где YEAR - год,
представленный четырьмя цифрами, интервал в несколько лет или "all".
17.14 Почему недопустимы вложенные комментарии? Как прикажете
"выключить" фрагмент программы, в котором уже есть комментарии?
Можно ли использовать комментарии внутри стринговых констант?
О: Вложенные комментарии принесут больше вреда, чем пользы, главным
образом из-за возможности случайно не закрыть комментарий, оставив
внутри него символы "/*". По этой причине лучше "выключить" большой
фрагмент программы, в котором уже есть комментарии, с помощью средств
препроцессора #ifdef или #if 0 (но имейте в виду вопрос 5.11).
Последовательность символов /* и */ не имеет специального значения
внутри заключенных в двойные кавычки стрингов. Эта последовательность
не рассматривается как комментарий, поскольку программа (особенно
та, которая создает текст другой С програмы) должна иметь
возможность эти комментарии печатать.
Смотри: ANSI Appendix E p. 198, Rationale Разд. 3.1.9 p. 33.
17.15 Как получить значение кода ASCII той или иной литеры, и наоборот?
О: В С литеры представлены целыми числами, соответствующими их значениям.
(в соответствии с набором символов данной машины). Так что нет
необходимости в преобразовании: если известна литера, то известно
и ее значение.
17.16 Как реализовать последовательности и/или массивы бит?
О: Используйте массивы переменных типа char или int и несколько
макросов для операций с отдельными битами (используйте определение
8 для CHAR_BIT, если нет головного файла <limits.h>:
#include <limits.h> /* для CHAR_BIT */
#define BITMASK(bit) (1 << ((bit) % CHAR_BIT))
#define BITSLOT(bit) ((bit) / CHAR_BIT)
#define BITSET(ary, bit) ((ary)[BITSLOT(bit)] /= BITMASK(bit))
#define BITTEST(ary, bit) ((ary)[BITSLOT(bit)] & BITMASK(bit))
17.17 Как наилучшим образом определить число установленных бит,
соответствующих определенному значению?
О: Решение этой и многих других проблем из области битоверчения можно
ускорить и сделать более эффективным с помощью таблиц перекодировки.
(но имейте в виду следующий вопрос).
17.18 Как повысить эффективность работы программы?
О: Тема эффективности, очень часто затрагиваемая в comp.lang.c,
не так важна как многие склонны думать. Большая часть кода в
большинстве программ не влияет на время исполнения. Если время,
занимаемое каким-то участком кода, мало по сравнению с общим
временем исполнения, то для этого участка гораздо важнее простота
и мобильность, чем эффективность. (Помните, что компьютеры очень,
очень быстры и даже "неэффективный" участок кода может выполняться
без видимой задержки).
Печально известны попытки предсказать "горячие точки" программы.
Когда эффективность программы имеет значение, важно использовать
профилировщики для определения тех участков программы, которые
заслуживают внимания. Часто основное время выполнения поглощается
периферийными операциями, такими как ввод/вывод и выделение памяти,
которые можно ускорить с помощью буферизации и хеширования.
Для небольших участков программы, критичных в смысле эффективности,
жизненно важно выбрать подходящий алгоритм; "микрооптимизация"
этого участка менее важна. Многие часто предлагаемые "приемы по
увеличению эффективности" (вроде замены операции сдвига умножением
на степень двойки) выполняются автоматически даже неизощренными
компиляторами.
Неуклюжие попытки оптимизации способны так увеличить размер
программы, что ее эффективность упадет.
Дальнейшее обсуждение противоречий, связанных с эффективностью, а так
же хорошие советы по увеличению эффективности, когда это важно,
смотрите в главе 7 книги Кернигана и Плоджера "Элементы стиля
программирования", а также в книге Джона Бентли "Написание
эффективных программ".
17.19 Правда ли, что применение указателей более эффективно, чем
применение массивов? Насколько замедляет программу вызов функции?
Быстрее ли ++i чем i = i + 1?
О: Точные ответы на эти и многие другие похожие вопросы, конечно же,
зависят от процессора и применяемого компилятора. Если знать это
необходимо, придется аккуратно определить время выполнения тестовыхпрограмм. (Часто различия столь незначительны, что потребуются сотни-тысяч повторений, чтобы их увидеть. Если есть возможность,
посмотрите ассемблерный листинг, выдаваемый компилятором, чтобы
убедиться в различной трансляции двух претендующих на первенство
альтернатив).
"Обычно" быстрее продвигаться по большим массивам с помощью
указателей, чем с помощью индексов, однако есть процессоры, для
которых справедливо обратное.
Хотя вызовы функций и увеличивают время выполнения, сами функции
настолько повышают модульность и простоту понимания программы,
что едва ли полезно от них отказываться.
Прежде чем переписывать выражения типа i=i+1, вспомните, что имеете
дело с компилятором С а не с программируемым калькулятором. Любой
приличный компилятор будет одинаково транслировать ++i,i+=1; i=i+1.
Использовать ++i, i+=1 или i=i+1 - вопрос стиля, не эффективности.
(См. также вопрос 4.7).
Читать 6 часть
Ссылки по теме