Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Сабуров С.В. - Язык программирования C и C++ - 2006

.pdf
Скачиваний:
312
Добавлен:
13.08.2013
Размер:
1.42 Mб
Скачать

Тонкости и хитрости в вопросах и ответах

символами осуществляется вспомогательными процессорами и не находится под контролем центрального процессора.

Вопросы, ответы на которые зависят от операционной системы, неуместны в comp.lang.c. Ответы на многие вопросы можно найти в FAQ таких групп как comp.unix.questions и comp.os.msdos.programmer.

Имейте в виду, что ответы могут отличаться даже в случае разных вариантов одной и той же операционной системы. Если вопрос касается специфики операционной системы, помните, что ответ, пригодный в вашей системе, может быть бесполезен всем остальным.

Как определить — есть ли символы для чтения (и если есть, то сколько?) И наоборот, как сделать, чтобы выполнение программы не блокировалось, когда нет символов для чтения?

Ответ на эти вопросы также целиком зависит от операционной системы.

В некоторых версиях curses есть функция nodelay(). В зависимости от операционной системы вы сможете использовать «неблокирующий ввод/вывод» или системный вызов «select» или ioctl FIONREAD, или kbhit(), или rdchk(), или опцию O_NDELAY функций open() или fcntl().

Как очистить экран? Как выводить на экран негативное изображение?

Это зависит от типа терминала (или дисплея). Можете использовать такую библиотеку как termcap или curses, или какие то другие функции, пригодные для данной операционной системы.

Как программа может определить полный путь к месту, из которого она была вызвана?

argv[0] может содержать весь путь, часть его или ничего не содержать.

Если имя файла в argv[0] имеется, но информация не полна, возможно повторение логики поиска исполнимого файла, используемой интерпретатором командного языка. Гарантированных или мобильных решений, однако, не существует.

597

Тонкости и хитрости в вопросах и ответах

Как процесс может изменить переменную окружения родительского процесса?

Вобщем, никак. Различные операционные системы обеспечивают сходную с UNIX возможность задания пары имя/значение. Может ли программа с пользой для себя поменять окружение, и если да, то как — все это зависит от операционной системы.

Всистеме UNIX процесс может модифицировать свое окружение (в некоторых системах есть для этого функции setenv() и/или putenv()) и модифицированное окружение обычно передается дочерним процессам но не распространяется на родительский процесс.

Как проверить, существует ли файл? Мне необходимо спрашивать пользователя перед тем как переписывать существующие файлы

В UNIX подобных операционных системах можно попробовать функцию access(), хотя имеются кое какие проблемы. (Применение access() может сказаться на последующих действиях, кроме того, возможны особенности исполнения в setuid программах). Другое (возможно, лучшее) решение — вызвать stat(), указав имя файла. Единственный универсальный, гарантирующий мобильность способ состоит в попытке открыть файл.

Как определить размер файла до его чтения?

Если «размер файла» — это количество литер, которое можно прочитать, то, вообще говоря, это количество заранее неизвестно. В операционной системе Unix вызов функции stat дает точный ответ, и многие операционные системы поддерживают похожую функцию, которая дает приблизительный ответ. Можно c помощью fseek переместиться в конец файла, а затем вызвать ftell, но такой прием немобилен (дает точный ответ только в системе Unix, в других же случаях ответ почти точен лишь для определенных стандартом ANSI «двоичных» файлов).

В некоторых системах имеются подпрограммы filesize или filelength.

И вообще, так ли нужно заранее знать размер файла? Ведь самый точный способ определения его размера в Си программе заключается в открытии и чтении. Может быть, можно изменить программу так, что размер файла будет получен в процессе чтения?

598

Тонкости и хитрости в вопросах и ответах

Как укоротить файл без уничтожения или переписывания?

В системах BSD есть функция ftruncate(), несколько других систем поддерживают chsize(), в некоторых имеется (возможно, недокументированный) параметр fcntl F_FREESP. В системе MS DOS можно иногда использовать write(fd, "", 0). Однако, полностью мобильного решения не существует.

Как реализовать задержку или определить время реакции пользователя, чтобы погрешность была меньше секунды?

У этой задачи нет, к несчастью, мобильных решений. Unix V7 и ее производные имели весьма полезную функцию ftime() c точностью до миллисекунды, но она исчезла в System V и Posix. Поищите такие функции: nap(), setitimer(), msleep(), usleep(), clock() и gettimeofday(). Вызовы select() и poll() (если эти функции доступны) могут быть добавлены к сервисным функциям для создания простых задержек. В системе MS DOS возможно перепрограммирование системного таймера и прерываний таймера.

Как прочитать объектный файл и передать управление на одну из его функций?

Необходим динамический компоновщик и/или загрузчик. Возможно выделить память с помощью malloc и читать объектные файлы, но нужны обширные познания в форматах объектных файлов, модификации адресов и пр.

В системе BSD Unix можно использовать system() и ld A для динамической компоновки. Многие (большинство) версии SunOS и System V имеют библиотеку ldl, позволяющую динамически загружать объектные модули. Есть еще GNU пакет, который называется «dld».

Как выполнить из программы команду операционной системы?

Используйте system().

Как перехватить то, что выдает команда операционной системы?

Unix и некоторые другие операционные системы имеют функцию popen(), которая переназначает поток stdio каналу, связанному с процессом, запустившим команду, что позволяет прочитать выходные данные (или передать входные). А можно просто перенаправить выход команды в файл, затем открыть его и прочесть.

599

Тонкости и хитрости в вопросах и ответах

Как получить содержимое директории в Си программе?

Выясните, нельзя ли использовать функции opendir() и readdir(), доступные в большинстве систем Unix. Реализации этих функций известны для MS DOS, VMS и других систем. (MS DOS имеет также функции findfirst и findnext, которые делают в точности то же самое).

Как работать с последовательными (COM) портами?

Это зависит от операционной системы. В системе Unix обычно осуществляются операции открытия, чтения и записи во внешнее устройство и используются возможности терминального драйвера для настройки характеристик. В системе MS DOS можно либо использовать прерывания BIOSa, либо (если требуется приличная скорость) один из управляемых прерываниями пакетов для работы с последовательными портами.

Что можно с уверенностью сказать о начальных значениях переменных, которые явным образом не инициализированы? Если глобальные переменные имеют нулевое начальное значение, то правильно ли нулевое значение присваивается указателям и переменным с плавающей точкой?

«Статические» переменные (то есть объявленные вне функций и те, что объявлены как принадлежащие классу stаtic) всегда инициализируются (прямо при старте программы) нулем, как будто программист написал «=0». Значит, переменные будут инициализированы как нулевые указатели (соответствующего типа), если они объявлены указателями, или значениями 0.0, если были объявлены переменные с плавающей точкой.

Переменные автоматического класса (т.е. локальные переменные без спецификации static), если они явно не определены, первоначально содержат «мусор». Никаких полезных предсказаний относительно мусора сделать нельзя.

Память, динамически выделяемая с помощью malloc и realloc также будет содержать мусор и должна быть инициализирована, если это необходимо, вызывающей программой. Память, выделенная с помощью calloc, зануляет все биты, что не всегда годится для указателей или переменных с плавающей точкой.

600

Тонкости и хитрости в вопросах и ответах

Этот текст взят прямо из книги, но он не компилируется: f()

{

char a[] = "Hello, world!";

}

Возможно, ваш компилятор создан до принятия стандарта ANSI и еще не поддерживает инициализацию «автоматических агрегатов» (то есть нестатических локальных массивов и структур).

Чтобы выкрутиться из этой ситуации, сделайте массив статическим или глобальным, или инициализируйте его с помощью strcpy, когда вызывается f(). (Всегда можно инициализировать автоматическую переменную char * стрингом литер.)

Как писать данные в файл, чтобы их можно было читать на машинах с другим размером слова, порядком байтов или другим форматом чисел с плавающей точкой?

Лучшее решение — использовать текстовые файлы (обычно ASCII), c данными, записанными fprintf. Читать данные лучше всего с помощью fscanf или чего то подобного. (Такой же совет применим для сетевых протоколов). К мнениям, что текстовые файлы слишком велики и могут долго обрабатываться, относитесь скептически.

Помимо того, что эффективность таких операций может быть на практике приемлемой, способность манипулировать данными с помощью стандартных средств может иметь решающее значение.

Если необходимо использовать двоичный формат, переносимость данных можно улучшить (или получить выгоду от использования готовых библиотек ввода/вывода), если использовать стандартные форматы данных, такие как XDR (RFC 1014) (Sun), ASN.1(OSI), X.409 (CCITT), или ISO 8825 «Основные правила кодирования».

Как вставить или удалить строку (или запись) в середине файла?

Придется, видимо, переписать файл.

Как возвратить из функции несколько значений?

Или передайте указатель на то место, которое будет заполнено функцией, или пусть функция возвращает структуру,

601

Тонкости и хитрости в вопросах и ответах

содержащую желаемые значения, или подумайте о глобальных переменных (если их немного).

Если есть указатель (char *) на имя функции в виде стринга, то как эту функцию вызвать?

Наиболее прямолинейный путь — создание таблицы имен и соответствующих им указателей:

int function1(), function2();

struct {char *name; int (*funcptr)(); } symtab[] =

{

"function1", function1, "function2", function2, };

Ну а теперь нужно поискать в таблице нужное имя и вызвать функцию, используя связанный с именем указатель.

У меня, кажется, нет головного файла <sgtty.h>. Где мне его взять?

Стандартные головные файлы существуют в том смысле, что содержат информацию, необходимую компилятору, операционной системе и процессору. «Чужой» головной файл подойдет лишь тогда, когда взят из идентичного окружения. Поинтересуйтесь у продавца компилятора, почему отсутствует головной файл, или попросите прислать новый взамен потерянного.

Как вызвать процедуры, написанные на языке 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).

602

Тонкости и хитрости в вопросах и ответах

В C++ модификатор «C» внешней функции показывает, что функция будет вызываться с использованием соглашения о передаче параметров языка Си.

Кто!нибудь знает о программах, переводящих 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.)

Составитель этого списка вопросов и ответов имеет список других коммерческих трансляторов, среди них трансляторы для менее известных языков.

Правда ли, что C++ — надмножество Си. Можно ли использовать компилятор C++ для трансляции C программ?

Си++ вырос из Си и в большой степени базируется на нем, но некоторые правильные конструкции Си недопустимы в C++. (Многие Си программы, будут, тем не менее, правильно транслироваться компилятором Си++).

Мне нужен генератор перекрестных ссылок Си и C форматизатор

Ищи программы, которые называются cflow, calls, cscope, cb, indent.

Где найти все эти общедоступные программы?

Если у вас есть доступ к Usenet, смотрите периодически помещаемые сообщения в comp.sources.unix и comp.sources.misc, которые описывают некоторые детали ведения архивов и подсказывают, как получить те или иные файлы. Обычно используется ftp и/или uucp c центральным, ориентированным на

603

Тонкости и хитрости в вопросах и ответах

пользователей сервером, таким как uunet (ftp.uu.net, 192.48.96.9). Однако, в этих вопросах и ответах невозможно исследовать или перечислить все архивные серверы и рассказать о доступе к ним.

Ай Ша (Ajay Shah) поддерживает список общедоступных программ в области численного анализа, который периодически публикуется, и его можно найти там же, где и данные вопросы и ответы. Группа Usenet comp.archives содержит многочисленные объявления о том, что доступно на различных ftp. Почтовый сервер «archie» может подсказать, на каком ftp имеются те или иные программы.

Наконец, группа comp.sources.wanted — обычно самое подходящее место, где можно поместить соответствующий запрос, но посмотрите прежде их список вопросов и ответов (FAQ) «Как найти источники».

Почему недопустимы вложенные комментарии? Как прикажете «выключить» фрагмент программы, в котором уже есть комментарии? Можно ли использовать комментарии внутри стринговых констант?

Вложенные комментарии принесут больше вреда, чем пользы, главным образом из за возможности случайно не закрыть комментарий, оставив внутри него символы «/*». По этой причине лучше «выключить» большой фрагмент программы, в котором уже есть комментарии, с помощью средств препроцессора #ifdef или #if 0.

Последовательность символов /* и */ не имеет специального значения внутри заключенных в двойные кавычки стрингов. Эта последовательность не рассматривается как комментарий, поскольку программа (особенно та, которая создает текст другой Си программы) должна иметь возможность эти комментарии печатать.

Как получить значение кода ASCII той или иной литеры, и наоборот?

В Си литеры представлены целыми числами, соответствующими их значениям (в соответствии с набором символов данной машины). Так что нет необходимости в преобразовании: если известна литера, то известно и ее значение.

Как реализовать последовательности и/или массивы бит?

Используйте массивы переменных типа char или int и несколько макросов для операций с отдельными битами

604

Тонкости и хитрости в вопросах и ответах

(используйте определение 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))

Как наилучшим образом определить число установленных бит, соответствующих определенному значению?

Решение этой и многих других проблем из области битоверчения можно ускорить и сделать более эффективным с помощью таблиц перекодировки.

Как повысить эффективность работы программы?

Тема эффективности, очень часто затрагиваемая, не так важна как многие склонны думать. Большая часть кода в большинстве программ не влияет на время исполнения. Если время, занимаемое каким то участком кода, мало по сравнению с общим временем исполнения, то для этого участка гораздо важнее простота и мобильность, чем эффективность. (Помните, что компьютеры очень, очень быстры и даже «неэффективный» участок кода может выполняться без видимой задержки).

Печально известны попытки предсказать «горячие точки» программы.

Когда эффективность программы имеет значение, важно использовать профилировщики для определения тех участков программы, которые заслуживают внимания. Часто основное время выполнения поглощается периферийными операциями, такими как ввод/вывод и выделение памяти, которые можно ускорить с помощью буферизации и хеширования.

Для небольших участков программы, критичных в смысле эффективности, жизненно важно выбрать подходящий алгоритм; «микрооптимизация» этого участка менее важна. Многие часто предлагаемые «приемы по увеличению эффективности» (вроде замены операции сдвига умножением на степень двойки) выполняются автоматически даже неизощренными компиляторами.

605

Тонкости и хитрости в вопросах и ответах

Неуклюжие попытки оптимизации способны так увеличить размер программы, что ее эффективность упадет.

Правда ли, что применение указателей более эффективно, чем применение массивов? Насколько замедляет программу вызов функции? Быстрее ли ++i чем i = i + 1?

Точные ответы на эти и многие другие похожие вопросы, конечно же, зависят от процессора и применяемого компилятора. Если знать это необходимо, придется аккуратно определить время выполнения тестовых программ. (Часто различия столь незначительны, что потребуются сотни тысяч повторений, чтобы их увидеть. Если есть возможность, посмотрите ассемблерный листинг, выдаваемый компилятором, чтобы убедиться в различной трансляции двух претендующих на первенство альтернатив).

«Обычно» быстрее продвигаться по большим массивам с помощью указателей, чем с помощью индексов, однако есть процессоры, для которых справедливо обратное.

Хотя вызовы функций и увеличивают время выполнения, сами функции настолько повышают модульность и простоту понимания программы, что едва ли полезно от них отказываться.

Прежде чем переписывать выражения типа i=i+1, вспомните, что имеете дело с компилятором Си, а не с программируемым калькулятором. Любой приличный компилятор будет одинаково транслировать ++i, i+=1; i=i+1.

Использовать ++i, i+=1 или i=i+1 — вопрос стиля, а не эффективности.

Почему не выполняется такой фрагмент: char *p = "Hello, world!";

p[0] = tolower(p[0]);

Стринговые константы не всегда можно модифицировать, за исключением случая, когда ими инициализируется массив. Попробуйте:

char a[] = "Hello, world!";

(Для компиляции старых программ некоторые компиляторы имеют ключ, который управляет возможностью модификации стринговых констант.)

606

Соседние файлы в предмете Программирование на C++