Текст
                    Сэмюел П. Харбисон
Гай Л.Стил



Сэмюел П.Харбисон Гай Л.Стил ЯзыкС с примерами Перевод с английского под ред. С. Молявко Москва Издательство БИНОМ 2011
004.43 32.973.26-018.1 Х20 Перевод с английского Попова В.В., Юдина А.В. Самюел П. Харбисон, Гай Л. Стил Язык С с примерами. Пер. с англ. — М.: ООО «Издательство Бином», 2011 г. — 528 с.: ил. Это справочное руководство содержит полное описание последнего Стандарта языка С, его рабочих библиотек и стиля С-программирования. Американский оригинал выдер- жал пять изданий и по праву считается авторитетным справочником из категории бест- селлеров. В книгу входят описания Стандарта С 1999, Стандарта С 1989, традиционного С и С, совместимого с C++. Рассмотрен синтаксис языка, препроцессор, типы, выраже- ния, операторы и функции. Текст содержит множество тщательно проверенных и под- робно откомментированных примеров. Контрольные вопросы и задачи (с ответами) по- зволяют читателю проверить себя. Исчерпывающе описаны библиотечные функции, рас- смотрены особенности их вызова и возможные ошибочные ситуации. Приведено фор- мальное описание синтаксиса. Именно такое руководство всегда должно быть под рукой у каждого программиста и кодировщика. С A REFERENCE MANUAL, Fifth Edition ISBN 978-5-9518-0417-4 (рус.) ISBN 978-1-59749-106-8 (англ.) Prentice Hall, Inc. Издание на русском языке. Издательство Бином, 201 I Научно-техническое издание Самюел П. Харбисон, Гай Л. Стил Язык С с примерами Подписано в печать 28.09.2010. Формат 70х 100/16. Усл. печ. л. 42,9. Бумага газетная. Печать офсетная. Тираж 1000 зкз. Заказ 3580 Издательство Бином, 2011 г. 103473, Москва, ул. Краснопролетарская, 16 Отпечатано в ГУП ИПК «Ульяновский дом печати» 432601, г. Ульяновск, ул. Гончарова, 14
Содержание Предисловие........................................................11 Книга в Интернете................................................12 Благодарности....................................................12 ЧАСТЬ I. Язык С.......................................................15 Глава 1. Введение.....................................................17 1.1 Развитие языка С.............................................17 1.2 Какой из диалектов С следует использовать?...................20 1.3 Обзор процесса программирования на С.........................21 1.4 Соответствие Стандарту.......................................22 1.5 Нотация синтаксиса...........................................23 Глава 2. Лексика языка................................................25 2.1 Алфавит......................................................25 2.2 Комментарии..................................................33 2.3 Лексемы (токены).............................................35 2.4 Операции и разделители.......................................35 2.5 Идентификаторы...............................................36 2.6 Ключевые слова...............................................38 2.7 Константы................................................... 39 2.8 Совместимость с C++..........................................54 2.9 Наборы символов, алфавиты и кодировки........................55 2.10 Упражнения..................................................57 Глава 3. Препроцессор С...............................................59 3.1 Директивы препроцессора......................................59 3.2 Лексические принципы препроцессора...........................60 3.3 Макроопределение и макроподстановка..........................62 3.4 Включение файлов.............................................75 3.5 Условная компиляция..........................................77 3.6 Явная нумерация строк........................................82 3.7 Директива pragma.............................................83 3.8 Директива #еггог.............................................85 3.9 Совместимость с C++..........................................86 3.10 Упражнения.................................................87 Глава 4. Объявления...................................................89 4.1 Формат объявлений............................................90 4.2 Терминология.................................................91 4.3 Спецификаторы класса памяти и функций........................99 4.4 Спецификаторы и квалификаторы типа..........................102 4.5 Описатели...................................................112 4.6 Инициализаторы..............................................119 4.7 Неявные объявления..........................................130 4.8 Внешние имена...............................................130
6 Язык программирования С 4.9 Совместимость с С+- +.......................................133 4.10 Упражнения.................................................136 Глава 5. Типы.........................................................139 5.1 Целые типы..................................................140 5.2 Типы с плавающей точкой.....................................149 5.3 Указательные типы...........................................153 5.4 Массивы.....................................................157 5.5 Типы перечисления...........................................162 5.6 Структурные типы............................................165 5.7 Тип данных объединение......................................177 5.8 Функциональные типы.........................................182 5.9 Тип void....................................................184 5.10 Имена typedef..............................................185 5.11 Совместимость типов........................................188 5.12 Имена типов и абстрактные объявители.......................193 5.13 Совместимость с C+ +.......................................194 5.14 Упражнения.................................................195 Глава 6. Преобразования и представления...............................199 6.1 Представления...............................................199 6.2 Преобразования..............................................207 6.3 Обычные преобразования......................................213 6.4 Совместимость с C++.........................................219 6.5 Упражнения..................................................220 Глава 7. Выражения....................................................223 7.1 Объекты, 1-значения и именующие выражения...................223 7.2 Выражения и приоритет.......................................224 7.3 Первичные выражения.........................................227 7.4 Постфиксные выражения.......................................230 7.5 Унарные выражения...........................................239 7.6 Выражения бинарных операций.................................246 7.7 Логические операторные выражения............................260 7.8 Условные выражения..........................................262 7.9 Выражения присваивания......................................264 7.10 Последовательные выражения.................................267 7.11 Константные выражения......................................269 7.12 Последовательность оценки..................................272 7.13 Отбрасывание значений......................................274 7.14 Оптимизация обращений к памяти.............................275 7.15 Совместимость с C-i t-.....................................276 7.16 Упражнения.................................................276 Глава 8. Операторы....................................................279 8.1 Общий синтаксис операторов..................................279 8.2 Операторное выражение.......................................280 8.3 Операторы с метками.........................................281 8.4 Составные операторы.........................................281 8.5 Условные операторы..........................................284 8.6 Операторы циклов............................................286 8.7 Операторы switch............................................293 8.9 Операторы break и continue..................................296 8.9 Операторы return............................................298 8.10 Операторы goto.............................................299
Содержание 7 8.11 Пустые операторы................................................300 8.12 Совместимость с C++.............................................301 8.13 Упражнения......................................................301 Глава 9. функции...........................................................303 9.1 Определения функций..............................................304 9.2 Прототипы функций................................................306 9.3 Объявление формальных параметров.................................313 9.4 Настройка типов параметров.......................................315 9.5 Соглашения о преобразовании параметров...........................316 9.6 Соответствие параметров..........................................317 9.7 Типы возвращаемых значений.......................................318 9.8 Соглашение о возвращаемых значениях..............................319 9.9 Функция main.....................................................319 9.10 Встраиваемые функции............................................321 9.11 Совместимость с C++.............................................323 9.12 Упражнения......................................................324 ЧАСТЬ II. Библиотеки С.....................................................327 Глава 10. Введение в библиотеки............................................329 10.1 Стандартные средства С..........................................330 10.2 Совместимость с C++.............................................331 10.3 Библиотечные идентификаторы и заголовочные файлы................333 Глава 11. Стандартные дополнения языка.....................................341 11.1 NULL, ptrdiff_t, size_t, offsetof...............................341 11.2 EDOM, ERANGE, EILSEQ, errno, strerror, perror...................342 11.3 bool, false, true...............................................344 11.4 va_list, va_start, va_arg, va_end...............................345 11.5 Макросы операций Standard C.....................................348 Глава 12. Обработка символов...............................................349 12.1 isalnum, isalpha, iscntrl, iswalnum, iswalpha, iswcntrl ........350 12.2 iscsym, iscsymf.................................................352 12.3 isdigit, isodigit, isxdigit, iswdigit, iswxdigit................352 12.4 isgraph, isprint, ispunct, iswgraph, iswprint, iswpunct.........353 12.5 islower, isupper, iswlower, iswupper............................354 12.6 isblank, isspace, iswhite, iswspace.............................354 12.7 toascii.........................................................355 12.8 toint...........................................................355 12.9 tolower, toupper, towlower, towupper............................356 12.10 wctype_t, wctype, iswctype.....................................357 12.11 wctrans_t, wctrans.............................................358 Глава 13. Обработка строк..................................................359 13.1 strcat, strncat, wcscat, wcsncat................................360 13.2 strcmp, strncmp, wcscmp, wcsncmp................................361 13.3 strcpy, strncpy, wcscpy, wcsncpy................................361 13.4 strlen, wcslen..................................................362 13.5 strchr, strrchr, wcschr, wcsrchr................................363 13.6 strspn, strcspn, strpbrk, strrpbrk, wcsspn, wcscspn, wcspbrk....364 13.7 strstr, strtok, wcsstr, wcstok..................................365 13.8 strtod, strtof, strtold, strtol, strtoll, strtoul, strtoull.....367
8 Язык программирования С 13.9 atof, atoi, atol, atoll...........................................367 13.10 strcoll, strxfrm, wcscoll, wcsxfrm...............................367 Глава 14. функции управления памятью..........................................369 14.1 memchr, wmemchr...................................................369 14.2 memcmp, wmemcmp...................................................370 14.3 memcpy, memccpy, memmove, wmemcpy, wmemmove.......................370 14.4 memset, wmemset...................................................371 Глава 15. Средства ввода/вывода...............................................373 15.1 FILE, EOF, wchart, wint_t, WEOF...................................375 15.2 fopen, fclose, fflush, freopen, fwide.............................375 15.3 setbuf, setvbuf...................................................379 15.4 stdin, stdout, stderr.............................................380 15.5 fseek, ftell, rewind, fgetpos, fsetpos............................381 15.6 fgetc, fgetwc, getc, getwc, getchar, getwchar, ungetc, ungetwc....383 15.7 fgets, fgetws, gets...............................................384 15.8 fscanf, fwscanf, scanf, wscanf, sscanf, swscanf...................385 15.9 fputc, fputwc, putc, putwc, putchar, putwchar.....................394 15.10 fputs, fputws, puts..............................................395 15.11 fprintf, printf, sprintf, snprintf, fwprintf, wprintf, swprintf..395 15.12 v[x]prlntf, v[x]scanf............................................409 15.13 fread, fwrite....................................................411 15.14 feof, ferror, clearerr...........................................412 15.15 remove, rename...................................................412 15.16 tmpfile, tmpnam, inktemp.........................................413 Глава 16. Утилиты общего назначения...........................................415 16.1 malloc, calloc, mlalloc, clalloc, free, cfree.....................415 16.2 rand, srand, RAND_MAX.............................................418 16.3 atof, atoi, atol, atoll...........................................418 16.4 strtod, strtof, strtold, strtol, strtoll, strtoul, strtoull.......419 16.5 abort, atexit, exit, _Exit, EXIT FAILURE, EXITJSUCCESS............421 16.6 getenv............................................................423 16.7 system............................................................423 16.8 bsearch, qsort....................................................424 16.9 abs, labs, llabs, div, Idiv, Ildiv................................426 16.10 mblen, mbtowc, wctonib...........................................427 16.11 mbstowcs, wcstombs...............................................430 Глава 17. Математические функции..............................................433 17.1 abs, labs, llabs, div, Idiv, Ildiv................................434 17.2 tabs..............................................................434 17.3 ceil, floor, Irint, llrint, Iround, llround, nearbyint, round, rint, trunc . . . 434 17.4 fmod, remainder, remquo...........................................436 17.5 frexp, Idexp, modf, scalbn........................................437 17.6 exp, exp2, expml, ilogb, log, loglO, loglp, log2, logb............438 17.7 cbrt, fina, hypot, pow, sqrt......................................439 17.8 rand, srand, RAND MAX.............................................439 17.9 cos, sin, tan, cosh, sinh, tanh...................................440 17.10 acos, asin, atan, atan2, acosh, asinh, atanh.....................440 17.11 fdim, fmax, fmin.................................................442 17.12 Макросы общего типа..............................................442 17.13 erf, erfc, Igainma, tgamma.......................................445 17.14 fpclassify, isfinite, isinf, isnan, isnormal, signbit............446
Содержание 9 17.15 copysign, nan, nextafter, nexttoward..........................447 17.16 isgreater, isgreaterequal, isless, islessequal, islessgreater, isunordered..........................................................448 Глава 18. функции даты и времени..........................................449 18.1 clock, clock_t, CLOCKS_PER_SEC, times..........................449 18.2 time, time_t...................................................450 18.3 asctime, ctime.................................................451 18.4 gmtime, localtime, mktime......................................452 18.5 difftime.......................................................453 18.6 strftime, wcsftime.............................................454 Глава 19. Управляющие функции.............................................457 19.1 assert, NDEBUG.................................................457 19.2 system, exec...................................................458 19.3 exit, abort....................................................458 19.4 setjmp, longjmp, jmp_buf.......................................458 19.5 atexit.........................................................459 19.6 signal, raise, gsignal, ssignal, psignal.......................460 19.7 sleep, alarm...................................................462 Глава 20. Географическая установка........................................463 20.1 setlocale......................................................463 20.2 localeconv.....................................................465 Глава 21. Расширения целых типов..........................................469 21.1 Общие правила..................................................469 21.2 Целые типы точных размеров.....................................472 21.3 Типы минимальной ширины данных минимальных размеров............473 21.4 Быстрые типы с минимальной шириной.............................474 21.5 Целые типы размера указателя и максимального размера...........474 21.6 Интервалы значений типов ptrdiff_t, size_t, wchar_t, wint_t и sig_atomic_t......................................................475 21.7 imaxabs, imaxdiv, imaxdiv_t....................................476 21.8 strtoimax, strtoumax...........................................476 21.9 wcstoimax, wcstoumax...........................................477 Глава 22. Операции над значениями с плавающей точкой......................479 22.1 Обзор..........................................................479 22.2 Дополнительные средства выполнения операций над значениями с плавающей точкой...................................480 22.3 Исключения.....................................................482 22.4 Режимы округления..............................................483 Глава 23. Комплексная арифметика..........................................485 23.1 Соглашения комплексной библиотеки..............................485 23.2 complex, _Complex_I, imaginary, imaginary_I, 1.................485 23.3 CX_LIMITED_RANGE...............................................486 23.4 cacos, easin, catan, ccos, csin, ctan..........................486 23.5 cacosh, casinh, catanh, ccosh, csinh, ctanh....................487 23.6 cexp, clog, cabs, epow, csqrt..................................488 23.7 carg, cimag, creal, conj, cproj................................489
10 Язык программирования С Глава 24. функции обработки широких и многобайтовых символов .... 491 24.1 Основные типы и макросы..................................491 24.2 Преобразование из широких символов в многобайтовые и обратно . . . 492 24.3 Преобразование из строк широких символов в строки многобайтовых и обратно.......................................493 24.4 Преобразование арифметических типов......................495 24.5 Функции ввода и вывода...................................495 24.6 Строковые функции........................................496 24.7 Преобразование даты и времени............................497 24.8 Функции классификации и отображения широких символов.....497 Приложение А. Символьный набор ASCII...............................499 Приложение В. Синтаксис............................................501 Приложение С. Ответы к упражнениям.................................515
Предисловие Эта книга является справочным руководством по языку С. Наша цель состоя- ла в том, чтобы дать полное и четкое описание языка, стандартной библиотеки и такого стиля программирования на С, который придает особое значение кор- ректности, переносимости и удобству сопровождения. Мы предполагаем, что наши читатели уже владеют базовыми знаниями в области программирования, и что многие из них в дальнейшем станут опыт- ными программистами на С. Чтобы сохранить стиль справочника, эта книга описывает язык С снизу-вверх: лексические структуры, препроцессор, объяв- ления, типы, выражения, операторы, функции и стандартная библиотека. Для того чтобы читатели могли начинать читать это руководство с любого мес- та, в книге содержится множество перекрестных ссылок. Пятое издание книги содержит полное описание последнего международно- го стандарта С ISO/IEC 9899:1999 (С99). В книге отмечены те свойства языка, которые он приобрел в рамках Стандарта С99 и те, что отличают С99 от преды- дущей версии стандарта — С89. На сегодняшний день это единственная книга, которая может служить руководством для всех основных версий С: традици- онный С, Стандарт С 1989 года, Дополнения и поправки С89 1995 года и, нако- нец, Стандарт С 1999 года. Несмотря на то, что Стандарт С99 включает в себя много нового материала, мы не вносили значительных изменений в организа- цию глав и разделов книги, поэтому у читателей, знакомых с предыдущими изданиями этой книги не должно возникнуть проблем с поиском необходимой им информации. Эта книга изначально появилась как следствие нашей работы в фирме Tartan, Inc., где мы разрабатывали семейство компиляторов С для широкого спектра компьютеров — от микроконтроллеров до мэйнфреймов. Мы стреми- лись создать компиляторы, которые были хорошо документированы, выпол- няли точную и информативную диагностику ошибок, а также генерировали высокоэффективный объектный код. Если программа, написанная на С, кор- ректно компилируется одним компилятором С, то она должна компилировать- ся и другими компиляторами, в той мере, в которой это позволяют делать раз- личия между аппаратными платформами. В 1984 году мы пришли к выводу, что, несмотря на популярность С, не су- ществует достаточно точного описания языка, которое помогло бы нам в раз- работке новых компиляторов. Увы, точных описаний не было и для наших программистов/пользователей, которые хотели бы использовать компилято- ры, анализирующие С-программы более тщательно, чем доступные в то время инструменты. В этой книге мы специально обратили внимание на те особенно- сти языка, которые влияют на ясность программы, эффективность объектного кода и переносимость программ между разными программными средами.
12 Язык программирования С Книга в Интернете Приглашаем читателей посетить web-сайт книги www. CAReferenceManu- al.com. На нем размещены примеры программ, найденные в тексте ошибки, дискуссии, пояснения и ссылки на различные ресурсы сети, касающиеся язы- ка С. Благодарности Я хочу выявить особенную признательность за критику при подготовке пя- того издания этой книги Рексу Яшке (Rex Jaeschke), бывшему главе комитета NCITS J11; Антуану Траксу (Antione Тгих) из Хельсинки, Финляндия; а так- же Стиву Адамчику (Steve Adamczyk), основателю Edison Design Group. Я хочу поблагодарить за помощь при подготовке предыдущих изданий Джеффри Есакова (Jeffrey Esakov), Алана Дж. Филипски (Alan J. Filipski), Френка Дж. Вагнера (Frank J. Wagner), Дебру Мартин (Debra Martin), П. Дж. Плоугера (Р. J. Plauger) и Стива Виноски (Steve Vinoski). Кроме того, мне помо- гали Аурелио Бигноли (Aurelio Bignoli), Стив Клэмэйдж (Steve Clamage), Ар- тур Эванс мл. (Arthur Evans jr.). Рой Дж. Фуллер (Roy J. Fuller), Моррис М. Кессан (Morris М. Kessan), Джордж В. Рейли (George V. Reilly), Марк Лан (Mark Lan), Майк Хьюитт (Mike Hewett), Чарльз Фишер (Charles Fischer), Ке- вин Роджерс (Kevin Rodgers), Том Гибб (Tom Gibb), Дэвид Лим (David Lim), Ставрос Макракис (Stavros Macrakis), Стив Вегдал (Steve Vegdahl), Кристофер Викери (Christopher Vickery), Питер ван дер Линден (Piter van der Linden) и Дэйв Вилсон (Dave Wilson). А также Майкл Ангус (Michael Angus), Мэди Бауэр (Mady Bauer), Ларри Брид (Larry Breed), Сью Бротон (Sue Broughton), Алекс Чайковски (Alex Czajkowski), Роберт Фире (Robert Firth), Дэвид Гаффни (David Gaffney), Стив Горман (Steve Gorman), Деннис Гамильтон (Dennis Hamilton), Крис Ханна (Chris Hanna), Кен Гарренстин (Ken Harrenstien), Рекс Яшке (Rex Jaeschke), Дон Линдсэй (Don Lindsay), Том МакДональд (Тот MacDonald), Питер Нельсон (Peter Nelson), Джо Ньюкамер (Joe Newcomer), Кевин Нолиш (Kevin Nolish), Дэвид Ноткин (David Notkin), Питер Пламондон (Peter Plamondon), Роджер Рэй (Roger Ray), Ларри Рослер (Larry Rosler), Дэ- вид Спенсер (David Spencer) и Барбара Стил (Barbara Steele). Источником алгоритмов для некоторых тестовых программ, описанных в этой книге, являются алгоритмы, взятые из следующих источников: • Beeler, Michael, Gosper, R. William, and Schroeppel, Richard, HAKMEM, Al Memo 239 (Massachusetts Institute of Technology Artificial Intelligence Laboratory, February 1972); • Bentley, Jon Louis, Writing Efficient Programs (Prentice-Hall, 1982); • Bentley, Jon Louis, «Programming Pearls» (ежемесячный раздел, появив- шийся в Communications of the ACM в августе 1983 года); многократно издавался русский перевод книги Дж. Л. Бентли «Жемчужины програм- мирования»; • Kernighan, Brian W., and Ritchie, Dennis M., The C Programming Langu- age (Prentice-Hall, 1978); многократно издавался русский перевод книги Б. В. Керниган, Д. М. Ричи «Язык программирования С»;
Предисловие 13 • Knuth, Donald E., The Art of Computer Programming Volumes 1-3 (Addi- son-Wesley, 1968, 1969, 1973, 1981); существует русский перевод (Д. Е. Кнут «Искусство программирования», тт. 1-3); • Sedgewick, Robert, Algorithms (Addison-Wesley, 1983). Мы благодарны авторам за их прекрасные идеи. Гай Стил (Guy Steel) из-за своей насыщенной работы не может быть актив- ным участником последних изданий, что и отражено в частом использование местоимения «я» вместо «мы». Эта книга все еще отражает его глубокий и тщательный анализ языка С, но он не может нести ответственность за ошиб- ки настоящего издания. Книге С: A Reference Manual уже более 17 лет. Спасибо всем нашим читате- лям! Сэм Харбисон Питтсбург, Пенсильвания harbison@CAReferenceManual.com

ЧАСТЬ I Язык С

Глава 1 Введение Деннис Ритчи разработал язык программирования С в начале 70-х годов в Bell Laboratories. Его корни прослеживаются к таким языкам как Алгол 60 (1960), Кембриджский CPL (1963), язык BCPL (1967) Мартина Ричардса и язык В (1970), разработанный Кеном Томпсоном в Bell Labs. С является универсаль- ным языком программирования (языком общего назначения). Он традиционно использовался для системного программирования и, в частности, операцион- ная система UNIX была изначально написана на С. Популярность С объясняется множеством факторов. Он является эффектив- ным, мощным языком программирования с большой исполняемой библиоте- кой (run-time library), и, что важно, он очень компактен. Он обеспечивает точ- ный контроль над компьютером без сокрытия внутренних механизмов его функционирования. Программистам удобно с ним работать, так как он был стандартизирован более 10 лет назад. На С достаточно легко писать програм- мы, которые будут обладать переносимостью между разными операционными системами и разными национальными языками. И, наконец, существует ог- ромное количество программ на С, которые постоянно модифицируются и до- полняются. Следует также отметить, что, начиная с конца 90-х годов популярность языка С начала уменьшаться за счет «старшего брата» («big brother») C++. Однако язык С продолжает использоваться в тех случаях, где нет необходимо- сти в использовании особенностей C++, или где накладные расходы от исполь- зования C++ оказываются чрезмерными. Язык С проверен временем. Это язык, на котором опытные программисты могут писать быстро и качественно. Об этом свидетельствуют миллионы строк популярных приложений. 1.1 Развитие языка С На момент выхода первого издания этой книги в 1984 году, язык С уже был широко распространен. Но тогда еще не было ни официального стандарта, ни точного описания языка. Стандартами де-факто выступали используемые ком- пиляторы. Международный стандарт С появился в 1989 году и затем был пере- смотрен в 1994 и в 1999 годах. От модификации определения языка миллионы строк кода на С, которые существуют в мире, не изменятся. Поэтому мы постарались писать эту книгу
18 Глава 1 ?аким образом, чтобы программисты, использующие разные диалекты С мог- ш руководствоваться ею как справочником. 1.1.1 Традиционный С Первое описание языка С было приведено в первом издании книги «The С 3rogramming Language» (Prentice-Hall, 1978) Брайана Кернигана (Brian Ker- lighan) и Денниса Ритчи (Dennis Ritchie) (многократно издавался русский пе- ревод «Язык программирования С» — прим. ред.). Оно часто упоминается как :тандарт «K&R». После публикации книги язык продолжал развиваться, не- которые свойства добавлялись, некоторые исчезали. Под традиционным С мы тони.маем его описание начала 80-х годов до начала процесса его стандартиза- ции. Конечно, у компаний-распространителей С-инструментария были и свои юбственные расширения традиционного С. 1.1.2 Standard С (1989) Понимая, что стандартизация языка поможет С стать еще более распро- страненным в сфере коммерческого программирования, Американский На- циональный Институт Стандартов (ANSI) сформировал в 1982 году комитет X3J11 (ныне NCITS J11), целью которого была стандартизация языка С и его исполняемых библиотек (run time libraries). Под руководством Джима Бро- уди (Jim Brodie) этот комитет сформулировал стандарт, который был принят з 1989 году под названием Американский Национальный Стандарт X3.1591989 или «ANSI С». После завершения работы комитета ANSI была создана международная группа по стандартизации. Группа ISO/IEC JTC1/SC22/WG14 под руково- цством П. Дж. Плоугера (P.J. Plauger) преобразовала стандарт ANSI в между- народный стандарт ISO/IEC 9899:1990 с внесением незначительных редактор- ских правок стандарта ANSI С. Затем стандарт ISO/IEC был принят комитетом A.NSI. После этого программисты начали ссылаться на этот основной стандарт как на просто «Стандарт С» (Standard С). Впоследствии этот стандарт был из- менен, поэтому, когда мы будем на него ссылаться, он будет упоминаться как Стандарт С (1989) или просто «С89». В С89 были включены некоторые изменения и дополнения традиционного С: • Добавлена действительно стандартизированная библиотека. • Новые команды и параметры препроцессора. • Прототипы функций, которые позволяют определять типы аргументов функции в ее объявлении. • Некоторые новые ключевые слова, в том числе const, volatile и signed. • Широкие символы (wide characters), широкие строки (wide strings) и мно- гобайтовые символы (multibyte characters). • Много небольших изменений и пояснений, касающихся правил преобра- зований, объявлений и проверок типов. 1.1.3 Standard С (1995) После принятия стандарта «С89» работа группы WG14 над стандартом про- должалась. Группа выпустила две поправки и дополнение к стандарту С
Введение 19 1989 года. Это были незначительные изменения, касающиеся в основном до- бавления новых библиотек. В результате получился стандарт «С89 с Дополне- нием 1» или просто «С95». Были внесены следующие изменения: • В стандартную библиотеку добавлены три новых заголовочных файла: iso646.h, wctype.h и wchar.h. • Добавлены некоторые новые лексемы (token) и макросы, используемые для замены операций и знаков пунктуации (punctuation character), кото- рых нет в наборах символов некоторых национальных языков. • Добавлены некоторые новые коды форматирования для семейства функ- ций printf/scanf. • Для многобайтных и широких символов добавлены новые типы, констан- ты и большое количество новых функций. 1. f.4 Standard С (1999) Стандарты ISO/IEC должны пересматриваться и обновляться на регуляр- ной основе. В 1995 году группа WG14 начала работать над более основатель- ной ревизией Стандарта С. Результатом этой работы стал новый стандарт С, который был завершен и утвержден в 1999 году под названием ISO/IEC 9899:1999. Стандарт ISO/IEC 9899:1999 или «С99» замещает предыдущий стандарт С (а также все поправки и дополнения) и становится теперь офици- альным Стандартом С (Standard С). Поставщики компиляторов и библиотек С обновляют свои продукты для соответствия новому стандарту. С99 вносит значительные изменения в стандарт С89/С95, как в сам язык, так и в стандартную библиотеку. Добавлены следующие элементы: • Комплексная арифметика. • Расширен набор целых типов данных, в том числе за счет добавления бо- лее длинного целого. • Массивы переменной длины. • Булев тип. • Улучшена поддержка неанглийских наборов символов. • Улучшена поддержка типов с плавающей точкой, включая математиче- ские функции для всех типов. • Комментарии в стиле C++ (//). С99 включает намного больше изменений, чем С95, так как он включает как изменения в самом языке, так и расширение стандартной библиотеки. До- кумент стандарта С99 значительно объемнее, чем С89, но внесенные измене- ния не затрагивают фундаментальных основ языка и выполнены «в духе С». 1.1.5 Standard C++ Язык C++ был разработан Бьерном Страуструпом в начале 80-х годов в AT&T Bell Labs. Ныне он значительно потеснил С в сфере программирования общего назначения. Большинство реализаций С являются, по сути, реализа- циями C/C++, что дает программистам возможность выбора языка програм- мирования. C++ был также стандартизирован комитетом ISO в рамках ISO/IEC 14882:1998 или как «Стандарт C++» (Standard C++). По сравнению с С, C++ включает в себя большое количество необходимых для написания
20 Глава 1 крупных приложений усовершенствований, таких как улучшенная проверка типов и поддержка объектно-ориентированной технологии программирова- ния. Недостатком C++ является его относительная сложность. Стандарт C++ является в большой степени надмножеством Стандарта С. Но так как эти стандарты разрабатывались по разным программам, они не могут быть полностью согласованы между собой. Более того, С всегда держался на некотором расстоянии от C+ + . Например, не было даже попыток внесения в С хотя бы упрощенной версии типов классов C++. На С возможно написание программ таким образом, чтобы исходный код соответствовал одновременно двум стандартам (С и C++). Это так называемый чистый С. Программы, написанные на чистом С, могут быть откомпилирова- ны и как С-программы, и как С+ + -программы. C++ налагает более жесткие ог- раничения, чем стандарт С, поэтому желательно использование чистого С при необходимости написания переносимого кода. При этом надо учитывать две особенности: • Программы, написанные на чистом С должны использовать прототипы функций. Объявления функций в старом стиле не разрешены в C++. • Программы, написанные на чистом С не должны использовать имена, ко- торые являются ключевыми словами для C++, например virtual и class. Существует еще несколько правил и различий, но они обычно не вызывают проблем. В этой книге мы расскажем вам, как писать программы на языке С, удовлетворяющие стандарту С и не вызывающие ошибок компиляторов C++. Мы не обсуждаем особенностей C++, которые противоречат стандарту С (хотя как раз в них и заключено все интересное, что есть в C+ + ). 1.1.6 О чем эта книга? Эта книга описывает три основных вариации С: традиционный С, С89 и С99. Здесь также описаны особенности С89 с Дополнением 1 и чистый С, ко- торый является подмножеством С и C++. Мы рассмотрим технику написания «хороших» программ на С — программ, которые легко читаются, переносятся и сопровождаются. Официально стандартом С является С99. Но мы будем использовать термин Стандарт С (или Standard С), понимая под ним особенности и концепции, заложенные в С89 и развиваемые в С99. Особенности, которые появились только в С99, выделены отдельно, чтобы программисты, ориентирующиеся на С89 могли обойти их использование. 1.2 Какой из диалектов С следует использовать? Выбор диалекта С зависит от того, какая реализация С вам доступна и на- сколько переносимый код вы собираетесь писать. Вам доступны: • С99 — текущая версия Стандарта С. В нем описана самая новая специфи- кация С, но некоторые компиляторы С могут ее еще не поддерживать (ситуация быстро меняется).
Введение 21 • С89 — предыдущая версия Стандарта С. На этой версии базируется боль- шинство программ, написанных на С, и большинство компиляторов С (в основном, с учетом Дополнения 1). • Традиционный С. Сейчас его используют в основном для поддержки про- грамм, написанных на С достаточно давно. • Чистый С, совместимый с C++. Стандарты С совместимы снизу вверх. Это означает, что С99 в основном со- вместим с С89, который в свою очередь (по большей части) совместим с тради- ционным С. К сожалению, очень сложно писать код на С, который обладает об- ратной совместимостью. Возьмем, к примеру, прототипы функций. Их необхо- димо использовать в C++, они опциональны в Стандарте С и их использование недопустимо в традиционном С. Для решения этой проблемы можно использо- вать препроцессор, с помощью директив которого можно сделать ваш код со- вместимым со всеми диалектами С. Описание того, как использовать препро- цессор для этих целей вы найдете в главе 3, а конкретный пример описан в разделе 3.9.1. Если вы не ограничены уже существующим исходным кодом или компиля- тором, то вам определенно следует использовать в качестве базового языка стандартный С. Компиляторы Стандарта С ныне общедоступны. Вы можете использовать, к примеру, компилятор GNU С (gcc) Фонда свободного про- граммного обеспечения (Free Software Foundation), который распространяется бесплатно, отвечает Стандарту С и имеет множество расширений. 1.3 Обзор процесса программирования на С Мы предполагаем, что большинство наших читателей достаточно хорошо знакомы с программированием на таком высокоуровневом языке, как С. Но некоторым, вероятно, будет полезен краткий обзор процесса программирова- ния на С. Программа на С состоит из одного или более исходных файлов или единиц (модулей) трансляции, каждый из которых содержит какую-то часть всей программы. Обычно, это набор внешних функций. Основная часть объявлений часто собрана в заголовочные файлы, которые включены в исходные файлы с помощью директивы #include (раздел 3.4). Внешняя функция, с которой на- чинается выполнение программы, должна носить название main (раздел 9.9). Каждый исходный файл независимо обрабатывается компилятором (com- piler) С. Он транслирует программу в инструкции, понятные компьютеру. Компилятор «разбирает» программу на С и анализирует ее корректность. Если программист допустил ошибку, которую компилятор может обнаружить, он выдаст о ней сообщение. Если ошибок нет, то на выходе компилятор выдаст так называемый объектный код или объектный модуль. Когда все исходные файлы откомпилированы, объектные модули подаются на вход другой программы, называемой компоновщиком (linker). Компонов- щик связывает модули, добавляет функции из стандартной библиотеки, а так- же распознает ошибки, такие как ошибка определения функции. Компонов- щик обычно не специфичен для С. У каждой компьютерной системы есть свой стандартный компоновщик, который может использоваться для программ, на- писанных на разных языках программирования. Компоновщик создает один исполняемый модуль, который может быть запущен или выполнен (run). Про-
22 Глава 1 граммисту может показаться, что в разных компьютерных системах процесс компилирования и компоновки программы выполняется по-разному, но это не так. Дело в том, что в интегрированных средах, таких как Microsoft Visual Studio этот процесс полностью скрыт. В этой книге мы не затрагиваем подроб- но процесс компоновки программ, написанных на С. Для ознакомления с дета- лями, читателям следует обратиться к документации по программированию в их компьютерных системах. Пример Предположим, у нас есть программа aprogram, состоящая из двух исходных фай- лов — hello .с и startup.с. Файл hello.с содержит следующие строки: ((include <stdio.h> /* определяет функцию print! */ void hello(void) { print!("Hello’\n"); } Так как файл hello.с содержит элементы (функция hello), которые будут использо- ваны другой частью нашей программы, мы создадим заголовочный файл hello.h, ко- торый будет объявлять эти элементы (функции). Он будет содержать строку: extern void hello(void); Файл startup.с содержит основную часть программы, которая просто вызывает функцию hello: ((include "hello.h" int main(void) { hello () ; return 0; } В системе UNIX для компилирования, компоновки и выполнения программы потре- буется только два шага: % сс -о aprogram hello.с startup.с % aprogram Первая команда компилирует и компонует два исходных файла, добавляя необхо- димые библиотечные функции и записывая исполняемую программу в файл apro- gram. Вторая команда выполняет программу aprogram, которая выводит на экран: Hello! В системах, отличных от UNIX, могут использоваться другие команды. Все чаще в современном инструментарии программирования программист обеспечивается ин- тегрированным графическим интерфейсом. Здесь построение приложений h<i С тре- бует только выбора команд из меню или нажатия на графические кнопки. 1.4 Соответствие Стандарту Как программы, написанные па С, так и компиляторы (реализации) С мо- гут соответствовать (conform) стандарту С. Говорят, что программа, напи- санная на С, строго соответствует (strictly conforming) стандарту С, если она использует только возможности языка и библиотеки, описанные в стан- дарте С. Функционирование программы не должно касаться ни одного аспекта С, который стандарт охарактеризовывает как точно не установленный, псоп-
Введение 23 ределенный или зависимый от реализации. Для определения соответствия реализации Стандарту С существуют наборы тестов от фирм Perennial, Inc. и Plum Hall, Inc. Существует два вида соответствия реализаций — базовые и автономные. Го- ворят, что реализация С имеет базовое соответствие (conforming hosted implementation) стандарту, если она допускает любую программу со строгим соответствием стандарту. Если же реализация допускает любую программу со строгим соответствием стандарту, использующую только элементы библиотек, которые находятся в заголовочных файлах float.h, iso646.h (С95), limits.h, stdarg.h, stdbool.h (C99), stddef.h и stdint.h, то она имеет автономное соот- ветствие (conforming freestanding implementation) стандарту. В главе 10 при- ведено содержимое этих заголовочных файлов. Автономное соответствие стан- дарту подразумевает приспособление реализаций С для встроенных систем и других целевых сред с минимальными средствами поддержки времени вы- полнения. У таких систем, к примеру, может не быть файловой подсистемы. Соответствующая стандарту программа — это программа, которая допуска- ется соответствующей стандарту реализацией. Таким образом, программа, ко- торая соответствует стандарту, может зависеть от непереносимых, зависимых от реализации особенностей соответствующей стандарту реализации. В то вре- мя как программа со строгим соответствием не может зависеть от таких осо- бенностей (поэтому она и будет максимально переносимой). Соответствующие стандарту реализации могут предоставлять расширения, которые не будут искажать смысл строго соответствующей стандарту програм- мы. Это позволяет реализациям добавлять библиотечные функции и опреде- лять свои собственные директивы #pragma, но не разрешает вводить новые за- резервированные слова или менять функционирование стандартных библио- течных функций. Поставщики компиляторов продолжают распространять расширения, кото- рые не соответствуют стандарту, но к которым уже привыкли их пользовате- ли. В компиляторах эти расширения можно включать и выключать с помо- щью специальных ключей. 1.5 Нотация синтаксиса В этой книге используется стилизованная нотация для выражения формы программы, написанной на С. При определении грамматики С терминальные символы напечатаны обычным шрифтом и в программе должны выглядеть точно также как и в книге. Нетерминальные символы напечатаны курсивом. Составленные из них слова начинаются с буквы, со следующими за ней (их мо- жет не быть) литерами, цифрами или дефисами: выражение список-аргументов описатель Синтаксические определения вводятся с помощью имени нетерминального символа и следующего за ним двоеточия. При наличии альтернатив они запи- сываются в столбец: символ : печатный-символ управляющий-символ
24 Глава 1 Если за двоеточием стоят слова «один из (одна из)», то это означает, что ка- ждый из терминальных символов, расположенных на одной и более строках, является альтернативным определением понятия, которое расположено перед двоеточием: цифра : одна из 0123456789 Опциональные компоненты обозначаются в определении суффиксом опц, добавленным к терминальному или нетерминальному символу: определение-константы перечисления ; константа-перечисления инициализатор-константы-перечисленияопц инициализатор : выражение { список-инициализаторов, опц }
Глава 2 Лексика языка Эта глава описывает лексические структуры языка С. В ней приведены прави- ла построения лексических единиц или лексем из символов, которые допусти- мы в исходных файлах С. 2.1 Алфавит Исходные файлы С состоят из последовательности символов, входящих в ал- фавит (набор символов — character set) языка С. Этот набор принадлежит разде- лу «Основная латиница» (Basic Latin) стандарта ISO/IEC 10646 и включает: 1. 52 заглавных и строчных литеры: ABCDEFGHIJKLMNOPQRSTUVWX YZabcdefghijklmnopqrstuv W X у Z 2. 10 цифр: 0123456789 3. символ пробела SPACE, 4. управляющие символы: символ горизонтальной табуляции (НТ), сим- вол вертикальной табуляции (VT) и символ перевода страницы (FF), 5. 29 графических символов (их официальные названия и сами символы приведены в таблице 2.1). Должен быть также предусмотрен способ разделения исходной программы на строки (line). Такое разделение можно осуществить с помощью символа, по- следовательности символов или некоторого механизма, не входящих в исход- ный алфавит (например, с помощью символа окончания записи). Таблица 2.1. Графические символы Символ Название Символ Название Символ Название • EXCLAMATION MARK (ВОСКЛИЦАТЕЛЬНЫЙ ЗНАК) # NUMBER SIGN (ЗНАК НОМЕРА) PLUS SIGN (ЗНАК ПЛЮС) EQUALS SIGN (ЗНАК РАВЕНСТВА) QUOTATION MARK (КАВЫЧКА) LEFT CURLY BRACKET (ОТКРЫВАЮЩАЯ ФИГ/PI 1АЯ СКОБКА)
26 Глава 2 Символ Название Символ Название Символ Название % PERCENT SIGN (ЗНАК ПРОЦЕНТОВ) — TILDE (ТИЛЬДА) } RIGHT CURLY BRACKET (ЗАКРЫВАЮЩАЯ ФИГУРНАЯ СКОБКА) CIRCUMFLEX ACCENT (ДИАКРИТИЧЕСКИЙ ЗНАК) 1 LEFT SQUARE BRACKET (ОТКРЫВАЮЩАЯ КВАДРАТНАЯ СКОБКА) / СОММА (ЗАПЯТАЯ) & AMPERSAND (АМПЕРСАНД) ] RIGHT SQUARE BRACKET (ЗАКРЫВАЮЩАЯ КВАДРАТНАЯ СКОБКА) • FULL STOP (ТОЧКА) * ASTERISK (ЗВЕЗДОЧКА) APOSTROPHE (АПОСТРОФ) < LESS-THAN SIGN (ЗНАК «МЕНЬШЕ») ( LEFT PARENTHESIS (ОТКРЫВАЮЩАЯ СКОБКА) 1 VERTICAL LINE (ВЕРТИКАЛЬНАЯ ЧЕРТА) > GREATER-THAN SIGN (ЗНАК «БОЛЬШЕ») - LOWLINE (СИМВОЛ ПОДЧЕКИВАНИЯ) \ REVERSE SOLIDUS или backslash (ОБРАТНАЯ КОСАЯ ЧЕРТА) / SOLIDUS или slash (ПРЯМАЯ КОСАЯ ЧЕРТА) ) RIGHT PARENTHESIS (ЗАКРЫВАЮЩАЯ СКОБКА) / SEMICOLON (ТОЧКА С ЗАПЯТОЙ) 7 QUESTION MARK (ВОПРОСИТЕЛЬНЫЙ ЗНАК) - HYPHEN-MINUS (ДЕФИС-МИНУС) 2 COLON (ДВОЕТОЧИЕ) В некоторых странах используются алфавиты, которые включают не весь набор графических символов, приведенных в таблице 2.1. В стандарте С89 (Дополнение 1) определены триграфы (trigraph) и лексемы (token), позволяю- щие писать программы на С в универсальной кодировке ISO 646-1083 Invari- ant Code Set. Иногда в исходных программах на С встречаются дополнительные символы: 1. символы форматирования, такие как символ возврата (BS) и символ возврата каретки (CR); 2. дополнительные символы раздела «Основная латиница», такие как символ $ (DOLLAR SIGN — символ доллара), символ @ (COMMERCIAL АТ — коммерческое при) и ' (GRAVE ACCENT — гравис). Символы форматирования интерпретируются как пробелы и не влияют на исходную программу. Дополнительные графические символы могут использо- ваться только в символьных и строковых константах, комментариях и именах файлов. Ссылки: базовая латиница 2.9; символьные константы 2.7.3; комментарии 2.2; кодировки 2.1.3; символьные управляющие коды 2.7.6; исполняемый набор символов 2.1.1; строковые константы 2.7.4; лексемы (токены) 2.4; триграфы 2.1.4
Лексика языка 27 2.1.1 Исполняемый набор символов Набор символов, проинтерпретированный во время выполнения программы (execution character set) не обязательно будет таким же, как и в исходном фай- ле программы. Символы из исполняемого набора представляются с помощью эквивалентов исходного набора или с помощью управляющих последовательно- стей, которые начинаются специальным символом обратной косой черты (\). В дополнение к упомянутым выше стандартным символам, исполняемый набор символов также содержит: 1. Нулевой символ (null), который должен кодироваться как символ со значением 0. 2. Символ новой строки (newline), который используется как маркер окончания строки. 3. Символы предупреждения (alert), возврата (backspace) и возврата ка- ретки (carriage return). Для обозначения конца строки используется нулевой символ. Для разделе- ния потока символов на строки во время процедур ввода/вывода используется символ новой строки. (В среде исполнения программы это выглядит так, как если бы символ новой строки был представлен в текстовых потоках. Тем не ме- нее, исполняемые библиотеки могут имитировать символ новой строки. На- пример, он может быть преобразован в признак конца строки на выходе и об- ратно в символ новой строки на входе.) Как и исходный набор символов, исполняемый набор символов тоже вклю- чает такие символы форматирования, как символ возврата, символы горизон- тальной и вертикальной табуляции, символ перевода страницы и символ воз- врата каретки. Для представления этих символов в исходных файлах исполь- зуются специальные управляющие последовательности. Исходные и исполняемые наборы символов одинаковы, если программа, написанная на С, компилируется и выполняется на одном и том же компьюте- ре. Однако иногда для программ выполняется кросс-компиляция, т.е. они скомпилированы на одном компьютере (главном), а выполняются на другом (целевом). Когда компилятор вычисляет значение константного выражения, которое включает в себя символы, то он должен использовать кодировку целе- вого компьютера, а не более естественную (для компилирующего компьютера) исходную кодировку. Ссылки: символьные константы 2.7.3; кодирование символов 2.1.3; алфавит 2.1; константные выражения 7.1 1; управляющие символы 2.7.5; текстовые потоки гл. 15 2.1.2 Символы-разделители и разделение строк Пробел, вертикальная и горизонтальная табуляция, перевод страницы и перевод строки имеют общее название символов-разделителей (whitespace character) или пробельных символов. (Комментарии, рассматриваемые в разде- ле 2.2, тоже относятся к символам-разделителям.) Эти символы игнорируются за исключением тех случаев, когда они используются в символьных и строко- вых константах, в именах файлов, включенных директивой #include, или для разделения смежных лексем. Символы-разделители также используются для представления исходного текста программы в удобной для чтения человеком форме.
28 Глава 2 Окончание строки исходной программы определяется символом или после- довательность символов признака конца строки. В некоторых реализациях символы форматирования, которые выполняют функции дополнительного разделение строк (такие как возврат каретки, перевод страницы и (или) верти- кальная табуляция), имеют отдельное название — символы-разделители строк (line break character). Разделение строк важно для различения препро- цессором строк, содержащих директивы. Первым символом следующей стро- ки является символ, который следует за символом-разделителем строк. Если первым символом строки является символ-разделитель строк, то эта строка считается пустой. Строка исходного кода может размещаться на двух и более строках, если каждая из них за исключением последней будет завершаться символом обрат- ной косой черты (\) или определенным Standard С триграфом ??/. При обра- ботке таких строк символ обратной косой черты и маркер конца строки удаля- ются для создания полной строки, носящей название логической строки (logical source line). Это преобразование всегда выполняется для строковых констант и строк, содержащих директивы препроцессора, так как там этот подход применим в полной мере. Стандарт С и многие реализации С не отве- чающие стандарту, распространяют это преобразование на любую строку ис- ходного текста программы. Такое объединение строк исходной программы вы- полняется перед препроцессированием и лексическим анализом исходной про- граммы, но уже после обработки триграфов и преобразования многобайтных символьных последовательностей к исходному набору символов. Пример В Standard С на несколько строк могут быть разбиты даже лексемы. Две строки: if (a==b) х=1; е!\ se х=2; эквивалентны строке if (а==Ь) х=1; else х=2; Если реализация трактует любой нестандартный исходный символ как сим- вол-разделитель или как символ-разделитель строк, то она должна опериро- вать ими, соответственно, как пробельными символами или маркерами конца строки. Для этих целей Standard С предлагает выполнение реализацией транс- ляции этих символов к некоторому каноническому виду на самом раннем эта- пе обработки исходной программы. Однако программисты должны относиться к подобным преобразованиям с осторожностью и не следует рассчитывать на то, что, к примеру, символ обратной косой черты будет устранен, если он будет находиться за символом перевода страницы. Большинство реализаций С устанавливают предельную длину строки ис- ходного кода, как до объединения, так и после него. С89 ограничивает макси- мальную длину логической строки 509 символами. С99 расширяет этот предел до 4095 символов. Ссылки: символьные константы 2 7,3; лексические соглашения препроцессора 3 2; строко- вые константы 2.7.4; лексемы (токены) 2 3, триграфы 2.1.4
Лексика языка 29 2.1.3 Кодирование символов Каждый символ в исполняемом наборе имеет свое числовое представление, т.е. является определенным образом закодированным. Так как в языке С воз- можно преобразование символов в целые числа, то это кодирование важно для того, чтобы и целые числа имели условное соответствие символам. Все стан- дартные символы, перечисленные ранее, должны иметь уникальные, неотри- цательные целые представления. Большая часть ошибок при программировании на С связана именно с ис- пользованием кодировок. Пример Возьмем выражение ’Z’ - 'А' + 1 результатом которого должна быть разность между значениями кодов символов А И Z увеличенная на один. Можно предположить, что полученное число будет количеством букв в английском алфавите. На самом же деле наше предположение будет правильным только для кодировки ASCII, где это выра- жение будет действительно иметь значение 26. В кодировке же EBCDIC буквы алфа- вита расположены непоследовательно и результат соответственно будет равен 41. Ссылки: исходный и исполняемый наборы символов 2.1.1 2.1.4 Триграфы С целью использования программистами для написания программ единой кодировки ISO 646-1083 Invariant Code Set в Standard С был включен набор триграфов (trigraph). Кодировка ISO 646-1083 Invariant Code Set представляет собой подмножество семибитной кодировки ASCII. Она также содержит набор символов, который является общим для национальных алфавитов многих не- англоязычных стран. Триграф является трехсимвольной последовательно- стью, первые два символа которой являются знаками вопроса. Определенные стандартом С триграфы приведены в таблице 2.2. Стандарт определяет и новое правописание для некоторых лексем (см. раздел 2.4), а заголовочный файл <iso646.h> содержит макросы для альтернативного написания некоторых опе- раторов, но в отличие от триграфов эти альтернативы не будут распознанными в строках и символьных константах. Таблица 2.2. Триграфы ISO Триграф Заменяет Триграф Заменяет ??( [ ??) 1 п< { ??> ) К/ \ ??! 1 ??' ??- - ??= # Обработка триграфов в исходной программе осуществляется перед лексиче- ским анализом и до распознавания управляющих последовательностей в стро- ках и символьных константах. Обрабатываются только девять триграфов, при- веденных выше в таблице. Все остальные последовательности символов (на-
30 Глава 2 пример ??&) остаются неизменными. Добавлена также новая управляющая последовательность \?, которая предотвращает интерпретацию символьных последовательностей, совпадающих с триграфами. Пример Если вы хотите, чтобы строка содержала трехсимвольную последовательность, ко- торая обычно интерпретируется как триграф, то для этого надо отделить обратной косой чертой как минимум один из символов триграфа. К примеру, строковая кон- станта "What?\?!” будет представлять строку, содержащую What??!. Для того чтобы записать строковую константу, содержащую один символ обратной косой черты, необходимо записать последовательно два символа обратной косой черты. (Первый символ разрешает второй). Так как каждый символ обратной косой черты может быть представлен своим триграфом, то представлением строковой кон- станты "??/??/" и будет строка, содержащая единственный символ обратной косой черты (\). Ссылки: алфавит 2.1; управляющие символы 2 7.5; iso646.h 1 1.9: конкатенация строк 2.7.4; правописание лексем 2.4 2.1.5 Многобайтные и широкие символы С целью использования в С неанглийских алфавитов, которые могут содер- жать большое количество символов, в Standard С включены широкие символы (wide character) и широкие строки (wide string). Для их представления во внешнем байт-ориентированном мире используется концепция многобайтных символов. Дополнение 1 к С89 расширяет возможности обработки широких и многобайтных символов. Широкие символы и широкие строки. Широкий символ (wide character) является двоичным представлением элемента расширенного набора символов (extended character set). Он относится к целому типу wchar_t, который опреде- лен в заголовочном файле stddef.h. В Дополнении 1 к Standard С89 определен целый тип wint_t, который может представить все значения типа wchar_t и одно дополнительное особое значение неширокого символа, обозначенное WEOF. Standard С не определяет кодировок для представления широких сим- волов, но резервирует значение ноль, как «нулевой широкий символ». Широ- кие символьные константы формируются с помощью специального синтаксиса констант, описанного в разделе 2.7.3. Пример Обычно широкие символы занимают 16 бит. Поэтому на 32-битном компьютере они могут быть представлены типами short или unsigned short. Если wchar_t представ- лен типом short, а значение 1 не является допустимым широким символом, то wint_t может представляться типом short, так как константа WEOF может иметь числовое значение -1. Однако более типичным представлением wint_t является тип int или unsigned int. Если принято решение не поддерживать расширенный набор символов (такой под- ход характерен для поставщиков в США), то wchar_t можно определить как char, а расширенный набор символов как совпадающий с обычным.
Введение в компьютеры, Internet и World Wide Web 31 Широкая строка (wide string) — это непрерывная последовательность ши- роких символов, заканчивающаяся нулевым широким символом. Нулевой ши- рокий символ (null wide character) — это широкий символ, числовое представ- ление которого равно нулю. За исключением нулевого широкого символа и константы WEOF Standard С ни коим образом не регламентирует способ ко- дирования расширенного набора символов. Широкие строковые константы оп- ределяются с помощью специальных строковых констант (раздел 2.7.4). Многобайтные символы. Широкие символы можно обрабатывать как еди- ницы информации внутри программы, но большинство внешних информаци- онных представлений (к примеру файлы), как и исходные программы на С, ориентированы на байтное кодирование информации. Поэтому программиста- ми для расширенного набора символов было разработано многобайтное коди- рование (multibyte encoding), которое устанавливает соответствие между по- следовательностями однобайтных символов и широкими символами. Многобайтный символ (multibyte character) является представлением ши- рокого символа, как в исходном, так и в расширенном наборе символов. (Для каждого набора может быть свое кодирование). Многобайтная строка (multi- byte string) — в понятиях С это обычная строка, символы которой могут быть проинтерпретированы как последовательность многобайтных символов. Вид многобайтных символов и их отображение на набор широких символов опреде- ляются реализацией. Это отображение реализуется преобразованием времени компиляции. В стандартной библиотеке также есть функции, которые выпол- няют это преобразование во время выполнения программы. Многобайтные символы могут использовать кодирование, зависимое от со- стояния (state-dependent encoding), где интерпретация текущего символа за- висит от предыдущих многобайтных символов. Обычно такое кодирование подразумевает использование символа смены регистра (shift character) — управляющего символа, который является частью многобайтного символа и модифицируют его интерпретацию и интерпретацию символов, следующих за ним. Текущая интерпретация последовательности многобайтных символов называется состоянием перехода (или состоянием регистра) кодирования (conversion state, shift state). В начале преобразования последовательности многобайтных символов используется так называемое начальное (initial) со- стояние преобразования. Часто к нему же возвращаются после завершения преобразования. Пример В примерах мы будем использовать кодировку А. Это гипотетическая кодировка, за- висимая от состояния, с двумя состояниями регистра — «верх» и «низ». Символ Т меняет состояние регистра на положение «верх», а символ 4 на состояние «низ». В нижнем регистре, который является начальным состоянием, все символы интер- претируются обычным образом. В верхнем регистре каждый многобайтный символ состоит из пары буквенно-цифровых символов, которые определяют широкий сим- вол некоторым способом. Каждая из приведенных ниже последовательностей сим- волов состоит из трех широких символов, представленных в кодировке А. Каждая последовательность кодируется с начального состояния преобразования. аЬс аьТеЗ TabtbT23 tatbtc Последняя строка содержит управляющие символы, но в данном случае они не яв- ляются обязательными. Если подобные избыточные последовательности управляю- щих символов считать разрешенными, то многобайтные символы могут быть произ-
32 Глава 2 вольно длинными (например Если вы не знаете, какое состояние регистра является начальным для последовательности многобайтных символов, то невозмож- но проинтерпретировать, например, такую последовательность как abcdef, так как она может представлять как три, так и шесть широких символов. В кодировке А последовательность ab|?x недопустима, так как все символы, кото- рые не Относятся к буквенно-цифровым, должны быть представлены в верхнем ре- гистре. Последовательность a|b также неверна, так как последний многобайтный символ неполон. Многобайтные символы могут также использовать кодирование, независи- мое от состояния (state-independent encoding). В этом способе кодирования интерпретация многобайтного символа не зависит от предыдущих символов. (Хотя для того чтобы найти начало символа, который находится в середине строки, все равно придется просматривать строку с начала.) К примеру, син- таксис управляющих символов (раздел 2.7.5) представляет образец кодирова- ния, независимого от состояния, для типа char. В нем символ обратной косой черты (\) меняет представление одного или более следующих за ним символов к виду одного значения типа char. Пример Кодировка В — это еще одна гипотетическая кодировка, которая в отличие от коди- ровки А является независимой от состояния. В ней используется один управляю- щий символ, который обозначается как V и который меняет интерпретацию одного ненулевого символа, стоящего за ним. Ниже представлены последовательности сим- волов, каждая из которых содержит три многобайтных символа, представленных в кодировке В: abc VaVbVc WWW a Vbc Последовательность VVV является недопустимой в кодировке В, так как в конце по- следовательности отсутствует ненулевой символ. Standard С накладывает некоторые ограничения на многобайтные символы: 1. В кодировке должны быть представлены все символы из стандартного набора символов. 2. В начальном состоянии регистра все однобайтные символы из стандарт- ного набора символов сохраняют свое обычное представление и не влия- ют на состояние регистра. 3. Независимо от состояния регистра байт, содержащий все нули, являет- ся нулевым символом. Ни один многобайтный символ не может исполь- зовать в качестве второго и последующего символа (байта) нулевой байт. Эти правила гарантируют, что многобайтные последовательности будут об- рабатываться как обычные строки (к примеру, они не будут содержать нуле- вые символы), а обычные строки без специальных многобайтных кодов будут правильно интерпретироваться и как многобайтные последовательности. Использование многобайтных символов в исходном и исполняемом набо- рах символов. Многобайтные символы могут использоваться в комментариях, идентификаторах, в именах заголовочных файлов команд препроцессора, в строковых и символьных константах. Каждый комментарий, идентифика-
Лексика языка 33 тор, имя заголовочного файла, строковая или символьная константа, содержа- щая многобайтную последовательность, должна начинаться и заканчиваться начальным состоянием регистра и содержать только допустимую последова- тельность многобайтных символов. Многобайтные символы, составляющие ис- ходный текст программы, распознаются и транслируются в исходный набор символов на самом начальном этапе — перед лексическим разбором, перед препроцессированием и даже перед объединением логических строк. Пример Японская программа редактирования может разрешать использование японских символов в комментариях и строковых константах. Если исходный текст будет за- писан в файл последовательностью байт, то символы японской кодировки будут со- хранены многобайтными последовательностями, которые будут понятны реализа- циям, отвечающим Standard С. Во время обработки символы в строковых и символьных константах транс- лируются в исполняемый набор символов еще до их интерпретации в качестве многобайтных последовательностей. Следовательно, для формирования много- байтных символов могут использоваться и управляющие последовательности (см. раздел 2.7.5). Использование управляющих последовательностей в комментари- ях нецелесообразно, так как комментарии удаляются еще до этого этапа. Пример Предположим, что исходный и исполняемый наборы символов одинаковы. Тогда, если символ 'а' в исполняемом наборе имеет значение 141g, то строковая константа "Vaa” будет содержать два одинаковых многобайтных символа "V\141\141” (в ко- дировке В). Ссылки: строковые константы 2.7.3; комментарии 2.2; возможности преобразования для мнагобайтных символов 11.7, 11.8; строковые константы 2.7.4; wchar_t 11.1; WEOF 11.1; ши- рокие символы 2.7.3; широкие строки 2.7.4; wint_t 11.1. 2.2 Комментарии Standard С описывает два способа задания комментариев. Традиционно, комментарий в С открывается символами /* и закрывается обратной последо- вательностью */. Комментарии могут содержать любое количество символов и всегда интерпретируются как символы-разделители (пробельные символы). Начиная со Standard С99 стало возможным создавать комментарии, кото- рые начинаются двумя символами обратной косой черты (//). Такой коммен- тарий включает в себя все символы, которые стоят за //, но до появления пер- вого символа-разделителя строк (не включая его). Маловероятно, но все-таки возможно, что применение этого вида комментария в старых программах при- ведет к возникновению ошибок. Рассмотрение этого случая оставлено в каче- стве упражнения. Комментарии не распознаются в символьных или строковых константах, а также внутри других комментариев. Реализации С не анализируют содержи- мое комментариев, за исключением того, что выполняется распознавание
34 Глава 2 (и пропуск) в комментариях многобайтных символов и символов-разделителей строк. Пример Приведенная ниже программа содержит четыре правильных комментария: // Программа для вычисления квадратов // первых 10 целых чисел #include <stdio.h> void Squares( /* без аргументов */ ) { Цикл 1 до 10, который печатает значения квадратов чисел */ for (i=l; i <= 10; i++) printf("%d //squared// is %d\n", i, i * i) ; } Так как комментарии удаляются компилятором еще до этапа препроцесси- рования, директивы препроцессора внутри комментариев не распознаются, а символы-разделители строк внутри комментариев не повлияют на правиль- ность интерпретации препроцессорных директив. Пример Две следующие директивы #define эквивалентны: #define ten (2*5) #define ten /* десять : на один больше чем девять */ (2 * 5) Standard С устанавливает, что с целью дальнейшей трансляции программы реализации С должны заменять комментарии одним символом пробела. Неко- торые старые реализации не вставляют пробельный символ, что влияет на по- ведение препроцессора. Этот вопрос обсуждается в разделе 3.3.9. В некоторых реализациях С, не отвечающих Standard С, используются так называемые «вложенные комментарии», в которых каждое появление внутри комментария последовательности символов /* должно быть сбалансировано последующими символами */. Подобные комментарии не отвечают стандарту, и программисты не должны их использовать. Для того чтобы программа отве- чала и такому типу комментариев, она не должна содержать комментарии, внутри которых присутствует последовательность символов /*. Пример Если необходимо, чтобы компилятор проигнорировал большой фрагмент кода про- граммы, то рекомендуется выполнить обрамление этого участка командами препро- цессора: #if 0 #endif
Лексика языка 35 Такой подход предпочтительнее вставки символов /* перед и символов */ после игно- рируемого фрагмента. Это позволяет избежать появления вложенных комментариев. Ссылки: директива препроцессора #if 3.5.1; лексические соглашения для препроцессора 3.2; символы-разделители (пробельные символы) 2.1. 2.3 Лексемы (токены) Символы, составляющие программу на С, собраны в лексические структу- ры (лексемы, токены — token) согласно правилам, описанным в этой главе. Существует пять типов лексем: операции (operator), разделители (separator), идентификаторы (identifier), ключевые слова (keyword) и константы (cons- tant). Компилятор всегда формирует лексемы наибольшей возможной длины, об- рабатывая символы слева направо, даже если результатом будет неработоспо- собная программа. Соседние лексемы могут быть разделены символами-разде- лителями или комментариями. Во избежание неоднозначностей, идентифика- тор, ключевое слово, целая константа или константа с плавающей точкой всегда должна быть отделена от следующего идентификатора, ключевого сло- ва, целой константы или константы с плавающей точкой. Для препроцессора понятие лексемы имеет несколько иную трактовку. В частности, препроцессор Standard С определяет # и ## как лексемы, в то время как в традиционном С это не так. Пример Символы Лексемы С forwhile forwhile Ь>х Ь, >, х Ь->х Ь, х Ь—х Ь, х Ь—х Ь, х Последовательность символов Ь—х, в четвертом примере, не является правильной синтаксической структурой. Разбиение на лексемы Ь, -, -, х синтаксически правиль- но, но такой способ разбиения не разрешен. Ссылки: комментарии 2.2; константы 2.7; идентификаторы 2.5; лексемы препроцессора 3.2; ключевые слова 2.6; объединение лексем 3.3.9; симвалы-розделители 2.1. 2.4 Операции и разделители Полный список обозначений операций и разделителей (пунктуаторов) язы- ка С приведен в таблице 2.3. Для того чтобы программисты могли использо- вать устройства ввода/вывода, на которых отсутствуют некоторые английские символы (набора символов U.S. — English) для пунктуаторов {, },[,], #, ##
36 Глава 2 введены соответствующие альтернативные написания <%, %>, <:, :>, %: и %:%:. В дополнение к этим альтернативам заголовочный файл iso646.h со- держит макросы, действие которых распространяется на некоторые операции. Таблица 2.3. Операции и разделители Класс лексем Простые операции Составные операции присваивания Другие составные операции Разделители Альтернативное написание лексем Лексемы ! % ~ | < >/? += .= *= /= %= «= »= &= "= |= -> ++ .. « » <= >= = = 1= && || ()[]{},;:... <% %> <: :> %: %:%: Обозначения составных операций присваивания в традиционном С счита- лись состоящими из двух различных лексем — знака операции и символа при- сваивания, которые могли быть разделены символом-разделителем. Standard С определяет знак составной операции, как одну лексему. Ссылки: составные операции присваивания 7.9.2; заголовочный файл iso646.h 1 1.9; лексе- мы препроцессора 3.2; триграфы 2.1.4. 2.5 Идентификаторы Идентификатором или именем (пате) называется последовательность за- главных и строчных букв латиницы, цифр и символов подчеркивания (LOW- LINE). Идентификатор не должен начинаться с цифры и не должен иметь та- кое же написание как ключевые слова. Начиная со Standard С99 идентификаторы могут также содержать универ- сальные символьные имена (раздел 2.9) и другие многобайтные символы, оп- ределяемые реализацией. Идентификаторы не должны начинаться с универ- сальных символов, которые отображают цифры. Допустимо использование в идентификаторах только символов, написание которых похоже на буквы, и не являющихся разделителями. Полный список приведен в Standard С99 (ISO/IEC 9899:1999, Приложение D) и в ISO/IEC TR 10176-1998. идентификатор: идентификатор-не-цифра идентификатор идентификатор-не цифра идентификатор цифра идентификатор-не-цифра: не-цифра универсалъное-еимволъное имя и другие символы, определяемые реализацией не-цифра: одна из А В С D Е N О Р Q R а Ь с d е п о р q г FGHIJKLM STUVWXYZ fghijklm s tuvwxyz
Лексика языка 37 цифра: одна из 0123456789 Два идентификатора считаются идентичными, если одинаково их написа- ние, включая регистр букв. Поэтому идентификаторы abc и аВс считаются различными. Программисты должны избегать использования в качестве идентификато- ров не только ключевых слов, но и имен, задействованных в стандартной биб- лиотеке, причем, как библиотеке текущего стандарта, так и ее части, относя- щейся к «рекомендациям относительно будущей стандартной библиотеки» (future library directions). Standard С резервирует все идентификаторы, кото- рые начинаются с символа подчеркивания и следующего за ним буквы в верх- нем регистре или двух символов подчеркивания, поэтому программистам сле- дует избегать создания подобных идентификаторов. Некоторые реализации С иногда используют такие идентификаторы для расширений Standard С или для внутренних целей. Standard С89 ограничивает максимальную длину идентификаторов 31 зна- чащим символом. С99 увеличивает это значение до 63 значащих символов. В данном контексте каждое универсальное символьное имя или многобайтная последовательность считаются одиночными символами. Пример Реализация С, созданная до появления стандарта ограничивает длину идентифика- торов восемью значащими символами, поэтому в ней идентификаторы countless и countlessone считаются одним и тем же идентификатором. Более длинные иден- тификаторы увеличивают ясность программы и тем самым уменьшают потенциаль- ное количество ошибок. Использование символов подчеркивания и разных регист- ров букв делает длинные идентификаторы более читабельными: averylongidentifier AVeryLongldentifier a_very_long_identifier Внешние идентификаторы — это идентификаторы, объявленные с классом памяти extecn. На этот тип идентификаторов могут накладываться более же- сткие ограничения, так как они обрабатываются другими программами, таки- ми как компоновщики (редакторы связей) и отладчики, которые могут обла- дать ограниченными возможностями. Standard С89 регламентирует предель- ную длину идентификатора шестью значащими символами без учета регистра букв. Standard С99 увеличивает предельную длину до 31 символа и делает зна- чимым регистр букв, но каждое универсальное символьное имя занимает шесть символов (до значения \UOOOOFFFF) или 10 символов (от \U00010000 и далее). Но даже до появления Standard С99 большинство реализаций поддер- живали для внешних имен длину, как минимум, в 31 значащий символ. Пример Если целевой компьютер требует использования коротких внешних имен, а компи- лятор С разрешает длинные внутренние идентификаторы, то для предотвращения последствий такой несогласованности можно использовать директивы препроцессо- ра. В приведенном ниже исходном тексте показано, как на функцию с малопонят- ным названием eh73 можно ссылаться с помощью более удобного идентификатора
38 Глава 2 error_handler. Это делается с помощью макроопределения, которое заменяет иден- тификатор error_handler на eh73: #define error_handler eh73 extern void error_handler(); error_handler("nil pointer error"); Отдельные компиляторы позволяют использовать для идентификаторов не только те символы, которые были определены выше, но и некоторые другие. Для доступа к специальным библиотечным функциям, которые были написа- ны не на С, разрешенным для идентификаторов часто является символ долла- ра ($). Ссылки: директива #define 3,3; внешние имена 4.2.9; ключевые слова 2.6; многобайтные последовательности 2.1.5; зарезервированные библиотечные идентификаторы 10.1.1; универ- сальные символьные имена 2.9. 2.6 Ключевые слова Идентификаторы, перечисленные в таблице 2.4, являются ключевыми сло- вами, определенными Standard С. Ключевые слова не должны использоваться как обычные идентификаторы. Ключевые слова могут использоваться в мак- роопределениях, так как препроцессирование выполняется до этапа распозна- вания ключевых слов. Ключевые слова _Bool, _Complex, —Imaginary, inline и restrict впервые появились в С99. Таблица 2.4. Ключевые слова Standard С99 auto _Bool’ break case char -Complex’ const continue default restrict’ do double else enum extern float for goto if Imaginary inline int long register return short signed sizeof static struct switch typedef union unsigned void volatile while ’ Эти ключевые слова введены в Standard С99 и не являются зарезервированными в C++. В дополнение к перечисленным выше, распространенными расширениями языка являются идентификаторы asm и fortran. Макросы, определенные в за- головочном файле iso646.h программисты также могут трактовать как ключе- вые слова (and, and_eq, bitand, bitor, compl, not, not_eq, or, or_eq, xor и xor_eq). Эти идентификаторы зарезервированы в C++.
Лексика языка 39 Пример Приведенный ниже исходный код является одним из немногих случаев, когда удоб- но использование макроопределения с именем, совпадающим с ключевым словом. Такое макроопределение позволяет использовать тип void в программе, обрабаты- ваемой компилятором, не отвечающим Standard С. #ifndef __STDC_ #define void int #endif Ссылки: _Boal 5.1.5; ключевые слова C++ 2.8; -Complex 5.2.1; директива # define 3.3; идентификаторы 2.5; директива #ifndef 3.5; ключевое слово inline 9.10; заголовочный файл <iso646.h> 1 1.5; ключевое слова restrict 4.4.6;_STDC_11.3; спецификатор типа void 5.9. 2.6.1 Предопределенные идентификаторы Предопределенный идентификатор (predefined identifier) — это новое по- нятие, введенное в Standard С99. Стандарт описывает один предопределенный идентификатор: _func__. В отличие от предопределенных макросов, предо- пределенные идентификаторы подчиняются правилам блочной области види- мости. Как и ключевые слова, предопределенные идентификаторы не должны использоваться программистами как обычные идентификаторы (т.е. не долж- ны описываться в программе). Реализации, поддерживающие Standard С99, неявно объявляют этот иден- тификатор так, как если бы нижеследующее объявление находилось после от- крывающей фигурной скобки каждого определения функции: static const char _func_[] = "function-name"; Этот идентификатор удобно использовать средствами отладки для вывода имени функции, на которой завершилось выполнение программы: if(failed) printf("Function %s failed\n", _func_); Реализация С должна уметь транслировать программу без добавления этих строк, в случае, если к ней предъявлены жесткие требования по использова- нию памяти и эти строки не нужны во время выполнения программы. Ссылки: определение функции 9.1; предопределенные макросы 3.3.4; область видимо- сти 4.2.1 2.7 Константы Лексический класс констант состоит из четырех типов: целые числа, числа с плавающей точкой, символы и строки: константа: целая-константа константа-с-плавающей-точкой символъная-константа строчная-коне тан та В некоторых языках для отделения констант от объектов, чьи значения представляют собой константы (т.е. не меняются), к первым применяют тер-
40 Глава 2 мин литеральная константа или литерал (literal), хотя оба эти типа объек- тов относятся к одному лексическому классу. Примером объектов литерально- го типа в С может послужить константа перечисления, которая относится к лексическому классу идентификаторов. В этой книге использована традици- онная терминология, согласно которой термин константа применяется для обоих случаев. Каждая константа характеризируется типом и значением. Форматы раз- личных типов констант приведены в следующих разделах. Ссылки: символьные константы 2.7.3; константы перечисления 5.5; константы с плавающей точкой 2.7.2; целые константы 2.7.1; строковые константы 2.7.4; лексемы 2.3; значения 7.3. 2.7.1 Целые константы Целые константы (integer constant) могут быть определены в десятичной, восьмеричной или шестнадцатеричной системах счисления: целая-константа: десятичная-константа суффикс-целогоопц восьмеричная-константа суффикс-целогоопц шестнадцатеричная-константа суффикс-целогоогщ десятичная-константа: ненулевая-цифра десятичная-константа цифра восьмеричная-константа: 0 восьмеричная-константа восьмеричная-цифра шестнадцатеричная-константа: Ох шестнадцатеричная-цифра Ох шестнадцатеричная-цифра шестнадцатеричная-константа шестнадцатеричная-цифра цифра: одна из 0123456789 ненулевая-цифра: одна из 123456789 восьмеричная-цифра: одна из 01234567 шестнадцатеричная-цифра: одна из 0123456789 ABCDEFabcdef суффикс-целого: суффикс-длинного суффикс-беззнаковогоопц суффикс-длинного-длинного суффикс-беззнаковогоопц (С99) суффикс-беззнакового суффикс-длинногоопц суффикс-беззнакового суффикс-длинного-длинногоопц (С99) суффикс-длинного: один из 1 L
Лексика языка 41 суффикс-длинного-длинного: один из (С99) 11 LL суффикс-беззнакового: один из u U Для определения основания системы счисления целой константы существу- ет последовательность правил: 1. Если целая константа начинается с литер Ох или ОХ, то эта константа представлена в шестнадцатеричной системе счисления, причем буквы от а до f (или от А до F) представляют цифры от 10 до 15. 2. Иначе, если константа начинается с цифры 0, то она представлена в восьмеричной системе счисления. 3. Иначе она представлена в десятичной системе счисления. Константа может заканчиваться буквами суффикса, которые определяют тип этой константы. Это позволяет константе занимать минимальное количе- ство памяти. Используются следующие суффиксы: • для константы типа long (длинная) буква 1 или L; • для константы типа long long (длинная-длинная) буквы 11 или LL (С99); • для константы беззнакового типа (unsigned) буква и или U (int, long или long long). Суффикс беззнакового типа unsigned можно комбинировать с суффиксами типа long и long long в любом порядке. В нижнем регистре буква 1 может быть легко спутана с цифрой 1, поэтому в суффиксах следует избегать ее использо- вания. При отсутствии переполнения, значение целой константы всегда неотрица- тельно. Если перед константой стоит знак минуса, то это означает, что к кон- станте применена унарная операция минуса, не являющаяся частью константы. Фактический тип целой константы зависит от ее размера, системы счисле- ния, литер суффикса и способа представления, выбранного для данной целой константы самой реализацией С. Правила определения типа константы доста- точно сложны и различны для достандартного С, С89 и С99. Все правила при- ведены в таблице 2.5. Если значение целой константы превышает наибольшее значение, которое может быть представлено последним типом, перечисленным в соответствуй^ щей группе таблицы 2.5, то результат не определен. В С99 в таком случае большая константа может быть представлена расширенным целым типом, со- гласно беззнаковым соглашениям, приведенным в таблице. (Если все стан- дартные варианты знаковые, то и расширенный тип должен быть знаковым, и наоборот. Иначе, подходят оба варианта.) В Standard С89 информация, ка- сающаяся представления целых типов, содержится в заголовочном файле limits.h. В Standard С99 заголовочные файлы stdint.h и inttypes.h содержат дополнительную информацию о целых типах. Для иллюстрации некоторых тонкостей целых констант допустим, что тип int использует 16-битное представление в дополнительном коде, тип long ис- пользует 32-битное представление в дополнительном коде, а тип long long — 64-битное представление в дополнительном коде. В таблице 2.6 перечислены некоторые интересные целые константы, их действительные математические значения, их типы — традиционный и по правилам Standard С, а также их фактическое представление, используемое для хранения этих констант.
42 Глава 2 В этой таблице можно заметить интересное свойство целых чисел, которые находятся в диапазоне от 215 до 216-1. Эти числа будут иметь положительные значения, если будут представлены десятичными константами, но отрицатель- ные, в случае восьмеричных или шестнадцатеричных (после приведения к типу int). Несмотря на подобные аномалии, программисты редко бывают удивлены значениями целых констант, так как независимо от их типа, их представление одинаково. С помощью макросов INTN_C, UINTN_C, INTMAX_C, UINTMAX_C, опре- деленных в заголовочном файле stdint.h, Standard С99 обеспечивает некото- рый машинонезависимый контроль размеров и типов целых констант. Пример Если тип long имеет 32-битное представление в дополнительном коде, то следую- щая программа иллюстрирует действие правил: #define К OxFFFFFFFF /* -1 в 32-битном дополнительном коде */ #include <stdio.h> int main() { if (0 < К) printff'K - беззнаковое число (Standard C)\n"); else printffK - знаковое число (традиционный С)\п"); return 0; } Таблица 2.5. Типы целых коиставт Константа Традиционный C1 C89’ C991 2 dd...d Int long int long unsigned long int long long long Odd...d OXdd.d unsigned long int unsigned long unsigned long int unsigned long unsigned long long long unsigned long long dd..dU Odd.dU OXdd.dU не применяется unsigned unsigned long unsigned int unsigned long unsigned long long dd...d\. long long unsigned long long long long Odd.dl OXdd...dl long long unsigned long long unsigned long long long unsigned long long dd..dUL не применяется unsigned long unsigned long
Лексика языка 43 Константа Традиционный C1 С89’ С99' 2 OcfcLdUl OXcfcLdUl unsigned long long dd...dU, не применяется не применяется long long Odd..dll Wdd..dU. не применяется не применяется long long unsigned long long dd..dlMl не применяется не применяется unsigned long long OcfcLdUU. OXc/rf..c/ULL 1 Укозанный тип является наименьшим из типов, способных представить значение константы без возникновения переполнения. 2 Если ни один из перечисленных типов не обладает достаточным размером для представления константы, то для ее представления может быть выбран расширенный тил (если он доступен). Таблица 2.6. Присвоение типов целым константам Представление константы в С Действительное значение Тип традици- онного С Тип Standard С Фактическое представление 0 0 int int 0 32767 215 - 1 int int 0x7FFF 077777 215 - 1 unsigned int 0x7FFF 32768 2’5 long long 0x00008000 0100000 2’5 unsigned unsigned 0x8000 65535 2i6 _ ] long long OxOOOOFFFF OxFFFF 2i6 _ ] unsigned unsigned OxFFFF 65536 216 long long 0x00010000 0x10000 216 long long 0x00010000 2147483647 231 _ ] long long 0x7FFFFFFF 0x7FFFFFFF 23! _ ] long long 0x7FFFFFFF 2147483648 231 long’ unsigned long 0x80000000 C99: long long 0x80000000 231 long' unsigned long 0x80000000 4294967295 232 _ ] long’ unsigned long OxFFFFFFFF C99: long long OxOOOOOOOOFFFFFFFF OxFFFFFFFF 232 _ ! long1 unsigned long OxFFFFFFFF 4294967296 232 не определен не определен 0x0 C99: long long 0x0000000100000000 0x100000000 232 не определен не определен 0x0 C99: long long 0x0000000100000000 1 Данный тип не мажет представить заданное значение точно Ссылки: преобразование целых типов 6.2.3; расширенные целые типы 5.1.4; целые типы 5.1; INTMAX_C 21 5; INTN_C 213; limits.h 5 11 ; переполнение 7 2.2 stdint.h гл. 21; унарный оператор минус 7 5.3; беззнаковые целые 5.1.2.
44 Глава 2 2.7.2 Константы с плавающей точкой Константы с плавающей точкой записываются с десятичной точкой или с экспоненциальной частью или и с тем и с другим. Для выделения констант типов float и long double Standard С определяет использование букв суффик- са. При отсутствии суффикса тип константы определяется как double: константа-с-плавающей-точкой: десятичная-константа-с-плавающей-точкой шестнадцатеричная-константа-с-плавающей-точкой десятичная-константа-с-плавающей-точкой: последователъностъ-цифр экспонента суффикс-плавающегоопц цифры-с-точкой экспонента0ПЦ суффикс-плавающегоогщ последователъностъ-цифр: цифра последователъностъ-цифр цифра цифры-с-точкой: последователъностъ-цифр . последователъностъ-цифр . последователъностъ-цифр . последователъностъ-цифр цифра: одна из 0123456789 экспонента: е знаковая-частъопц последователъностъ-цифр Е знаковая-частъопц последователъностъ-цифр знаковая-часть: один из + суффикс-плавающего: один из f F 1 L При отсутствии переполнения значение константы с плавающей точкой всегда неотрицательно. Если перед константой стоит знак минуса, то это озна- чает, что к константе применена унарная операция минуса. Если реализация не может представить константу с плавающей точкой точно, то она может вы- брать значение наиболее близкое к представляемому числу в окрестности чис- ла, которое она может представить. Если величина константы с плавающей точкой больше наибольшей или меньше наименьшей величины, которая мо- жет быть представлена реализацией, то результат непредсказуем. В таких слу- чаях некоторые компиляторы сообщают о проблеме программисту, но боль- шая часть заменит это число на значение, которое можно представить, без пре- дупреждения. В Standard С предельные значения для чисел с плавающей точкой записаны в заголовочном файле float.h. Специальные константы с пла- вающей точкой, такие как бесконечность и не-число (NaN — not a number) оп- ределены в заголовочном файле math.h. В Standard С99 комплексная константа с плавающей точкой определяется как константное выражение с плавающей точкой, включающее мнимую кон- станту _Complex_I (или I), определенную в заголовочном файле complex.h.
Лексика языка 45 Пример Вот примеры констант с плавающей точкой: 0., Зе1, 3.14159, .0, 1.0Е-3, 1е-3, 1.0, .00034, 2е+9. В Standard С допустимы также такие константы с плавающей точкой: l.Of, 1.0e67L, 0E1L. Пример комплексной константы: 1.0 + 1.0 * I (при включенном заголовочном файле complex, h). В отличие от предыдущих версий С, Standard С99 позволяет записывать константы с плавающей точкой в шестнадцатеричной системе счисления. Для разделения мантиссы от показателя степени в шестнадцатеричном формате используется буква р. Это сделано потому, что буква е может быть спутана с шестнадцатеричной цифрой. Бинарная экспонента — это знаковое десятич- ное число, которое представляет степень числа 2 (а не степень числа 10, как в случае с десятичной константой с плавающей точкой, и не степень числа 16, как может показаться). шестнадцатеричная-константа-с-плавающей-точкой: ( С99 ) шестнадцатеричный-префикс шестнадцатеричные-цифры-с-точкой бинарная-экспонента суффикс-плавающегоопц шестнадцатеричный-префикс последовательность- шестнадцатеричных-цифр бинарная-экспонента суффикс-плавающегоопц шестнадцатеричный-префикс: 0х 0Х шестнадцатеричные-цифры-с-точкой: последовательностъ-шестнадцатеричных-цифр . последовательность-шестнадцатеричных-цифр . последовательность- шестнадцатеричных-цифр . последовательность-шестнадцатеричных-цифр последователъностъ-шестнадцатеричных-цифр: шестнадцатеричная-цифра последовательность-шестнадцатеричных-цифр шестнадцатеричная-цифра бинарная-экспонента: р знаковая-частьОПц последовательность-цифр Р знаковая-частьопц последовательность-цифр Если FLT_RADIX (float.h) не равен 2, то возможно, что шестнадцатерич- ную константу нельзя будет представить точно. В таком случае она должна быть правильно округлена до ближайшего представимого числа. Ссылки: заголовочный файл complex.Н 23.2; тип double 5.2; заголовочный файл float.h 5.2; переполнение и потеря значимости 7.2.2; размеры типов с плавающей точкой 5.2; унар- ный оператор минуса 7.5.3. 2.7.3 Символьные константы Символьная константа (character constant) записывается с помощью за- ключения в апострофы одного и более символов. Для того чтобы записать сим- волы или их числовые значения, которые неудобно или невозможно записать
46 Глава 2 в исходной программе непосредственно, используется специальный механизм управляющих последовательностей. Для записи широких символьных кон- стант Standard С определяет использование префикса L. символьная константа: последовательность-исходных-символов ' L' последовательность-исходных-символов ' (С89) последовательностъ-исходных-символов: исходный-символ последовательность-исходных-символов исходный-символ исходный символ: любой символ из исходного набора символов за исключением символа апострофа ('), обратной косой черты (\) или символа новой строки управляющий-символ универсальное-символъное-имя ( С99 ) Символы апострофа, обратной косой черты и новой строки могут использо- ваться в символьных константах с помощью управляющих символов, как опи- сано в Разделе 2.7.5. Применение управляющих последовательностей являет- ся хорошим способом для отображения символов, которые плохо читаемы в исходной программе, таких как, например, символы форматирования. На- чиная со Standard С99, в символьных константах могут использоваться уни- версальные символьные имена (раздел 2.9). Символьные константы, записанные без префикса L, относятся к типу int. Обычно, такие символьные константы являются или единичными символами или управляющими символьными кодами (раздел 2.7.7). Их значение соответ- ствует символу из исполняемого набора. Результирующее значение символь- ной константы вычисляется так, если бы исходный объект преобразования от- носился к типу char. Например, если тип char представлен как восьмибито- вый знаковый тип, то символьная константа ’\377' будет иметь значение -1. Значение символьной константы определяет реализация, если: 1. в исполняемом наборе символов нет соответствующего символа; 2. в константе содержится более одного символа из исполняемого набора символов; 3. числовое значение, записанное после управляющего символа обратной косой черты, не представлено в исполняемом наборе символов. Пример Здесь представлены некоторые примеры односимвольных констант с их (десятич- ными) значениями в кодировке ASCII. Символ Значение Символ Значение а' 97 А’ 65 32 63 V 13 •\0' 0 «... 34 \377' 255 37 дгз1 19 '8' 56 •\\’ 92
Лексика языка 47 Широкие символьные константы, обозначаемые префиксом L, в Standard С относятся к типу wchar_t. Тип wchar_t определен в заголовочном файле stddef.h. Широкие символьные константы предназначены для представления символов, которые из-за размеров их алфавитов не могут быть представлены типом char (к примеру, японские иероглифы). Обычно, широкие символьные константы состоят из последовательности символов и управляющих кодов, ко- торые формируют один многобайтный символ. Преобразование многобайтных символов в соответствующий набор широких символов определяется в реали- зации функцией наподобие mbtowc, которая выполняет это преобразование на этапе выполнения программы. Если многобайтные символы используют коди- рование с зависимостью от состояния, то широкие символьные константы должны начинаться и заканчиваться в начальном состоянии. Если широкая символьная константа содержит в себе более одного широкого символа, то ее значение определяется реализацией. Многосимвольные константы. Целочисленные и широкие символьные константы могут содержать последовательности символов. После их преобра- зования к исполняемому набору символов такие константы могут также состо- ять из нескольких символов исполняемого набора. Значения таких констант определяется реализацией. Для согласования со старыми реализациями четырехбайтные целочислен- ные константы могут быть представлены четырехсимвольными константами, как например ’gR8t’. Применение четырехсимвольных последовательностей делает исходную программу непереносимой, так как некоторые реализации просто не разрешают их использовать, и различные реализации используют для этих констант разные размеры и разный порядок следования байтов (т.е. разные способы, которыми символы упаковываются в слова). Пример В кодировке ASCII при использовании четырехбайтных целых чисел с порядком байтов «слева направо» значение константы 'ABCD' будет 4142434416. (Значение А' — 0x41, В' — 0x42 и т.д.). Тогда как при упаковке справа налево, значение константы 'ABCD' будет равно 4443424116- Ссылки: Символы ASCII Прил. А; порядок байт 6.1.2; кодирование символов 2.1; тип char 5.1.3; управляющие символы 2.7.5; символы форматирования 2.1; функция mbtowc 1 1.7; мно- гобайтные символы 2.1.5; тип wchar_t 11,1. 2.7.4 Строковые константы Строковой константой (string constant) называется последовательность символов (возможно пустая), заключенная в кавычки (double qoutes). Для за- писи символов в строковой константе может использоваться тот же механизм управляющих последовательностей, что и для символьных констант. Для за- писи широких констант используется префикс L. строковая-константа: последователъностъисходных-з-символовопц L" последовательностъ-исходных-з-символовопц " (С89)
48 Глава 2 последователъность-исходных-з-символов: исходный-з-символ последовательность-исходных-з-символов исходный-з-символ исходный-з-символ: любой символ из исходного набора за исключением символов кавычек ("), обратной косой черты (\) и новой строки управляющий-символ универсальное-символъное-имя ( С99 ) Символы кавычек, обратной косой черты и новой строки могут быть вклю- чены в строковую константу с использованием управляющих символов, как описано в разделе 2.7.5. Желательно использовать управляющие последова- тельности для отображения символов, которые плохо читаемы в исходной про- грамме (например, символы форматирования). Standard С99 позволяет исполь- зовать в строковых константах универсальные символьные имена (раздел 2.9). Пример Ниже приведены 5 строковых констант. < \ I, и "Total expenditures:'" "Copyright 2000 \ Texas Instruments. " "Comments begin with ’/*'.\n" Четвертая строка эквивалентна строке "Copyright 2000 Texas Instruments. меж- ду символами 0 и T нет символа новой строки. Во время выполнения программы для каждой строковой константы разме- ром п символов будет статически выделен блок памяти величиной п+1 симво- лов, где первые п символов будут содержать саму строку, а n+1-й символ — символ с нулевым значением '\0'. Типом этого блока памяти будет массив char[n+l]. Аналогично, широкая строковая константа будет представлена как последовательность широких символов с завершающим нулевым символом. Для хранения такой строки будет использован массив wchar_t[n+l]. Пример В отличие от функции strlen (раздел 13.4), которая возвращает количество симво- лов в строке, оператор sizeof возвращает размер своего операнда. Поэтому size- of(”abcdef) имеет значение 7, а не 6, a sizeof("”) — 1, а не 0. В то время как strlen("abcdef") возвращает 6, a strlen("") — 0. При появлении строковой константы в любом месте программы выполняет- ся обычное преобразование массива, которое преобразует строку из массива символов в указатель на первый символ этой строки. Исключением из этого правила является использование строковой константы как аргумента для ад- ресной операции &, оператора sizeof или как инициализирующего значения для символьного массива.
Лексика языка 49 Пример Результатом объявления char *р = "abcdef" будет присвоение указателю р адреса блока памяти, содержащего 7 символов: 'а', 'Ъ', 'с', ’d', 'е', Т и ’\0' соответственно. Значения односимвольной строковой константы и символьной константы различ- ны. Например, объявление int X = (int) "А"; приводит к тому, что переменной X присваивается целочисленное значение указателя на двухсимвольный блок памяти, содержащий символы 'А' и '\0' (если такой указатель может быть представлен ти- пом int). В то же время объявление int Y = (int)'А'; инициализирует переменную Y значением символа 'А' (в кодировке ISO 646 — 0x41). Хранение строковых констант. Никогда не следует пытаться изменять об- ласть памяти, в которой находится строковая константа, так как эта область памяти может иметь атрибут «только для чтения». Некоторые функции (к примеру mktemp) получают в качестве аргументов указатели на строки, ко- торые они изменяют. Этим функциям не следует передавать строковые кон- станты. Вместо этого лучше инициализировать массив символов (без квалифи- катора const) строковой константой и передать в качестве аргумента указатель на первый элемент этого массива. Пример Рассмотрим три объявления: char pl[] = "Изменение допустимо"; char *р2 = "Возможно, изменение недопустимо"; const char рЗ[] = "Изменение недопустимо"; /* только Standard С */ Идентификаторы pl, р2 и рЗ являются указателями на символьные массивы. Их различие состоит в возможности модификации области памяти, на которую они указывают. Присвоение р1[0] = х' никогда не вызовет ошибки; р2[0] = х' может не вызвать ошибки, но может и привести к ошибке на этапе исполнения программы; рЗ[0] = 'х' обязательно вызовет ошибку на этапе компиляции программы из-за на- личия квалификатора const. Отдельные строковые константы не обязательно будут размещаться по раз- ным адресам. Standard С разрешает реализациям использовать один и тот же адрес для хранения одинаковых строковых констант. Пример Ниже приведена простая программа, которая показывает различные реализации строк. Задание значения для stringl[O] может вызвать ошибку времени исполне- ния, если строковая константа находится в области памяти, объявленной с атрибу- том «только для чтения». char «stringl, *string2; int main() { stringl = "abed"; string2 = "abed"; if (stringl ==string2) printf("Строки хранятся по одному адресу.\n"); else printf("Строки хранятся по разным адресам.\n"); stringl [0] = '1'; /* ВОЗМОЖНАЯ ОШИБКА ВРЕМЕНИ ВЫПОЛНЕНИЯ */ if(*stringl == '1') printf("Строки могут быть модифицированы\п"); else printf("Строки не могут быть модифицированы \п"); return 0; )
50 Глава 2 Сборка строк. Обычно символьная строка размещаются на одной строке ис- ходной программы. Если символьная строка слишком длинная для того, что- бы ее можно было разместить на одной строке исходного кода, то ее можно разместить на нескольких, заканчивая каждую исходную строку за исключе- нием последней, символом обратной косой черты. В этом случае при сборке символьной строки символы обратной косой черты и новой строки игнориру- ются. Это позволяет размещать символьные строки на более чем одной строке исходной программы. Некоторые старые реализации С могут удалять ведущие пробельные символы из собранной строки, хотя это и может рассматриваться как ошибка. Реализация, отвечающая Standard С, автоматически выполняет конкатена- цию соседних широких (нешироких) строковых констант и ставит в конец по- следней строки единичный нулевой символ. Поэтому запись длинной строко- вой константы несколькими раздельными строками является альтернативой использования механизма разделения строки символом обратной косой черты. С99 разрешает выполнять таким образом конкатенацию широких (неширо- ких) строковых констант с результирующей широкой строкой. Standard С89 этого не разрешал. Пример Проинициализированная строка si удовлетворяет как компиляторам Standard С, так и тем, которые были созданы до него, в то время как строка s2 отвечает только требованиям Standard С: char sl[] = "Эта длинная строка при\ менима для всех компиляторов С."; char s2[] = "Эта длинная строка допустима только" "в Standard С"; Символ новой строки (newline), т.е. конец строки в исполняемом наборе сим- волов, может быть также помещен в строку с помощью вставки в строковую константу управляющей последовательности \п. Но не следует путать символ новой строки с размещением строковой константы на нескольких строках. Широкие строки. Строковые константы, обозначенные префиксом L, явля- ются широкими строковыми константами Standard С и относятся к типу «мас- сив wchar_t». Такие строковые константы представляют последовательность широких символов из расширенного исполняемого набора символов и могут быть использованы для такого языка, как японский. Символы в широких строковых константах являются многобайтными символьными строками, ко- торые отображаются на набор широких символов способом, определяемым реализацией. (Функция mbstowcs выполняет такое преобразование на этапе выполнения программы.) Если кодирование многобайтных символов зависит от состояния, то широкие строковые константы должны начинаться и закан- чиваться в начальном состоянии регистра. Ссылки: типы массивов 5.4; квалификатор типа const 4.4 4; преобразование типов массивов 6.2.7; управляющие символы 2.7.5; инициализаторы 4.6; функция mbstowcs 11.8; функция mktemp 15.16; многобайтные символы 2.1.5; типы указателей 5.3; лексические соглашения препроцессора 3.2; оператор sizeof 7.5.2, функция strlen 13 4; символы-разделители (про- бельные символы) 2.1; стандартные унарные преобразования 6.3 3; тип wchdr_t 11.1; универ- сальные символьные имена 2.9.
Лексика языка 51 2.7.5 Управляющие символы Управляющие символы (escape characters) используются для представления в символьных и строчных константах символов, которые неудобно или сложно ввести в исходную программу непосредственно. Различают два вида управ- ляющих последовательностей: символьные (character escape), которые могут быть использованы для представления некоторых специальных символов и символов форматирования, и числовые (numeric escape), которые позволяют задавать символы с помощью их числовых значений. Standard С99 позволяет также использовать в качестве управляющих последовательностей универ- сальные символьные имена. управляющий-символ: \ управляющий-код универсальное-символьное-имя ( С99 ) управляющий-код: символъный-управляющий-код восъмеричный-управляющий-код шестнадцатеричный-управляющий-код ( С89 ) символъный-управляющий-код: один из n t b г f v \ ' " а ? (С89) восъмеричный-управляющий-код: восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра шестнадцатеричный-управляющий-код: х шестнадцатеричная-цифра шестнадцатеричный-управляющий-код шестнадцатеричная-цифра (С89) Значения этих управляющих последовательностей детально обсуждается в следующих разделах. Если символ, который следует за символом обратной косой черты, не явля- ется восьмеричной цифрой, буквой х и не относится ни к одному символьному управляющему коду, из перечисленных ранее, то результат не определен. (В таком случае в традиционном С символ обратной косой черты игнорировал- ся.) Standard С резервирует все буквы в нижнем регистре, следующие за сим- волом обратной косой черты, для будущих расширений языка. Буквы в верх- нем регистре могут использоваться реализациями для специфичных расшире- ний. Ссылки: универсальные символьные имена 2.9. 2.7.6 Символьные управляющие коды Символьные управляющие коды (character escape code) используются для представления обычных специальных символов способом, до определенной степени независимым от набора символов целевого компьютера. Символы, ко-
52 Глава 2 торые могут следовать за символом обратной косой черты и их значения пере- числены в таблице 2.7. Таблица 2.7. Символьные управляющие коды Упровляющий код Значение Управляющий код Значение о1 сигнал (звонок) V вертикальная тобуляция ь возврат но одну позицию \ обратная косая черта f перевод страницы одинарная кавычка (апостроф) п новая строка двойная кавычка г возврат каретки ?1 знак вопроса t горизонтальная табуляция 1 Упровляющий код. Управляющий код \а обычно обозначает «звонок» или другой звуковой сиг- нал устройства вывода (например, в кодировке ASCII это код 7 или Ctrl-G). Символьный управляющий код \? необходим для вставки знака вопроса в тех редких случаях, когда он может быть спутан с частью триграфа. В символьных константах кавычка (") может использоваться без предшест- вующего ей символа обратной косой черты. Аналогичное замечание справед- ливо для символа апострофа (’) в строковых константах. Пример Для иллюстрации использования символьных управляющих кодов ниже приведена небольшая программа, которая подсчитывает количество строк (на самом деле ко- личество символов новой строки) во вводе. Функция getchar возвращает следую- щий введенный символ до тех пор, пока не закончен ввод, после чего она возвраща- ет значение макроса EOF, который определен в заголовочном файле stdio.h: ftinclude <stdio.h> int main(void) /*Подсчитывает количество строк ввода. */ { int next_char; int num_lines - 0; while ((next_char = getchar()) •= EOF) if (next_char == ’\n') ++num_lines; printf(”%d строк прочитано.\n", num_lines); return 0; ) Ссылки: строковые константы 2 7 3, макрос EOF 15 1; функция getchar 15 6, заголовочный файл stdio.h 15 1; строковые константы 2.7 4; триграфы 2.1 4 2.7.7 Числовые управляющие коды Числовые управляющие коды (numeric escape code) позволяют задавать сим- волы из исполняемого набора с помощью записи их числового значения непо- средственно в восьмеричной или — в Standard С — шестнадцатеричной систе- ме счисления. За символом обратной косой черты может стоять до трех вось-
Лексика языка 53 меричных цифр и любое количество шестнадцатеричных цифр, но Standard С запрещает все значения, которые выходят за диапазон допустимости типа unsigned char для обычных символьных констант, и все значения вне диапазо- на типа wchar_t для широких символьных констант. Например, в ASCII-коди- ровке символ 'а' может быть записан как '\141' или '\х61', а символ '?’ как ’\77’ или ’x3F'. Нулевой символ, используемый для обозначения окончания строк, всегда записывается как \0. Значение числового управляющего кода, которому не сопоставлен символ из исполняемого набора символов, определя- ется реализацией. Пример Следующий фрагмент кода иллюстрирует использование числовых управляющий последовательностей. Переменная inchar относится к типу int. for (;;) { inchar = received; if (inchar = '\0’} continue; /* Игнорировать */ if (inchar — '\004') break; /* Выход */ if (inchar == 1\006'> reply (1 \006 '); /* Подтверждение приема */ else reply ('\025'); /* Отсутствие подтверждения приема */ } Существуют две причины, по которым программисты должны быть очень осторожными при использовании числовых управляющих последовательно- стей. Первая заключается в том, что числовые последовательности зависят от кодировки символов и поэтому могут быть не переносимыми. Поэтому лучше прятать управляющие коды в макроопределения, которые значительно легче менять: #define NUL #define EOT #define ACK #define NAK '\0 ’ '\004' '\006' \025’ /* Нулевой символ */ /* Символ конца передачи */ /* Символ подтверждения приема */ /* Символ отсутствия подтверждения приема */ Вторая причина состоит в непростом синтаксисе числовых последователь- ностей. Восьмеричная числовая последовательность прекращается после трех восьмеричных цифр или первой же Цифрой, которая не относится к восьме- ричным. Поэтому строка "\0111" состоит из двух символов — \011 и 1, а стро- ка ”\090” состоит из трех символов — \0, 9 и О. При использовании шестна- дцатеричных цифр проблема с окончанием последовательности никуда не ис- чезает, с учетом еще и того, что они могут быть вообще любой длины. Для ограничения шестнадцатеричной цифровой последовательности в строке, строку следует разделять на несколько частей: "\xabc" /* Эта строка содержит один символ. */ "\xab" "с" /* Эта строка содержит два символа. */ Некоторые реализации, не отвечающие Standard С, ограничивают шестна- дцатеричные последовательности фиксированным количеством цифр точно также как и восьмеричные последовательности. Ссылки: строковые константы 2.7.3; директива #define 3.3; макроопределения 3.3; нулевой символ 2.1; строковая константа 2.7.4; исполняемый набор символов 2.1.
54 Глава 2 2.8 Совместимость с C++ Этот раздел перечисляет лексические различия С и C++. 2.8.1 Наборы символов Правила написания лексем (токенов) и триграфов Standard С представляют собой часть соответствующих правил Standard C++. Однако они отличаются у реализаций C++, вышедших до Standard C++. Standard С и C++ используют универсальные символьные имена с одинаковым синтаксисом, но в отличие от C++, Standard С позволяет использовать в идентификаторах другие символы, определяемые реализацией. (Ожидается, что реализации C++ будут реализо- вать эту особенность в качестве расширения). 2.8.2 Комментарии Комментарии Standard С99 применимы для C++ и наоборот. До С99 ком- ментарии в стиле C++ не были применимы для Standard С и поэтому интерпре- тации последовательности символов //* для С и C++ могла различаться. (Де- тали оставлены в качестве упражнения.) 2.8.3 Операции В C++ есть три новых составных операции: ф * _>* Так как эти комбинации лексем недопустимы в программах Standard С, то они никак не могут повлиять на переносимость С на C++. 2.8.4 Идентификаторы и ключевые слова Идентификаторы, перечисленные в таблице 2.8, являются ключевыми сло- вами C++. Из перечисленных ключевых слов в Standard С зарезервирован идентификатор wchar_t, а ключевые слова bool, true и false зарезервированы как часть стандартной библиотеки в С99. Таблица 2.8. Дополнительные ключевые слова в C++ asm export private throw bool false protected true catch friend public try class mutable reinterpret_cast typeid constcast namespace staticcast typename delete new template using dynamic_cast operator this virtual explicit wchar_t
Лексика языка 55 2.8.5 Символьные константы Односимвольные константы (single-character constant) в С относятся к типу int, в отличие от C++, где они относятся к типу char. Многосимвольные кон- станты относятся к типу int и определяются реализацией в обоих языках. На практике, маленькое различие между С и C++ все равно существует, так как в C++ для символьных констант при использовании в выражениях выполняет- ся неявное преобразование к типу int. Однако sizeof(’c') в С имеет вид sizeof(int), в то время как в C++ это выглядит как sizeof(char). 2.9 Наборы символов, алфавиты и кодировки Изначально, язык программирования С создавался тогда, когда нужды ин- тернационального многоязычного сообщества программистов еще не были осознаны. Standard С расширяет возможности языка С для того, чтобы он стал ближе этому сообществу. Этот раздел является информационным обзором ис- тории и задач, которые были адресованы Standard С, чтобы сделать его более дружественным неанглийскому пользователю. Наборы символов и кодировка ASCII. Письменная коммуникация в рам- ках каждой культуры базируется на наборе символов (character repertoire) со- стоящем из знаков или символов. Для U.S.-English он состоит из 52 литер верхнего и нижнего регистра, десятичных цифр и некоторых знаков пунктуа- ции. Всего это составляет около 100 знаков, которым были назначены двоич- ные значения (англоязычными программистами и производителями компью- теров) с использованием семибитной кодировки под названием ASCII. Эти зна- ки используются как на стандартных клавиатурах, так и в определениях языка С. К сожалению, другие письменные культуры используют иные наборы сим- волов. Например, жители Великобритании хотели бы использовать вместо знака $, знак £, но этот символ не входит в семибитную кодировку ASCII. Та- кие языки как русский или иврит используют совсем другие алфавиты, а культуры Китая, Японии и Кореи имеют наборы символов, содержащие ты- сячи позиций. Сегодня программисты хотят создавать программы на С, кото- рые могут читать и записывать текст на многих языках, включая их родной. Они также хотят, чтобы комментарии и имена переменных были записаны на родном для них языке. Программы, написанные таким образом должны обла- дать свойством переносимости, — по крайней мере, перенос между разными языками не должен приводить к ошибкам. (Конечно, вам не удастся прочесть комментарии на санскрите, если вы не знаете этого языка и ваш компьютер не может его правильно отображать.) Полностью эти задачи решены не были и сейчас существуют и поддержива- ются только некоторые частные решения. Например, кодировка ISO 646-1083 Invariant Code Set, которая является подмножеством кодировки ASCII, содер- жит в себе множество общих символов для неанглийских наборов символов, но не содержит таких символов, как {, }, [, ] и #, для которых используется альтернативное написание. ISO/IEC 10646. Общим решением для согласования различных наборов символов является стандарт ISO/IEC 10646 (с дополнениями), который назы-
56 Глава 2 вается Universal Multiple-Octet Coded Character Set — UCS (Универсальный Символьный Набор Многооктетных Кодов). Это набор четырехбайтных (четы- рехоктетных) символьных кодов, который в состоянии представить все суще- ствующие на Земле алфавиты с большим запасом. Существует также UCS-2 — удобное 16-битное подмножество кодировки UCS-4. UCS-2 включает в себя те символы UCS-4, у которых два старших байта равны нулю. UCS-2 в состоянии представить все основные наборы символов разных культур, включая около 20000 иероглифов Китая, Японии и Кореи. Однако лучше все-таки использо- вать UCS-4, чем UCS-2, так как 16 бит все же недостаточно для представления всех необходимых наборов символов, а следующий «популярный размер» пор- ции данных, которую было бы удобно обрабатывать на компьютере, имеет ве- личину 32 бита. Изначально стандарт набора символов Unicode, который выпустил Unicode Consortium (www.unicode.org), был 16-битным и совместимым только с набо- ром символов UCS-2. Третья версия этого стандарта (Unicode 3.0) полностью совместима со стандартом ISO/IEC 10646. Сайт этого Консорциума является хорошим введением в мир кодировок. Стандарты наборов символов UCS-4, UCS-2 и Unicode совместимы со стан- дартом ASCII. Это означает, что 16-битные значения, старшие байты которых равны нулю, являются 8-битными расширениями соответствующих символов ASCII, входящими в набор, называемый Latin 1. Исходные семибитные симво- лы ASCII, составляющие набор, называемый сейчас Basic Latin, представляют собой символы UCS-2, старшие 9 бит которых равны нулю. Широкие и многобайтные символы. Символы, представление которых за- нимает больше чем 8 бит, называются широкими (wide character). К сожале- нию, отойти от использования семи- и восьмибитных кодировок символов очень сложно. Существует и интенсивно используется огромное множество компьютеров и программ, которые используют семи- и восьмибитные симво- лы, а для представления больших символьных алфавитов и широких симво- лов применяют последовательности семи- и восьмибитных кодов. Эти схемы представления называются многобайтным кодированием (multibyte encoding) или многобайтными символами (multibyte characters). В отличие от широких символов, представление которых имеют фиксированный размер, многобайт- ные схемы часто используют для одних символов один байт, для других — два, для третьих — три и так далее. Восьмибитные коды, которые стоят в на- чале последовательности обычно играют роль управляющих символов или символов смены регистра (выбора кодировки). В Standard С на сегодняшний день реализован ряд различных технологий: триграфы и диграфы для различных кодировок ASCII, поддержка широких символов, механизмы использования многобайтных символов для процедур ввода/вывода, и, не в последнюю очередь, способы представления различных алфавитов в программах, которые будут сохранять свойства переносимости (использование в идентификаторах национальных символов, а также универ- сальных символьных имен). Универсальные символьные имена. Standard С99 вводит нотацию, которая позволяет использовать в символьных/строковых константах и идентификато- рах символы из кодировок UCS-2 и UCS-4. Синтаксис этой нотации: универсалъное-символъное-имя: \и шестнадцатеричная-четверка \U шестнадцатеричная четверка шестнадиатеричная-четверка
Лексика языка 57 шестнадцатеричная-четверка: шестнадцатеричная-цифра шестнадцатеричная-цифра шестнадцатеричная-цифра шестнадцатеричная-цифра Каждая шестнадцатеричная-четверка представляет собой четыре шестна- дцатеричные цифры, которые определяют 16-битное значение. Значения ше- стнадцатеричных четверок определены стандартом ISO\IEC 10646 как четы- рехциферные и восьмициферные «короткие идентификаторы» универсальных символов. Обозначения символов \unnnn и \U0000nnnn эквивалентны. В С запрещены универсальные символьные имена, значения которых мень- ше 00А0 или лежат в диапазоне от D800 до DFFF. Исключения составляют символы со значениями 0024($), 0040(@) и 0060('). Перечисленные диапазо- ны содержат управляющие символы, включая DELETE, а также символы, за- резервированные для кодировки UTF-16. Результат использования объедине- ния лексем для создания универсальных символов не определен. Ссылки: идентификаторы и универсальные символьные имена 2.5; объединение лексем 3.3.9. 2.10 Упражнения 1. Что из перечисленного ниже относится к лексемам (токенам)? (а) ключевые слова (Ь) комментарии (с) символы-разделители (d) шестнадцатеричные константы (е) триграфы (f) широкие строковые константы (g) круглые скобки 2. Перечисленные ниже строки были обработаны компилятором Standard С. Какие строки будут распознаны, как последовательности лексем? Сколько лексем записано в каждом случае? (То, что некоторые из них не могут быть использованы в правильной программе, не имеет значе- ния.) (a) X++Y (b) -12uL (с) 1.37E+6L (d) "String ""FOO.. (е) ”String+\"FOO\"" (f) x**2 (g) "X??/" (h) B$C (i) A*=B (j) while##D0 3. Исключите из следующего фрагмента программы, написанной на С, все комментарии. /**/*/*"*/*/*"//*//**/*/ 4. Компиляторы Standard С должны выполнять над входной программой следующие действия. В каком порядке они должны выполняться?
58 Глава 2 • сборка символов в лексемы; • удаление комментариев; • преобразование триграфов; • обработка логических строк. 5. Ниже приведены примеры идентификаторов, использование которых в программе нежелательно. Объясните почему. (a) pipesendintake (b) Const (с) 10 (d) 077U (е) SYS$input 6. Приведите простой пример фрагмента исходного кода, который был бы неправильным для C++ или интерпретировался в нем отличным от Standard С способом по следующим причинам: (а) в С89 нет комментариев в стиле C++ (//); (Ь) типы констант; (с) конфликты использования ключевых слов.
Глава 3 Препроцессор С Препроцессор С — это простой обработчик макросов, который просматривает исходный текст программы, написанной на С, до того, как исходная програм- ма будет обработана компилятором. В некоторых реализациях С препроцессор является отдельной программой, которая читает изначальный исходный файл и дает на выходе новый «препроцессированный» исходный текст программы, который затем подается на вход компилятору. В других реализациях препро- цессор и компилятор являются одной программой, которая выполняет препро- цессирование и компиляцию за один проход исходной программы. 3.1 Директивы препроцессора Препроцессор управляется специальными строками директив, которые входят в текст исходной программы и начинаются символом #. Строки, кото- рые не содержат директив препроцессора, называются строками исходной программы. Директивы препроцессора приведены в таблице 3.1. Обычно препроцессор удаляет из исходного файла все строки директив и выполняет дополнительные преобразования исходного файла согласно ука- заниям, приведенным в этих директивах. Примером таких преобразований может служить развертывание макровызовов, которые встречаются внутри ис- ходной программы. Результирующий исходный файл должен быть коррект- ной (valid) исходной С-программой. Синтаксис директив препроцессора полностью независим от синтаксиса ос- тальной части языка С (хотя во многом и похож на него). Например, для мак- роопределений допустимо развертывание в синтаксически неполные фрагмен- ты исходной программы в той степени, в которой они имеют смысл во всех контекстах, где такие макроопределения вызываются (т.е. полученные непол- ные фрагменты корректно завершаются другими элементами текста програм- мы). Таблица 3.1. Директивы препроцессора Директива Значение Раздел #define Определяет макрос препроцессора 3,3 #undef Удоляет макроопределение препроцессора 3.3.5 #indude Вставляет текст из другого исходного файла 3.4
60 Глава 3 Директива Значение Роздел #if Условно включает некоторый текст на основании значения некоторого константного выражения 3 5 1 # ifdef Условно включает некоторый текст на основании того, определено ли имя некоторого макроса 3 5.3 #ifndef Включает некоторый текст при условии противоположном директиве # ifdef 3.5.3 Weise Альтернативно включает некоторый текст, если результат теста предшествующей директивы #if, #ifdef, Aifndef или Aelif был негативным 35 1 Wendif Завершает условный фрагмент 3.5 1 Aline Задает информацию о нумерации строк для сообщений компилятора 36 Aelif’ Альтернативно включает некоторый текст на основании другого константного выражения, если результат теста предшествующей директивы #if, # if def, #ifndef или #elif был негативным 3.5.2 defined1 Функция препроцессора, значение которой равно 1, если ее аргументом является имя, определенное как макрос, и 0 в противном случае. Используется в директивах #if и #elif 3.5 5 # операция2 Зомещает аргумент макроса строковой константой, содержащей значение аргумента макроса 3.3.8 ## операция2 Создает одну лексему из двух смежных 3.3.9 #pragma2 Задает для компилятора информацию, котороя является зависимой от реализации 3.7 # error2 Выводит указанное сообщение об ошибке времени компиляции 38 1 Изночально не входила в С, но теперь часто встречается как в ISO так и в не-ISO реализациях С. 2 Относится к нововведениям Slondord С. 3.2 Лексические принципы препроцессора Препроцессор не выполняет анализа исходной программы, за исключением ее разделения на лексемы (токены) с целью выявления макровызовов. Лекси- ческие принципы препроцессора немного отличны от принятых для компиля- тора. Препроцессор распознает как обычные лексемы С, так и другие симво- лы, которые с точки зрения компилятора С не являются корректными лексе- мами. Это позволяет препроцессору распознавать имена файлов, выявлять присутствие или отсутствие символов-разделителей и находить позиции мар- керов конца строк. Строка, начинающаяся с символа #, интерпретируется как директива пре- процессора. За символом # должно следовать имя директивы. Standard С, в отличие от некоторых старых компиляторов С, разрешает наличие пробель- ных символов (whitespace) в исходной строке, как до символа #, так и после него. Строка, состоящая из символов, из которых к пробельным не относится лишь символ #, Standard С считает пустой директивой (null directive) и интер- претирует как пустую строку. Более старые реализации С могут вести себя в данной ситуации иначе. За директивой препроцессора могут находиться ее аргументы. Если дирек- тиве аргументы не нужны, то та часть строки, которая следует .за директивой, должна быть пустой или содержать только пробельные символы или коммен-
Препроцессор С 61 тарии. Многие компиляторы, созданные до стандарта ISO, игнорировали все символы, которые располагались в строке следом за ожидаемыми аргумента- ми (если таковые аргументы были), без выдачи соответствующего предупреж- дения; это может порождать проблемы переносимости. Как правило, аргумен- ты директив препроцессора являются объектами макрозамены. Строки препроцессора распознаются до генерации макрорасширений. По- этому если макрос развертывается в некоторую конструкцию, которая выгля- дит как директива препроцессора, то эта директива не будет распознана как препроцессорами, которые отвечают стандарту С, так и многими другими пре- процессорами. (Некоторые старые реализации UNIX'a нарушали это правило). Пример Результатом обработки приведенного ниже фрагмента исходной программы не бу- дет включение заголовочного файла math.h в текст программы: /* Этот пример работает не так, как может показаться на первый взгляд! */ ttdefine GETMATH ftinclude <math.h> GETMATH Вместо этого, последовательность лексем # include < math h > будет обработана и скомпилирована как фрагмент исходной программы (ошибоч- ный). Как указано в разделе 2.1.2, все исходные строки (включая строки, кото- рые содержат директивы препроцессора) могут быть продолжены с помощью символа обратной косой черты \, предшествующего маркеру конца строки. Строки, продленные таким образом, обрабатываются до процесса поиска ди- ректив препроцессора. Пример Директива препроцессора #define err(flag, msg) if (flag) \ printf(msg) эквивалентна директиве #define err(flag, msg) if (flag) printf(msg) Если символ обратной косой черты, находится сразу перед маркером конца строки, то две строки #define BACKSLASH \ #define ASTERISK * будут интерпретироваться как одна директива препроцессора ftdefine BACKSLASH #define ASTERISK * В разделе 2.2 указано, что препроцессор интерпретирует комментарии как пробельные символы, а разделение строк не разрывает директив препроцессо- ра. Ссылки: комментарии 2.2; разделение и продление строк 2.1; лексемы 2.3.
62 Глава 3 3.3 Макроопределение и макроподстановка Директива препроцессора #define определяет имя (идентификатор) как макрос препроцессора. С именем ассоциируется последовательность лексем, называемая телом макроса. Когда имя макроса распознается в исходном тек- сте программы или в аргументах некоторых других директив, то оно интер- претируется как вызов этого макроса и выполняется замена его имени копией тела этого макроса (макроподстановка). Если определен макрос, который при- нимает аргументы, то формальные параметры, которые находятся в его теле, заменяются фактическими параметрами, которые следуют за именем ^макроса в макровызове. Пример Если макрос sum определен с двумя аргументами как #define sum(x,y) х+у то препроцессор заменяет строку исходной программы result = sum(5, a*b) ; простой заменой текста result = 5 + а*Ь; Так как препроцессор не отличает зарезервированные слова от других иден- тификаторов, то в качестве имен макросов препроцессора в принципе можно использовать зарезервированные слова языка С, но такой подход является примером плохого стиля программирования. Имена макросов никогда не рас- познаются внутри комментариев, в строковых или символьных константах или именах файлов, включенных директивой #include. 3.3.1 Объектные макроопределения В зависимости от наличия или отсутствия круглых скобок за именем мак- роса различают два вида макроопределений. Более простой вид не имеет круг- лых скобок и его нотация выглядит следующим образом: ftdefine имя последовательность-лексемопц Объектные (objectlike) макросы не принимают аргументов. Они вызываются простым указанием их имен. Когда в тексте программы упоминается имя мак- роса, то оно заменяется телом этого макроса (указанная в макроопределении по- следовательность-лексем, которая может быть пустой). В директиве #define не надо использовать после имени макроса знак равенства или какие-либо другие разделяющие лексемы. Тело макроса начинается сразу за его именем. Объектные макросы удобно использовать для определения в тексте про- граммы констант, на которые в дальнейшем можно ссылаться с помощью име- ни. Такой макрос может быть использован, например, чтобы один раз указать размер таблицы, а впоследствии ссылаться на него во всем тексте программы. Это дает возможность легко изменять это число в дальнейшем. Другим важным применением объектных макросов является их использова- ние для изоляции определяемых реализацией ограничений, накладываемых на имена внешних функций и переменных. Такой пример приведен в разделе 2.5.
Препроцессор С 63 Пример Некоторые типичные макроопределения: fldefine BLOCK_SIZE 0x100 # define TRACK_SIZE (16*BLOCK_SIZE) #define EOT '\004' #define ERRMSG "*** Error %d: %s.\n" Типичная ошибка программистов состоит в использовании ненужного в данном слу- чае знака равенства: # define NUMBER_OF_TAPE_DRIVERS = 5 /* Вероятио неправильно. */ Это макроопределение лексически корректно, но приводит к тому, что имени NUMBER_OF_TAPE_DRIVERS сопоставляется тело "= 5", а не "5", как можно было предположить. Поэтому, если включить в исходную программу фрагмент if (count != NUMBER_OF_TAPE_DRI VERS) то он будет развернут в конструкцию if (count != = 5) которая содержит синтаксическую ошибку. По этим же соображениям следует избе- гать и ненужных точек с запятой: # define NUMBER_OF_TAPE_DRIVERS 5 ; /* Вероятно неправильно. */ Ссылки: составные операторы присваивания 7.9.2; операции и разделители 2.4. 3.3.2 Макроопределения с параметрами Более сложным макроопределением (похожим на функцию — functionlike) является определение, которое использует аргументы, перечисленные за име- нем макроса в круглых скобках и разделенные запятыми: #define имя( список-идентификаторов0т ) последовательность-лексем0П[1 где список-идентификаторов представляет собой список имен формальных параметров, разделенных запятыми. В Стандарте С99 многоточие (... — три символа «точка») также может находиться после списка идентификаторов. Многоточие сообщает препроцессору, что список аргументов данного макрооп- ределения имеет переменную длину. Эта разновидность макросов обсуждается в разделе 3.3.10; до этого раздела мы будем рассматривать списки параметров только фиксированного размера. Левая круглая скобка должна находиться сразу за именем макроса, иначе, если между скобкой и именем находится пробельный символ (whitespace), то считается, что данное макроопределение не принимает аргументов, а его тело начинается с левой круглой скобки. Имена формальных параметров должны быть различающимися между со- бой идентификаторами, т.е. их имена не должны совпадать. Нет указаний к тому, что все параметры должны быть обязательно использованы в теле мак- роса (но обычно они используются все). Функциональные макросы могут иметь пустой список формальных параметров. Этот вид макросов полезен для представления функций, которые не принимают аргументов. В функциональных макросах количество фактических и формальных пара- метров должно совпадать. Подобный макрос вызывается с помощью указания имени, открывающей круглой скобки, последовательности лексем для каждо- го формального параметра и закрывающей круглой скобки. Последовательно- сти лексем (фактические параметры) разделяются запятыми. Когда вызывает-
64 Глава 3 ся функциональный макрос, не имеющий формальных параметров, то должен использоваться пустой список фактических параметров. При вызове макроса между его именем и открывающей круглой скобкой, или между параметрами в списке фактических параметров могут использоваться пробельные символы. (Некоторые старые и не отвечающие стандарту реализации препроцессоров за- прещали размещать список фактических параметров на нескольких строках, если эти строки не были продлены с помощью символа обратной косой черты \.) Список лексем фактических аргументов может содержать как круглые скобки, если они правильно размещены и сбалансированы, так и запятые, если каждая запятая находится внутри парных скобок. (Это ограничение по- зволяет не путать запятые внутри фактических параметров с запятыми, разде- ляющими фактические параметры.) Фигурные и угловые скобки также могут встречаться среди аргументов макроса, но они не должны содержать запятые и требование сбалансированности для них может не выполняться. Круглые скобки и запятые, находящиеся внутри лексем строковая-константа или символьная-константа, не принимаются во внимание при проверке сбаланси- рованности скобок и разграничении фактических параметров. В Стандарте С99 параметры макроса могут быть пустыми, т.е. могут не со- держать лексем. Пример Ниже приведено определение макроса, который перемножает два аргумента: ftdefine product(х,у) ((х) * (у)) В следующем выражении этот макрос вызывается дважды: х = product(а+3,Ь) + product (с, d); Аргументы макроса product могут содержать вызовы функций (или макросов). За- пятые в списках аргументов функций не влияют на синтаксический разбор аргу- ментов макроса: return product( f(a,b), g(a,b) ) ; /* Корректная запись */ Пример Макрос getchar имеет пустой список параметров: #define getchar() getc(stdin) При вызове этого макроса используется пустой список аргументов: while ((c=getchar()) ’= EOF) ... (getchar, stdin и EOF определены в стандартном заголовочном файле stdio.h.) Пример Мы можем определить макрос, который принимает в качестве аргумента произволь- ный оператор: #define insert(stmt) stmt Вызов макроса insert( (а=1; b=l;) ) не вызывает ошибок. Но если мы заменим два оператора присваивания на один, ко- торый будет содержать два выражения присваивания insert( <а=1, Ь=1;) )
Препроцессор С 65 то тогда компилятор выдаст сообщение о том, что мы использовали для макроса слишком много параметров. Для решения этой проблемы следует написать: insert( {(а=1, Ь=1);) ) Пример Определение функциональных макросов для использования в операторном контек- сте может таить в себе опасность. Следующий макрос меняет местами значения пе- ременных х и у, которые принадлежат к типам, чьи значения могут быть преобразо- ваны к типу unsigned long и обратно без изменений и без вовлечения идентификато- ра _temp. #define swap(x, у) { unsigned long _temp=x; x=y; y=_temp; } Проблема состоит в том, что вполне естественным является желание поставить при вызове после имени swap точку с запятой, как будто макрос swap является функци- ей: if (х > у) swap(x, у); /* Неверио! */ else х = у; В результате возникнет ошибка, поскольку макрорасширение будет содержать лиш- нюю точку с запятой (раздел 8.1). В приведенном ниже фрагменте кода операторы макрорасширения приведены в отдельных строках для наглядности: if (х > у) { unsigned long _temp=x; х=у; y=_temp; } else x = у; Изощренным способом решения этой проблемы является определение тела макроса с помощью оператора do-while, который поглощает точку с запятой (раздел 8.6.2): #define swap(x, у) \ do { unsigned long _temp=x; x=y; y=_temp; } while {0) При вызове функционального макроса выполняется обработка параметров, а затем макровызов с параметрами замещается на обработанное тело этого макроса. Обработка параметров выполняется следующим образом. Строки лексем фактических параметров ассоциируются с соответствующими именами формальных параметров. Затем при каждом появлении в копии тела макроса формального параметра он замещается ассоциированной с ним копией после- довательности лексем фактического параметра. Эта копия тела макроса и за- мещает макровызов. Весь процесс замены макровызова обработанной копией его тела называется макрорасширением (macro expansion); обработанная ко- пия тела макроса называется расширением макровызова (expansion). Пример Макроопределение является удобным способом создания цикла, который считает от заданного значения до некоторого предельного значения, включая его: #define incr(v,low,high) \ for ( (v) = (low); (v) <= (high); (v)++) Для того чтобы напечатать таблицу кубов чисел от 1 до 20 мы могли бы написать #include <stdio.h> int main(void) { int j ; incr(j, 1, 20)
66 Глава 3 printf("%2d %6d\n", j, ; return 0; ) Расширение макроса incr для этого цикла выглядит так: for ((j) = (1) ; (j) <= (20) ; (j) + + ) Использование дополнительных круглых скобок гарантирует, что составные факти- ческие параметры не будут неправильно проинтерпретированы компилятором. (См. раздел 3.3.6) Ссылки: оператор do 8.6.2; синтаксис операторов 8.1; тип unsigned long 5.1.2; симво- лы-разделители (пробельные символы) 2.1.2. 3.3.3 Повторный просмотр выражений, содержащих макросы Как только макровызов заменяется расширением, процесс поиска макро- вызовов возобновляется с начала расширения для того, чтобы распознать име- на макросов внутри расширения с целью их дальнейшей замены. Во время об- работки макроопределения и имени макроса в рамках директивы #define мак- рорасширение не выполняется ни для какой из частей директивы #define. Имена макросов распознаются внутри тела только в расширении макроса по- сле его вызова. Макрорасширение также не выполняется на этапе сканирования фактиче- ских параметров функционального макроса во время его вызова. Имена мак- росов распознаются среди лексем фактических параметров только в процессе повторного просмотра расширения, учитывая то, что соответствующие фор- мальные параметры появлялись в теле макроса один или более раз (вызывая таким образом появление строк лексем фактических параметров в расшире- нии один или более раз). Пример Даны следующие определения; #define plus(х,у) add(y,x) ttdefine add(x,y) ( (х) + (у)) Вызов plus(plus(a ,b),с) разворачивается следующим образом; Шаг Результат 1. (исходное выражение) plus (plus (a , b) , с) 2. add(c,plus(a,b)) 3 ((c) + (plus(а,Ь))) 4 ((C) + (add (Ь, а) ) ) 5. (результирующее выражение) ( (с) + (((b) + (а)))) Для макросов, которые упоминаются в собственном расширении, независи- мо от того, выполняется это непосредственно или с помощью последовательно-
Препроцессор С 67 сти вложенных расширений макросов, повторное расширение не выполняет- ся. Это позволяет программистам переопределять функцию в терминах ее ста- рого определения. Старые препроцессоры С обычно не распознавали эту рекурсию и выполняли последовательность расширений до появления некото- рой системной ошибки. Пример Следующий макрос меняет определение функции извлечения квадратного корня с целью обработки отрицательных аргументов отличным от обычного образом: #define sqrt(x) ((х)<0 ? sqrt(-(x)) : sqrt(x)) За исключением того, что аргумент будет оцениваться более одного раза, этот мак- рос будет выполняться так, как предполагает Standard С, но в старых компиляторах это может вызвать проблемы. Аналогично: #define char unsigned char В разделе 7.4.3 приведен интересный пример использования макросов для отслеживания вызовов функций. 3.3.4 Предопределенные макросы Для препроцессоров Standard С требуется определение некоторых основных объектных макросов (таблица 3.2). Имя каждого такого макроса начинается и заканчивается двумя символами подчеркивания. Предопределенные макро- сы не могут быть «забыты» с помощью директивы #undef или переопределены программистом. Макросы___LINE___и___FILE__удобно использовать для вывода основных типов сообщений об ошибках. Макросы_DATE_и__TIME___могут быть ис- пользованы для записи времени выполнения компиляции. Значения _Т1МЕ_ _ И _ _DATE_ _ остаются неизменными на всем протяжении компиля- ции. Значения макросов_LINE__и______FILE_устанавливаются реализаци- ей, но могут меняться директивой #line (раздел 3.6). Предопределенный иден- тификатор __func_(раздел 2.6.1), появившийся в С99, схож по назначению с макросом _LINE___, но, по сути, является не макросом, а переменной с блочной областью видимости, которая содержит имя обрамляющей функ- ции. Макросы___STDC___ и _STDC_VERSION______удобно использовать для на- писания исходного кода, совместимого с реализациями, отвечающими и не от- вечающими Стандарту С. Макрос_STD_HOSTED____введен Стандартом С99 для различения реализаций базовых и автономных. Остальные макросы Стан- дарта С99 введены для индикации информации о том, отвечают ли средства обработки чисел с плавающей точкой и широких символов другим соответст- вующим международным стандартам. (Соблюдение стандартов здесь является желательным, но не обязательным.)
68 Глава 3 Таблица 3.2. Предопределенные макросы Макрос Зночение LINE 1 Номер текущей строки исходного файла, выраженный целой десятичной канстонтой FILE 1 Имя текущего исходного файла выраженного строковой константой. DATE Календарная дата трансляции, выраженная строковой константой вида "Mmm dd уууу" Mmm имеет то же зночение, что и у функции asctime TIME Время трансляции, выраженное строковой константой такого же вида, кок возвращаемое значение функции osctime — "hh:mm:ss" STDC Десятичная канстонто, равная 1 тогда и только тогда, когда данный компилятор соответствует стандарту ISO STDC_VERSION Если реализация соответствует Стандарту С89 с Дополнением 1, то данный мокрое имеет значение 199409L Если реализация соответствует Стандарту С99, то — 199901L. Иначе его значение не определено STDC_HOSTED (С99) Определен как 1, если донная реализация является базовой, и как 0 в случае автономной. STdC_IEC_559 (С99) Определен как 1, если реализация операций с плавоющей тачкой соответствует стандарту IEC 60559; иначе не определен STDC IEC (С99) Определен как 1, если реализация комплексной арифметики 559_COMPLEX соответствует стандарту IEC 60559; иначе не определен. STDC_ISO_ (С99) Определен как целая длинная константа вида yyyymmL, 10646 свидетельствующая, что тип wchar_t строго соответствует стандарту ISO 10646 с поправками и дополнениями, вышедшими до доты, указанной этим значением; иначе не определен 1 Эти макросы есть и в реализациях, не соответствующих стандарту ISO Реализации часто определяют дополнительные макросы для передачи ин- формации о среде, например, типе компьютера, для которого программа была скомпилирована. Подобные макросы определяются реализацией, хотя в реа- лизациях для систем UNIX обычно предопределен макрос unix. В отличие от встроенных макросов эти макросы могут быть «забыты». Standard С требует, чтобы все определяемые реализацией имена макросов начинались с символа подчеркивания с последующей литерой верхнего регистра или последующим символом подчеркивания. (Макрос unix не отвечает данному требованию.) Пример Предопределенные макросы удобно использовать в некоторых видах сообщений об ошибках: if (п •= ш) fprintf(stderr, "Внутренняя ошибка: строка %d, файл %s\n", _______LINE_, __FILE_) ; Другие макросы, определяемые реализацией, могут быть использованы для отделе- ния базового исходного кода от кода для целевого компьютера. Например, Microsoft Visual СИ- определяет макрос_WIN32 равным 1: ttifdef _WIN32 /* Исходный код для среды Win32 */ #endif Макросы __STDC и _____STDC_VERSION_____ удобно использовать для написания программ, которые должны соответствовать как реализациям Standard С. так и реа- лизациям, не отвечающим Стандарту:
Препроцессор С 69 #ifdef __STDC__ /* Некоторая версия Standard С */ #if defined(___STDC_VERSION_) && ___STDC_VERSION__ >= 199901L /* C99 */ #elif defined(___STDC_VERSION__) S& __STDC_VERSION__ >= 199409L /* C89 с Дополиениеи 1 */ «else /* C89 без Дополнения 1 */ #endif «else /* __STDC__ не определен */ /* С, не отвечающий Стандарту */ «endif Ссылки: функция asctime 20.3; комплексной арифметика гл. 23; функция fprintf 15.11; авто- номная и базовые реализации 1.4; директива препроцессора #ifdef 3.5.3; директива пре- процессора #if 3.5.1; разопределение макросов 3.3.5; тип wchar_t 24.1. 3.3.5 Отмена определения и переопределение макроса Директива #undef используется для того, чтобы сделать определенное ра- нее имя неопределенным: #undef имя Эта директива заставляет препроцессор «забыть» любое определение имени макроса имя. Использование директивы #undef для неопределенного имени не считается ошибкой. Как только была применена директива #undef имя может быть заново определено с помощью директивы #define и это не вызовет ошиб- ки. Внутри директивы #undef макрозамена не выполняется. Как в Standard С, так и во многих реализациях, не отвечающих Стандарту, разрешено мягкое переопределение макросов. Это означает, что макрос может быть переопределен, если новое его определение имеет такую же лексическую структуру, как старое. Переопределение должно включать пробельные симво- лы в тех же позициях, что и изначальное определение, хотя некоторые про- бельные символы могут быть другими. На наш взгляд программистам следует избегать мягких переопределений. Значительно лучше выглядит стиль, в ко- тором для определений всех элементов программы, включая макросы, исполь- зуется одно место в тексте. (Некоторые старые реализации С могут вообще не разрешать любые виды переопределений). Пример В следующих определениях переопределение NULL корректно, хотя ни одно из пе- реопределений FUNC не является правильным. (Первое включает пробельные сим- волы, которых нет в исходном определении, а второе меняет две лексемы). «define NULL 0 «define FUNC(x) x+4 «define NULL /* нулевой указатель */ О «define FUNC(x) x+4 «define FUNC(y) y+4
70 Глава 3 Пример Если программист не может сказать, существует ли предыдущие определение, то чтобы избежать переопределения он может использовать директиву #ifndef: Aifndef MAXTABLESIZE Adeline MAXTABLESIZE 1000 Aendif Использование этой идиомы удобно с практической точки зрения в реализациях, которые допускают применение макроопределений в командах, вызывающих ком- пилятор С. Например, следующий вызов компилятора С в UNIX позволяет задать макросу MAXTABLESIZE значение 5000: сс -с -DMAXTABLESIZE=5000 prog.с Программист может проверить наличие такого определения, использовав приведен- ную выше конструкцию. Некоторые старые реализации препроцессора С обрабатывали директивы ttdefine и #undef как стек определений (это не разрешено в Standard С). Когда имя переопределялось директивой #define, оно помещалось в стек, а новое оп- ределение замещало предыдущее. Когда для этого имени выполнялась дирек- тива #undef, то текущее определение отбрасывалось, а самое последнее преды- дущее (при его наличии) восстанавливалось из стека. Ссылки: директива #define 3.3; директивы #ifdef и #ifndef 3.5.3. 3.3.б Ошибки старшинства операций в макрорасширениях Макросы выполняют только текстовую подстановку лексем. Разбор тела макроса на объявления, выражения или операторы выполняется только после завершения макрорасширения. Игнорирование элементарных требований ак- куратности в написании макросов может приводить к удивительным результа- там. Хорошие гарантии безопасности дает обрамление круглыми скобками ка- ждого параметра, который появляется в теле макроса. Если макрос с синтак- сической точки зрения является выражением, то его тоже следует взять в круглые скобки. Пример Рассмотрим следующее макроопределение: Adeline SQUARE(х) х*х Макрос SQUARE принимает в качестве аргумента выражение и выдает новое выра- жение, которое подсчитывает квадрат аргумента. Например, SQUARE(5) расширя- ется как 5*5. Однако выражение SQUARE(z-l-l) расширяется как z+l*z+l, что в от- личие от ожидаемого (z+l)*(z+l) интерпретируется как z+(l*z)+l. Определение макроса SQUARE, которое решает эту проблему, выглядит следующим образом: Adeline SQUARE(х) ((х)*(х)) Внешние скобки необходимы для того, чтобы такое выражение как (short) SQUARE (z+1) было правильно проинтерпретировано. Ссылки: приведение выражений 7.5.1; последовательность вычисления выражений 7.2 1
Препроцессор С 71 3.3.7 Побочные эффекты в аргументах макросов Использование макросов может давать побочные эффекты. Так как факти- ческие параметры макросов могут быть текстуально дублированы, то они мо- гут быть обработаны более одного раза и, соответственно, побочные эффекты в фактических параметрах могут проявиться более одного раза. В противопо- ложность макросам, при вызове функции, с которой макрос имеет сходство, ее аргументы вычисляются только один раз и, соответственно, побочные эффек- ты могут проявиться только один раз. Во избежание подобных проблем макро- сы должны использоваться с большой осторожностью. Пример Рассмотрим макрос SQUARE из предыдущего примера и функцию square, которая выполняет практически те же действия: int square(int х) { return х*х; } Макрос SQUARE может возводить в квадрат, как целые числа, так и числа с пла- вающей точкой. Функция может выполнять указанные действия только над целы- ми числами. При этом отработка вызова функции во время выполнения программы немного медленнее, чем использование макроса. Но эти различия менее важны, чем возникающие побочные эффекты. Во фрагменте программы а = 3; Ь = square(а++); переменная b получает значение 9, а переменной а в конце этого фрагмента при- сваивается значение 4. Следующий внешне идентичный фрагмент программы а = 3; b = SQUARE(а++); приведет к тому, что переменной b будет присвоено значение 12, а переменная а по- сле выполнения этого фрагмента будет иметь значение 5, так как расширение по- следнего фрагмента будет выглядеть следующим образом: а = 3; Ь = ((а++)*(а++)); (Мы говорим, что 12 и 5 могут быть результирующими значениями переменных b и а потому, что разные реализации Standard С могут вычислять выражение ((а++)*(а++)) разными способами. См. раздел 7.12). Ссылки: оператор инкремента ++ 7,4.4. 3.3.8 Преобразование лексем в строки В Standard С существует механизм, который позволяет преобразовывать ар- гументы макроса (после расширения) в строковые константы. До появления этого механизма для достижения такого результата программистам было необ- ходимо искать лазейки в препроцессоре, причем способы достижения необхо- димого результата были различны. В Standard С лексема #, которая появляется внутри макроопределения, распознается как унарная операция преобразования в строку (stringization), за которой (операцией) должно следовать имя формального параметра. Во вре- мя макрорасширения лексема # и следующий за ней формальный параметр за- меняются соответствующим фактическим параметром, обрамленным строко- выми кавычками. При формировании строки, каждая последовательность пробельных символов в списке лексем, выступающих аргументами, заменяет-
72 Глава 3 ся единственным символом пробела, а перед встроенными символами кавычек или обратной косой черты помещается символ обратной косой черты с целью избежания их неправильной интерпретации в строке. Пробельные символы в начале и конце параметра игнорируются, поэтому пустые параметры (даже в случае наличия пробельных символов между запятыми) расширяются пус- той строкой Пример Рассмотрим определение макроса TEST в категориях Standard С: ttdefine TEST(a,b) printf( #а "<" #b "=%d\n", (a)<(b) ) Операторы TEST(O,OxFFFF); TEST('\n',10); будут расширены в printf("О" "<" "OxFFFF" "=%d\n", (O)C(OxFFFF) ) ; printf("'\\n...<" "10" "=%d\n", ('\n’)<(10) ); После конкатенации соседних строк получим printf("0<0xFFFF=%d\n", (0)< (OxFFFF) ), printf("'\\n'<10=%d\n", (’\n')<(10) ); Некоторые компиляторы, не отвечающие Стандарту С, заменяют формаль- ные параметры макроса внутри строк и символьных констант. Standard С это запрещает. Пример В реализациях не соответствующих Стандарту, макрос TEST может быть определен следующим образом: tfdefine TEST(a,b) printf( "a<b=%d\n", (a)<(b) ) Результат расширения макроса TEST(O, OxFFFF) будет похож на результат преобра- зования в строку, которое выполняется реализациями, отвечающими Стандарту С: printf("0<0xFFFF=%d\n", (0)<(0xFFFF) ); Однако расширение макроса TEST('\n',10) почти наверняка будет неправильным из-за отсутствия дополнительного символа обратной косой черты, и вывод функции printf будет искажен наличием непредвиденного перехода на новую строку: printf(”'\n'<10=%d\n", ('\п')<(10) ); Обработка пробельных символов в реализациях, не отвечающих стандарту ISO, также варьируется от компилятора к компилятору, что служит еще од- ним поводом избегать использования этих возможностей во всех реализациях, за исключением тех, которые соответствуют Standard С. 3.3.9 Склеивание лексем в макрорасширениях Склеивание (merging) лексем с целью формирования новых лексем в макро- определениях в Standard С осуществляется с помощью операции склеивания ##. Две лексемы, которые перед повторным сканированием расширения раз- делены в списке параметров макроподстановки операцией ##, склеиваются в единую лексему. Лексемы должны удовлетворять следующему условию — операция ## не должна находится ни в начале ни в конце списка макроподста- новок. Если полученная в результате склеивания лексема не является синтак- сически правильной, то результат не определен.
Препроцессор С 73 Пример ftdefine TEMP(i) temp ## i TEMP(l) = ТЕМР(2 + к) + х; После препроцессирования это выражение будет иметь следующий вид: tempi = temp2 + к + х; В предыдущем примере могла возникнуть интересная ситуация при форми- ровании расширения для выражения ТЕМР() + х. Макроопределение коррект- но, но операция ## в макрорасширении оставлена без правой части, с которой должна комбинироваться левая ее часть (если операция не использует для это- го знак +, что было бы нежелательно). Эта проблема решается интерпретацией формального параметра i так, как если бы он расширялся в специальную «пус- тую» лексему. Поэтому расширение TEMPQ + х приведет к temp + х, что и предполагалось. Конкатенация (склеивание) лексем не должна использоваться для форми- рования универсальных символьных имен. Как и в случае с преобразованием параметров макроса в строки (раз- дел 3.3.8), конкатенация лексем в нестандартных реализациях С может быть достигнута с помощью использования лазеек. Хотя исходное описание С яв- ным образом описывает тела макросов, как последовательности лексем, а не последовательности символов, тем не менее, многие компиляторы С выполня- ют расширение макросов и повторное сканирование тел макросов так, как если бы они были последовательностями символов. Это становится заметным, в первую очередь, в том случае, когда компилятор обрабатывает комментарий полным его удалением (вместо того, чтобы заменить его пробелом). Подобная ситуация может быть использована хитроумно написанными программами. Пример Рассмотрим следующий пример #define INC ++ ftdefine TAB internal_table ftdefine INCTAB table_of_increments #define CONC(x,y) x/“/y CONC(INC,TAB) Standard С проинтерпретирует тело макроса CONC, как разделенные пробелом лек- семы х и у (комментарий преобразован в символ пробела). Вызов макроса CONC(INC,TAB) расширяется в две лексемы INC и TAB. Однако в случае нестан- дартных реализаций С, которые просто удаляют комментарии и выполняют повтор- ное сканирование тела макроса на наличие лексем, расширение макроса CONC(INC,TAB) приведет к одной лексеме INCTAB: Шаг Макрорасширение Standard С Вариант макрорасширения нестандартного препроцессора 1 CONC(INCJAB) CONQINCJAB) 2 INC/”/TAB INC/*7TAB 3 INC TAB INCTAB 4 ++ internal toble table of increments Ссылки: операция инкремента ++ 7.5.8; универсальные символьные имена 2.9.
74 Глава 3 3.3.10 Использование в макросах списков параметров переменной длины В Стандарте С99 для функционального макроса в конце списка формаль- ных параметров допустимо многоточие, которое свидетельствует о том, что данный макрос может принимать переменное число параметров: (define имя{список-идентификаторсв, .•.) последовательноать-лексем. (define имя ( . . . ) последовательиость-.пексем..... Когда вызывается подобный макрос, в списке фактических параметров должно быть как минимум столько аргументов, сколько указано в списке- идентификаторов макроопределения. Параметры, которые не вошли в спи- сок-идентификаторов, включая разделяющие их запятые, склеиваются в еди- ную последовательность препроцессорных лексем, называемых переменными параметрами (variable arguments). Возникающий в списке аргументов макро- определения идентификатор__VAARGS______интерпретируется так, как если бы он был параметром макрокоманды, равным объединению переменных па- раметров. То есть идентификатор_VA_ARGS___заменяется списком отдель- ных аргументов, включая разделяющие их запятые. _VA_ARGS____ может появляться только в тех макроопределениях, которые содержат троеточие (...) в их списке параметров. Часто макросы с переменным числом параметров используют для взаимо- действия с функциями, которые принимают переменное число аргументов, на- пример printf. С помощью использования операции преобразования в строку # они (макросы) могут быть использованы для преобразования списка парамет- ров в единую строку без использования обрамляющих параметры скобок. Пример Следующие директивы определяют макрос my_printf, который может записывать свои параметры, как в стандартный вывод, так и в вывод ошибок (при отладке). (ifdef DEBUG (define my_printf(...) fprintf(stderr, VA ARGS ) (else (define my_jprintf (...) printf (_VA_ARGS_) (endif Этот макрос можно использовать следующим образом: my_printf("х = %d\n", х), Пример Дано определение #define make_em_a_string(...) # VA ARGS Вызов make_em_a_string(a, b, c, d) расширяется в строку “a, b, c, d"
Препроцессор С 75 3.3.11 Другие трудности Некоторые, не отвечающие Стандарту, реализации С не выполняют жестко- го контроля ошибок в макроопределениях и вызовах. Например, допускается запись в теле макроса неполных лексем, которые дополняются текстом, сле- дующим за макровызовом. Не стоит использовать отсутствие контроля подоб- ных ошибок в некоторых компиляторах для построения изощренных про- грамм. Standard С четко указывает, что тела макросов должны быть последо- вательностями правильно-построенных (well-formed) лексем. Пример Например, следующий фрагмент исходного кода корректен для одной из реализа- ций, которая не отвечает стандарту ISO: #define FIRSTPART "Это расщепленная printf(FIRSTPART строка."); /* Вот так! */ После препроцессирования результирующий исходный код будет иметь вид printf("Это расщепленная строка."); 3.4 Включение файлов Директива препроцессора #include заставляет обрабатывать исходный файл так, как если бы весь текст, который находится в указанном файле, был вставлен на месте директивы. Существует три формы директивы #include: # include < б-символьная-последавательность > # include " к-символъная-последовательность " # include препроцессорные-лексемы (Standard С) б-символьная-последователъностъ: любая последовательность символов за исключением символа «больше» (>) и символа конца строки к-символьная-последовательносты любая последовательность символов за исключением символа кавычек (") и символа конца строки препроцессорные-лексемы: любая последовательность лексем С (или символов, не относящихся к пробельным, которые не могут быть проинтерпретированы как лексемы), которая не начинается с символа «меньше» (<) или кавычек ("). Последовательность символов, которая находится между разделителями в первых двух формах директивы #include, должна представлять собой имя файла в формате, который зависит от реализации. За закрывающими символа- ми > и ” могут находиться только пробельные символы. Первые две формы ди- рективы #include поддерживаются всеми компиляторами С. В Standard С име- на файлов относятся к субъектам замены триграфов и продления строк, но за исключением этих действий они никак не обрабатываются. В третьей форме #шс11Ые-директивы препроцессорные-лексемы подлежат макрорасширению, но результат такого расширения должен совпадать с одной
76 Глава 3 из первых двух форм (включая кавычки и символы < >). Эта форма директивы #include встречается значительно реже и в реализациях, не отвечающих Стан- дарту С, может не поддерживаться или поддерживаться иначе. Пример Ниже приведен пример использования третьей формы директивы #include: # if some_thing==this_thing # define IncludeFile " thisname . h" Helse # define IncludeFile "thatname.h" Hendif ((include IncludeFile Этот стиль может быть использован для локализаций, но программистам, заинтере- сованным в совместимости со старыми компиляторами, следует вместо применения директивы Wdefine непосредственно в директиве #include определять файл, кото- рый должен быть включен: # if some_thing==this_thing # include "thisname.h" Helse # include "thatname.h" #endif Общеизвестно, что синтаксис имен файлов зависим от реализации, но Standard С требует, чтобы все реализации разрешали в директиве #include имена файлов, состоящих из букв и цифр (начинающиеся с букв) и следующих за ними точкой и одной буквой. В Стандарте С89 допускалось до 5 значащих символов (до точки) в имени файла. Стандарт С99 увеличил допустимое коли- чество значащих символов перед точкой до 8. Под допустимостью мы подразу- меваем, что файлы с именами данного формата должны проецироваться на оп- ределяемые реализацией файлы. Файлы, обрамленные кавычками, и файлы, обрамленные угловыми скобка- ми, различаются способом их размещения в реализации. Обе формы заставля- ют искать файлы в наборе путей, определяемых реализацией. Обычно, форма вида # include < имя-файла > ищет файл в основных стандартных путях согласно правилам, определяемым реализацией. Эти стандартные пути часто содержат файлы реализации, на- пример, такие как stdio.h. Форма вида {(include "имя-файла" ищет файл также в стандартных путях, но обычно после того, как данный файл не найден в каком-то локальном месте, например, в текущей директо- рии. Обычно, для поиска этих файлов в реализациях предусмотрен стандарт- ный способ задания путей, внешний по отношению к языку С. Основная цель использования формы вида "состоит в доступе к заголовочным файлам, на- писанным самим программистом, в то время как форма вида <...> применяет- ся для ссылки на стандартные файлы реализации. По сути, в Standard С стандартные заголовочные файлы, наподобие stdio.h, рассматриваются, как особый случай. Standard С требует, чтобы реализации распознавали стандартные заголовочные библиотечные имена, обрамленные
Препроцессор С 77 угловыми скобками и включенные с помощью директивы #include. Но в Стан- дарте не оговаривается, что это должны быть имена реальных файлов. Поэто- му такие имена могут обрабатываться как особые случаи — их содержимое «известно» реализации С. Из этих соображений Standard С называет их стан- дартными заголовками (standard header), а не стандартными заголовочными файлами (standard header file). В данной книге мы будем ссылаться на них обоими способами. Включенный файл также может содержать директивы #include. Разрешен- ная глубина такого вложения зависит от реализации, но Standard С требует поддержки как минимум 8 уровней (в С99 — 15). Местоположение включен- ных файлов может повлиять на правила поиска для вложенных файлов. Пример Предположим, что мы компилируем программу first.с, которая расположена в ди- ректории \near. Файл first.с содержит следующие строки: // В /near/first.с #include "/far/second.h” которые определяют файл second.h, находящийся в директории /far. Заголовочный файл second.h содержит строки: // В /far/second.h #include "third.h" определяющие файл third.h без явного указания директории. Какой файл выберет реа- лизация С — тот который находится в исходной директории /near/third.h или тот, ко- торый находится в директории файла, который включил данный (/far/third.h)? Неко- торые реализации UNIX реализуют второй вариант. Оригинальная спецификация С правильным считает первый вариант. Большинство реализаций С дает программи- сту возможность самостоятельно определить список директорий, в которых будет осуществляется последовательный поиск для включенных файлов, если их директо- рии явным образом не были указаны. Ссылки: строковые константы 2.7.4; триграфы 2.1.4. 3.5 Условная компиляция Директивы условной компиляции позволяют препроцессору включать в текст программы или исключать из него строки исходного текста по резуль- татам оценки условия. 3.5.1 Директивы #if, #else и #endif Директивы препроцессора #if, #else и #endif совместно используются для включения или исключения строк исходного текста на основании выполнения (или невыполнения) определенных условий. Они используются следующим образом: #if константное-выражение последовательность-строк-1 #else последовательное тъ-строк-2 #endif
78 Глава 3 константное-выражение является объектом макрорасширения и его оценка должна представлять собой целое константное значение. Ограничения, накла- дываемые на выражение, обсуждаются в разделе 7.11.1. 'Последователъ- ность-строк может содержать любое количество строк текста любого типа, даже другие строки директив препроцессора, или не содержать строк вообще. Директива #else может быть опущена со строками, следующими за ней, что эквивалентно включению директивы #else с пустой последовательностью строк. Последовательность-строк может также включать одну и более конст- рукций #if-#else-#endif. Описанная выше конструкция обрабатывается так, что одна последова- тельность-строк будет включена для компиляции, а другая — пропущена. Сначала в директиве #if вычисляется константное-выражение. Если его зна- чение не 0, то последовательность строк-1 включается в компиляцию, а по- следовательность строк-2 (если присутствует) — исключается. Иначе после довательность-строк-1 исключается, а если присутствует директива #else, то последовательность строк-2 — включается. Но если директива #else отсутст- вует, то в компиляцию не включается ни одна последовательность строк. Кон- стантные выражения, которые могут использоваться в директиве #if, детально обсуждаются в разделах 3.5.4 и 7.11. Препроцессором не обрабатываются последовательности строк, исключен- ных из компиляции средствами условной компиляции. Соответственно, в них игнорируются директивы препроцессора, и не выполняется макрозамена. В этом правиле есть одно исключение, состоящее в том, что в опущенных стро- ках распознаются директивы #if, #ifdef, #ifndef, #elif, #else и #endif с единст- венной целью — подсчета. Это важно для контроля правильности вложения ди- ректив условной компиляции. Это распознавание, однако, предполагает, что опущенные строки сканируются и разбиваются на лексемы, а строковые кон- станты и комментарии распознаются и должны быть ограничены корректно. Если в директиве #if или в #elif константное-выражение содержит имя мак- роса, который не был определен, то он заменяется целой константой 0. Это озна- чает, что директивы ”#ifdef имя" и "#if имя" будут действовать одинаково, если имя указанного определенного макроса имеет константное арифметическое нену- левое значение. На наш взгляд, в таких случаях использование директивы #ifdef значительно нагляднее, но Standard С разрешает и использование #if. Ссылки: операция defined 3.5.5; директива #elif 3.5 2; директива Wifdef 3.5.3. 3.5.2 Директива tfelif Директива #elif представлена, как в Standard С, так и в большинстве ком- пиляторов, выпущенных до стандарта ISO. Она удобна, так как опа упрощает формирование условных препроцессорных выражений. Формат директивы #elif следующий: #if константное выражение-1 (или #ifdef или #ifndef) последовательность строк-1 #elif константное-выражение-2 последовательность-строк-2 #elif константное-выражениеп последовательное ть-строкп
Препроцессор С 79 #else послед няяпоследовательность-строк #endif Эта последовательность директив обрабатывается таким образом, что для компиляции будет включена только одна последовательность-строк, а все ос- тальные — опущены. Сначала в директиве #if вычисляется константное-вы- ражение-1. Если оно не нулевое, то для компиляции включается последова- тельность-строк-1, а все остальные последовательности строк вплоть до ди- рективы #endif опускаются. Если в #1£-директиве константное-выражение-1 равно нулю, тогда вычисляется выражение в первой директиве #elif; если вы- численное значение не равно нулю, то для компиляции включается последова- телъность-строк-2. В общем случае, по порядку вычисляется констант- ное-выражение-i, пока не будет найдено ненулевое значение; затем препроцес- сор включает для компиляции последовательность строк, следующих за директивой, содержащей ненулевое значение, игнорируя в последовательно- сти директив остальные константные выражения и следующие за ними после- довательности строк. Если ни одно из константное-выражение-i не содержит ненулевое значение, то в предназначенный для компиляции текст включается фрагмент, находящийся за директивой #else. Если же директива #else отсут- ствует, то в подлежащий компиляции текст не включается ни одна последова- тельность строк. Константные выражения, которые могут быть использованы для директивы #else, не отличаются от тех, которые используются в директи- ве #if (см. раздел 3.5.4 и раздел 7.11). Среди последовательностей опущенных строк директива #elif распознается точно так же, как и директивы #if, #ifdef, #ifndef, #else и #endif. Это распо- знавание выполняется с целью подсчета таких директив, что необходимо для контроля корректности вложенности условных директив компиляции. В константных выражениях могут использоваться макровызовы, так как макрозамена выполняется и для части строки, которая следует за директивой #elif. Пример Хотя использование директивы #elif удобно во многих случаях, ее можно заменить соответствующей конструкцией из директив #if, #else и #endif. Пример приведен ниже. С использованием #elif Без использования #elif #if константное-выражение-1 последовательность-строк-1 #elif константное-выражение-2 последовательность-строк-2 #else последняя-последовательность-строк #endif # if константное-выражение-1 последовательность-строк- / #else #if константное-вырожение-2 последовотельность-строк-2 #else последняя-последовательность-строк #endif #endif
80 Глава 3 3.5.3 Директивы #ifdef и tfifndef Директивы #ifdef и #ifndef используются для определения того, является ли данное имя макросом. Строка вида #ifdef имя эквивалентна по смыслу строке # if 1 если имя было определено (даже с пустым телом), и эквивалентна # if 0 если имя не было определено или было сделано неопределенным с помощью директивы #undef. Директива #ifndef имеет противоположный смысл; ее зна- чение истинно, когда имя не было определено, и ложно, когда оно было опре- делено. Заметьте, что директивы #ifdef и #ifndef тестируют только те имена, кото- рые были определены директивой #define (или сделаны неопределенными ди- рективой #undef); эти директивы не тестируют имена, определяемые в тексте компилируемой программы. (Некоторые реализации С разрешают определе- ние имен с помощью специальных параметров командной строки для компи- лятора.) Пример В программах, написанных на С, директивы #ifdef и #ifndef принято использовать в нескольких вариантах стилизованной нотации. Первый вариант: препроцессор- ный перечислительный тип реализуется с помощью набора идентификаторов, из ко- торых определяют только один. Предположим, например, что для определения ап- паратной платформы, для которой компилируется данная программа, мы хотим ис- пользовать набор имен: VAX, PDP11 и CRAY2. Можно определить все имена, причем одно из них со значением 1, а остальные — с 0: # define VAX 0 tydefine PDP11 0 # define CRAY2 1 После этого выбор варианта компиляции машинно-зависимого исходного кода про- граммы может быть осуществлен следующим образом: # if VAX исходный текст для VAX #endif # if PDP11 ИСХОДНЫЙ TeKCT для PDP11 #endif # if CRAY2 исходный текст для CRAY2 tfendif Однако более привычен стиль, предполагающий определение одного идентификато- ра: # define CRAY2 1 /* Другие идентификаторы не определены */ Тогда условные директивы тестируют, были ли определены отдельные идентифика- торы: tfifdef VAX исходным vexcT для VAX
Препроцессор С 81 «endif «ifdef PDP11 исходный код текст PDP11 «endif It if def CRAY2 исходный текст для CRAY2 «endif Пример Макрокоманды ©ifndef и #ifdef также используются для записи определений по умолчанию для макросов. Например, библиотечный файл может содержать некото- рое определение для имени, при условии, что данное имя не было определено иначе: «ifndef TABLE_SIZE «define TABLE_SIZE 100 «endif static int internal_table[TABLE_SIZE]; Программа может просто включать этот файл: «include <table.h> В этом случае значение TABLE_SIZE будет равно 100 как в библиотечном файле, так и после директивы ©include. Но если программа будет содержать перед вызовом библиотечного файла определение имени TABLE_SIZE: «define TABLE_SIZE 500 «include <table.h> то значение имени TABLE_SIZE будет равна 500 как до вызова библиотечного фай- ла, так и после него. Весьма распространена следующая ошибка: программист пытается выяс- нить, является ли имя определенным, пользуясь директивой "#if имя", а не ”#ifdef имя" или "#if defined(uj«^)". Такая некорректная проверка наличия определения заданного имени часто дает верные результаты из-за того, что препроцессор заменяет в директиве #if значением 0 любое имя, которое не было определено как макрос. Поэтому, если имя не было определено, то все три формы эквивалентны. Однако если имя было определено со значением 0, то директива "#if имя" будет давать результат проверки «ложь», хотя имя и было определено. Аналогично, если имя было определено со значением, ко- торое не является корректным выражением, то строка " if имя " вызовет ошибку. Ссылки: директива ©define 3.3; операция defined 3.5.5; директива ©include 3.4; лексиче- ские соглашения препроцессора 3.2; директива ©undef 3.3. 3.5.4 Константные выражения в директивах условной компиляции В разделе 7.11.1 описаны выражения, которые могут использоваться в ди- рективах #if и #elif. Эти выражения могут включать целые константы и все арифметические, битовые, логические операции, а также операции отноше- ния.
82 Глава 3 Стандарт С99 определяет, что все препроцессорные арифметические опера- ции должны выполнятся с использованием наибольшего целого типа целевого компьютера, который определен как intmax_t или uintmax_t в заголовочном файле stdint.h. Предыдущие версии Standard С не требовали от транслятора учета арифметических свойств целевого компьютера. Ссылки: intmax_t 21,5; uintmax_t 21 5 3.5.5 Операция defined Операция defined может быть использована только в выражениях содержа- щихся в директивах #if и #elif. Соответствующее выражение может иметь следующие формы: defined имя defined(n.wi) Значение этого выражения считается равным 1, если имя было определено и 0 в противном случае. Пример Операция defined позволяет программисту писать #if defined(VAX) вместо #ifdef VAX Операция defined зачастую более удобна, так как он дает возможность строить весь- ма сложные выражения: #if defined(VAX) && !defined(UNIX) && debugging 3.6 Явная нумерация строк Директива препроцессора #line информирует компилятор С о том, что ис- ходная программа была сгенерирована другим инструментарием, и показыва- ет соответствие позиций сгенерированного исходного текста программы стро- кам файла изначальной пользовательской программы, из которой и была сформирована исходная С-программа. Директива #Iine может иметь одну из двух форм. Форма # line п "имя-файла" показывает, что следующая исходная строка была получена из строки п. поль- зовательского исходного файла программы с именем имя-файла. п должно быть последовательностью десятичных цифр. Форма # line п показывает, что следующая исходная строка была получена из строки п поль- зовательского исходного файла, который последним упоминался в директиве #line. Наконец, если формат директивы #line не совпадает ни с одной из вы- шеперечисленных, то такая директива интерпретируется как # line лексеми-препроцессора
Препроцессор С 83 После выполнения макрозамены для последовательности лексемы-препро- цессора, результат должен совпадать с одной из двух описанных ранее форм. Информация, которую формирует директива #line, используется для уста- новки значений предопределенных макросов_LTNE___и___FILE___. В против- ном случае ее поведение не определено и компиляторы могут ее проигнориро- вать. Обычно, такая информация используется для диагностических сообще- ний. Некоторые программные средства, генерирующие исходный текст, могут использовать директиву #line для того, чтобы привязывать сообщения об ошибках к строкам обрабатываемого, а не генерируемого файла. Некоторые реализации С позволяют использовать препроцессор независимо от остальной части компилятора. Действительно, иногда препроцессор пред- ставляет собой отдельную программу, которая генерирует промежуточный файл, который затем обрабатывается настоящим компилятором. В таких слу- чаях препроцессор может вставлять директивы #line в промежуточный файл. Тогда компилятор должен правильно распознавать эти директивы, даже если он не распознает никаких других. Способ генерирования препроцессором ди- ректив #line зависит от реализации. Зависимым от реализации также являет- ся способ обработки препроцессором директив #Iine, которые поступают ему на вход. Старые версии С позволяют использовать символ "#" как синоним директи- вы #line, разрешая такие директивы, как: # л имя-файла Этот синтаксис считается устаревшим и не разрешен в Standard С, но мно- гие реализации продолжают его поддерживать с позиции совместимости. Ссылки:__FILE_3.3.4;_LINE_3.3.4. 3.7 Директива pragma Директива #pragma относится к нововведениям Standard С. За именем ди- рективы может следовать любая последовательность лексем: #pragma лексемы-препроцессора Директива #pragma может использоваться реализациями С для расшире- ния функциональности препроцессора или передачи компилятору информа- ции, которая зависит от реализации. На информацию, которая следует за ди- рективой #pragma, никаких ограничений не накладывается. Реализациям следует игнорировать ту информацию, которую они не понимают. Аргумент директивы #pragma относится к объектам макрорасширения. Очевидно, существует опасность того, что две разные реализации получат несовместимые интерпретации одной и той же информации, поэтому разумно использовать директиву #pragma в условных конструкциях, учитывающих специфику применяемого компилятора. Пример Следующий исходный код перед использованием директивы #pragma делает про- верку компилятора (tec), компьютера и соответствия реализации Стандарту:
84 Глава 3 #if defined(_ТСС) && defined)___STDC___) && defined(vax) ((pragma bulltin(abs), inline(myfunc) ffendif Ссылки: операция defined 3 5.3; модели памяти 6.1 5; директива #if 3.5 1. 3.7.1 Стандартные директивы #pragma В Стандарте С99 некоторые директивы #pragma введены с определенными значениями. Их можно отличить от остальных директив #pragma, поскольку стандартные директивы записываются с предшествующей лексемой STDC. Например, директива ttpragma FENV_ACCESS ON относится к прагмам, определяемым реализацией, в то время как директива tfpragma STDC FENV_ACCESS ON определяет прагму FENV_ACCESS Стандарта С99. Для реализации желатель- но предусмотреть выдачу предупреждения в случае, если имя стандартной прагмы используется без предшествующей ей лексемы STDC, так как это одна из самых распространенных ошибок. Стандарт С99 определяет следующие прагмы: FP_CONTRACT, FENV_AC- CESS и CX_LIMITED_RANGE. Все они принимают в качестве аргумента двух позиционный-ключ: двухпозиционнный-ключ: ON OFF DEFAULT Аргумент DEFAULT устанавливает прагм}' в значение по умолчанию (ON или OFF). Для каждой стандартной прагмы определяется свое значение по умолчанию (иногда они определяются реализациями). Ссылки: CX_LIMITED_RANGE 23 2; FENV_ACCESS 22 2 3.7.2 Размещение стандартных прагм Стандартные прагмы должны следовать некоторым основным правилам с целью упрощения их обработки и разрешения вложенности. Стандартные прагмы могут появляться в двух местах: на верхнем уровне трансляционного модуля перед любым внешним объявлением, или перед всеми явными объяв- лениями и операторами в начале составного выражения. Когда прагма помещена на верхнем уровне, то она действует вплоть до кон- ца данного трансляционного модуля, либо до места появления другого экземп- ляра этой же директивы. Эта вторая прагма гложет находиться на верхнем уровне трансляционного модуля, причем в этом случае она отменяет действие первой прагмы, или она может входить в составное выражение. Если прагма помещена в начале составного выражения, то ее областью дей- ствия является это составное выражение, или она действует до места появле- ния другого экземпляра этой прагмы внутри составного выражения. Эта вто- рая прагма может находиться, как в начале этого составного выражения (при-
Препроцессор С 85 чем в этом случае она отменяет действие первой), так и во внутреннем составном выражении. В конце составного выражения, содержащего стандарт- ную прагму, прагма восстанавливает свое состояние, в котором она находилась перед составным выражением. По сути, вложение стандартных прагм следует обычным правилам блочной области видимости переменных, за тем исключе- нием, что они могут быть определены более одного раза на одном уровне вло- женности. Ссылки: область видимости 4.2.1. 3.7.3 Операция _Ргадта В стандарт С99 добавлена операция -Pragma, которая придает директиве #pragma большую гибкость. После макрорасширения выражения вида _Pragma( "строковый-литерал" ) результат интерпретируется так, как если содержимое строкового литерала (после удаления внешних ссылок, замены \” на ’’ и \\ на \) было бы препроцес- сорными лексемами, находящимися в директиве #pragma. Например, выра- жение _Pragma("STDC FENV_ACCESS ON") интерпретировалось бы так, если на его месте была следующая прагма: #pragma STDC FENV_ACCESS ON В то время как директива #pragma должна располагаться в отдельной стро- ке и ее препроцессорные лексемы не подлежат макрорасширению, операция —Pragma может входить в выражения и быть объектом макрорасширения. 3.8 Директива #еггог Директива #еггог относится к нововведениям Standard С. За именем этой директивы может находиться любая последовательность лексем: #еггог препроцессорные-лексемы Директива #еггог генерирует сообщение об ошибке времени компиляции, которое включает лексемы аргумента, являющиеся объектом макрорасшире- ния. Пример Директива #еггог полезна при поиске и идентификации ошибок программирования и нарушений ограничений во время препроцессирования. Приведем пример: #if defined(A_THING) SS defined(NOT_A_THING) #error Несовместимые понятия! ffendif #include "sizes.h" /* Определяет SIZE */ if (SIZE % 256) != 0 terror "SIZE должен быть кратен 256!" ttendif
86 Глава 3 В первом примере применения директивы #еггог мы не оформляли аргумент в виде строковой константы, в то время как во втором примере — оформили, так как не хо- тели, чтобы в выходном потоке было осуществлено макрорасширение лексемы SIZE. Ссылки: операция defined 3.5.3, директива #if 3.5.1. 3.9 Совместимость с C++ C++ использует препроцессор стандарта С89, поэтому между С и C++ есть некоторые различия. 3.9.1 Предопределенные макросы Макрос __cplusplus предопределен реализациями C++ и поэтому может быть использован в исходных файлах, ориентированных как на среду С так и на среду C++. Данное имя не отвечает лексическим соглашениям Standard С для преопределенных макросов, но оно совместимо с существующими реали- зациями C++. В Standard C++ его значением служит номер версии, такой как 199711L. Определения макроса_STDC_в средах C++ зависят от реализации. Меж- ду Standard С и C++ так много различий, что даже не понятно, должен ли мак- рос _STDC__быть определенным вообще. Ни один из макросов, введенных Стандартом С99 (таблица 3.2) не присутст- вует в C++. Пример Совместимость вашей среды с традиционным С, Standard С и C++ можно проверить следующим образом: tfifdef _cplusplus /* Это компиляция C++ */ #else tfifdef _STDC__ /* Это компиляция Standard С */ #else /* Это нестандартная компиляция */ tfendif #endif Если вы знаете, что ваша реализация С соответствует Standard С, то проверка может быть сведена к #if defined(___aplusplus) /* Это компиляция C++ */ #else /* Это компиляция Standard С */ ffendif Ссылки: _STDC_ 3 3 4; _STDC_VERSION 3 3 4
Введение в компьютеры, Internet и World Wide Web 87 ЗЛО Упражнения 1. Какое из следующих макроопределений Standard С (возможно) непра- вильное? Почему? Какое из определений может вызвать проблемы в традиционном С? (a) #define ident (х) х (b) #define FIVE = 5; (с) #define PLUS + (d) #define void int 2. Ниже приведены макроопределения и их вызовы. Как будет выпол- няться макрорасширение вызовов в стандартном и традиционном С? Определение Вызов (a) #define sum(a,b) a+b sum(b,a) (b) #define paste(x.y) x/**/y paste(x,4) (c) #define str(x) # x str(a book) (d) #define free(x )x ? free(x) : NULL free(p) 3. Ниже приведены два заголовочных файла и файл программы. Что бу- дет в результате обработки препроцессором файла программы? /* Файл blue.h * /int blue = 0; #include "red.h" /* Файл red.h */ #ifndef __red__ #define __red__ #include "blue.h" int red = 0; #endif /* Файл test.h */ #include "blue.h" #include "red.h" 4. Вот определение макроса, который должен удваивать свой числовой ар- гумент. Что не так в этом макросе? Перепишите его так, чтобы он вы- полнял предполагаемую операцию корректно. #define DBL(a) а+а 5. Ниже приведен фрагмент исходной программы на С, отвечающей Standard С. Какой результат будет при макрорасширении М(М)(А,В)? #define М(х) М ## х #define ММ(М, у) М = # у М(М) (А,В) 6. Напишите последовательность директив препроцессора, которая приве- дет программу Standard С к ошибке компиляции, если макрос SIZE не был определен или если его значение не находится в промежутке от 1 до 10.
88 Глава 3 7. Приведите пример последовательности символов, которая является единой лексемой для препроцессора, но не является таковой для ком- пилятора С. 8. Что не так в следующем фрагменте программы? if (х != 0) у = z/x; else # error "Попытка деления на ноль, строка " LINE
Глава 4 Объявления Объявление имени (name declaration) в С осуществляется связыванием (ас- социированием) идентификатора с некоторым объектом С. В роли объекта мо- жет выступать, например, переменная, функция или тип. Имена, которые мо- гут быть объявлены в С относятся к следующим видам: • переменные, • функции, • типы, • теги типов, • компоненты структуры или объединения, • константы перечисления, • метки операторов, • макросы препроцессора. За исключением меток операторов и макросов препроцессора, все идентифи- каторы объявляются их появлением в объявлениях (declaration). Переменные, функции и типы определяются в описателях (declarator) внутри объявлений, а компоненты объединений и структур, теги типов и константы перечисления определяются в соответствующих спецификаторов типов (type specifier) в объ- явлениях. Метки операторов объявляются появлением в функциях, а макросы препроцессора объявляются директивой препроцессора #define. Объявления в С трудно описывать по нескольким причинам. Во-первых, они используют необычный для С синтаксис, который может смутить нович- ка. Например,объявление int (*f)(void); объявляет указатель на функцию, которая не принимает аргументов и возвра- щает целое. Во-вторых, многие абстрактные свойства объявлений, такие как область видимости и время жизни, в С сложнее, чем в других языках программирова- ния. Перед переходом непосредственно к синтаксису объявлений, мы обсудим эти свойства в разделе 4.2. И, наконец, некоторые нюансы объявлений в С сложно понять без знания сис- темы типов С, которая описана в главе 5. В частности, обсуждение тегов типов, компонентов структур, объединений и констант перечисления оставлено для пя- той главы, хотя некоторые свойства этих объявлений обсуждаются в этой главе. Ссылки: тип перечисления 5,5; директива препроцессора #define 3,3; метки операторов 8.3; структурные типы 5.6; спецификаторы типов 4,4, типы объединения 5.7.
90 Глава 1 4.1 Формат объявлений Объявления могут появляться в разных местах программы, и их свойства меняются в зависимости от места их появления. Исходный файл С или модуль трансляции (translation unit) состоит из последовательности объявлений верх- него уровня (top-level declaration): объявлений функций, переменных и других объектов. Каждая функция включает объявление параметров (parameter de- claration) и тело (body); тело в свою очередь может содержать отдельные бло- ки (block), включая составные операторы. Блок может содержать последова- тельность внутренних объявлений (inner declaration). Базовый синтаксис объявлений приведен ниже. Обсуждение определения функции отложено до главы 9. объявление: спецификаторы объявления инициализированный-список-описателей : спецификаторы-объявления: спецификатор-класса-памяти спецификаторы-объявления,„щ спецификатор-типа спецификаторы-объявления()11ц квалификатор-типа спецификаторы-объявления,;,,., спецификатор-функции спецификаторы-объявленияогщ (С99) инициализированный-список-описателей: инициализированный-описатель инициализированный-список-описателей , инициализированный-описатель инициализированный-описатель: описатель = инициализатор В спецификаторах-объяв ления задается не более одного спецификато ра-класса-памяти и одного спецификатора-типа, хотя спецификатор типа может состоять из нескольких лексем (например, unsigned long int). В стан- дарте С99 спецификатор типа является обязательным. В спецификаторах-объ- явления каждый квалификатор-типа может указываться не более одного раза. Спецификатор функции (inline) Стандарта С99 может указываться толь- ко в объявлениях функций. С учетом этих ограничений спецификаторы типов, спецификаторы класса памяти и квалификаторы типов могут задаваться в спе- цификаторах-объявления в любом порядке. Пример Принято при объявлении ставить первым спецификатор класса памяти, затем ква- лификатор тина и, наконец, спецификатор типа. В приведенных ниже объявлениях i и j имеют один и тот же тип и класс памяти, однако стиль объявления i лучше. unsigned volatile long extern int const j; extern const volatile unsigned long int i; Ссылки: описатели 4.5, выражения гл. 7; определения функции гл 9, инициализаторы 4 6; опе- раторы гл. 8, спецификаторы клосса памяти 4.3; спецификаторы и квалификаторы типа 4.4
Объявления 91 4.2 Терминология Этот раздел устанавливает некоторую терминологию, используемую для описания объявлений. 4.2.1 Область видимости Областью видимости (scope) объявления является часть программы, в ко- торой это объявление видимо. В С идентификаторы могут иметь один из шести видов области видимости, перечисленных в таблице 4.1. Таблица 4.1. Области видимости идентификаторов Вид Видимость объявления Идентификаторы верхнего уровня От точки объявления (раздел 4.2.3) до конца фойла исходной программы. Формальные параметры в определении функции Формальные параметры в прототипох функций1 Блочные (локальные) идентификаторы Метки операторов Макросы препроцессора От точки объявления до конца тела функции. От точки объявления до концо прототипо. От точки объявления в блоке до конца блока. Все тело функции, в которой были определены. От директивы #define, которая объявляет макрос, до конца исходного файло программы или до первой директивы #undef, которая отменяет определение макроса. 1 Нововведение Стандарта С. Часто говорят, что непрепроцессорные (nonpreprocessor) идентификаторы, объявленные внутри определения функции или блока (включая формальные параметры) обладают блочной (block) или локальной (local) областью видимо- сти. Идентификаторы, объявленные в прототипах, имеют область видимо- сти прототипа (prototype). Метки операторов имеют область видимости функции (function). Остальные идентификаторы имеют область видимости файла (file). Чаще всего блок представляет собой составной оператор. В Стандарте С99 предусмотрены неявные (implicit) блоки, связанные с операторами выбора и итераций. Область видимости любого идентификатора С ограничена исходным фай- лом, в котором появился этот идентификатор. Однако некоторые идентифика- торы могут быть объявлены внешними (external). В этом случае, объявления одного и того же идентификатора в двух и более файлах могут быть связаны, как показано в разделе 4.8 Ссылки: директива #define 3.3; внешние имена 4.8; прототипы 9.2; директива #undef 3.3 4.2.2 Видимость Объявление идентификатора видимо (visible) в некотором контексте, если использование этого идентификатора в данном контексте будет относиться к данному объявлению (то есть идентификатор будет связан с этим объявлени-
92 Глава 1 ем). Объявление может быть видимым в своей области видимости, но может быть также скрыто другим объявлением, чья область видимости перекрывает область видимости первого объявления. Пример В следующей программе объявление идентификатора foo, как переменной целого типа, скрыто внутренним объявлением foo, как переменной с плавающей точкой. Внешняя переменная foo невидима только внутри тела функции main. int foo =10; /* foo определена на верхнем уровне */ int main(void) { float foo; /* эта переменная foo скрывает внешнюю переменную foo */ ) В С объявления в начале блока могут скрыть объявления вне блока. Для того чтобы одно объявление скрыло другое, необходимо чтобы объявленные идентификаторы были идентичными, то есть они должны принадлежать к од- ному классу перегрузки (overloading class) и они должны быть объявлены в двух различных областях видимости, причем одна область должна содер- жать другую. В Стандарте С область видимости объявлений формальных параметров в оп- ределении функции такая же, как область видимости идентификаторов, объ- явленных в начале блока, который формирует тело функции. Однако некото- рые более ранние реализации С рассматривали область видимости параметров как объемлющую для блочной области видимости. Пример Следующее переопределение переменной х в Standard С является ошибкой, но в не- которых старых реализациях С вполне корректно, что может служить причиной маскировки ошибки. int f(x) int х; ( long х = 34; /* Неверно ? */ return х; } Ссылки: блок 8.4; класс перегрузки 4.2.4; объявления параметров 9.3; объявления верхнего уровня 4.1. 4.2.3 Ссылки вперед До того как идентификатор полностью объявлен, он не может быть нор- мально использован. Если быть точным, то мы определяем в качестве точки объявления (declaration point) идентификатора конец описателя, который со- держит лексему идентификатора. После точки объявления использование идентификатора разрешено. В примере, приведенном ниже, переменная цело- го типа intsize инициализируется значением своего размера, так как intsize в инициализаторе используется уже после точки объявления:
Объявления 93 static int intsize = sizeof(intsize); Когда идентификатор используется перед своим полным объявлением, то говорят, что появилась опережающая ссылка или ссылка вперед (forward reference). С разрешает ссылки вперед в трех случаях: 1. Метка оператора может появляться в операторе goto до того места, где она была определена, так как ее область видимости распространяется на все тело функции: if (error) goto recover; recover: CloseFiles(); 2. He полностью объявленные структуры, объединения, массивы и типы перечисления могут быть использованы для некоторых целей до того, как будут полностью определены (раздел 5.6.1). 3. Функция может быть объявлена отдельно от своего определения, вме- сте с определением или неявно, посредством вызова функции (раздел 4.7 и 5.8). Стандарт С99 не разрешает неявно объявлять функцию с по- мощью ее вызова. Пример В этом примере проиллюстрированы неправильные опережающие ссылки. Про- граммист хочет определить с помощью объявления typedef структуру со ссылкой на себя. В данном случае, последнее возникновение cell в строке является местом объ- явления и поэтому использование cell внутри структуры неверно. typedef struct ( int Value; cell *Next; ) cell; Правильное объявление такого вида базируется на использовании структурного тега S, который определяется в месте своего первого упоминания и затем использу- ется внутри объявления: typedef struct S ( int Value; struct S *Next; ) cell; Дальнейшее обсуждение неявных и повторных объявлений приводится в разделах 4.7 и 4.2.5 соответственно. Ссылки: повторные объявления 4.2.5; типы функций 5.8; оператор goto 8.10; неявные объяв- ления 4.7; типы указателей 5.3, типы структур 5.6. 4.2.4 Перегрузка имен В С и в других языках программирования один и тот же идентификатор мо- жет быть связан в данный момент времени с более чем одним объектом про- граммы. Когда это происходит, то говорят, что имя перегружено (overloaded), а выбор связи определяет контекст, в котором это имя используется. Напри- мер, идентификатор может быть именем переменной и именем тега структу- ры. Когда такой идентификатор встречается в выражениях, то используется для связи с переменной; когда используется в спецификаторе типа, то исполь- зуется для связи с тегом. Для имен в С существует пять классов перегрузки (overloading class). (Мы иногда ссылаемся на них, как на пространства имен (name space).) Все они перечислены и описаны в таблице 4.2.
94 Глава 1 Таблица 4.2. Классы перегрузки Класс Включенные в класс идентификаторы Имена макросов препроцессора Так кок препроцессирование осуществляется до компиляции, то имена, используемые препроцессором, независимы ат любых других имен в программе. Метки операторов Именованные метки операторов являются частью операторов. Определение меток оператора всегда следует за символом двоеточия : {и не является частью меток оператора case). Используются метки операторов сразу зо зарезервированным словом goto. Теги структур, объединения и перечисления Эти теги являются частью спецификатора структуры, объединения или типа перечисления. Если они есть, то всегда следуют за зарезервированными славами struct, union или enum Имена компонентов («В Standard С — члены») Имя компонента определено в пространстве имен, связанном с соответствующим типом структуры или объединения. Поэтому, один и тот же идентификатор может быть именем компонента любого количества структур или объединений в одно и тоже время, Определение имени компонента всегда осуществляется внутри спецификатора типа структуры или объединения. Использование имени компонента всегда следует за операцией выбора или Другие имена Все другие имена попадают в класс перегрузки, который включает в себя переменные, функции, имена, определенные спецификатором typedef, и константы перечисления. Эти правила перегрузки немного отличаются от тех, которые были изна- чально введены в С. Во-первых, метки операторов существовали в том же про- странстве имен, что и обычные идентификаторы. Во-вторых, все имена компо- нентов структур и объединений помещались в одно пространство имен, вместо раздельных пространств имен для каждого типа. Когда имя перегружено с несколькими связями, то каждая связь имеет свою собственную область видимости и может быть спрятана от других объяв- лений независимо от других связей. Например, если идентификатор использу- ется и как переменная и как тег структуры, то внутренний блок может переоп- ределить связь переменной без изменения связи тега. C++ вводит теги структур и объединений в пространство имен «другие име- на» (раздел 4.9.2). Ссылки: имена компонентов 5 6.3; повторное определение 4.2.5; теги перечисления 5.5; опе- ратор goto 8.10, операция выбора 7 4.2, метки операторов 8.10; теги структур 5 6; специфи- каторы типа структур 5.6; имена typedef 5.10; теги объединений 5.7; спецификаторы типа объединения 5.7. 4.2.5 Повторные определения Создание двух объявлений одного имени (в одном классе перегрузки) в од- ном блоке или на верхнем уровне является ошибочным. Такие объявления на- зываются конфликтными. Пример В приведенном ниже фрагменте исходной программы два объявления идентифика- тора howmany конфликтны. А два объявления идентификатора str не являются та- ковыми, так как находятся в разных пространствах имен.
Объявления 95 extern int howmany; extern char str[10); typedef double howmany (); extern struct str (int a, b; ) x; У запрета на повторные объявления есть два исключения. Первое состоит в том, что может существовать любое количество внешних (ссылочных) объяв- лений для одного имени, если эти объявления задают имени один и тот же тип в каждом экземпляре. Это исключение отражает мнение, что двойное объявле- ние библиотечной функции не должно вызывать ошибок. Второе исключение: если идентификатор объявлен как внешний, то за эти объявлением может следовать определение (раздел 4.8) имени далее в програм- ме, если определение задает имени такой же тип, как внешнее объявление (объявления). Это исключение позволяет пользователю создавать для функ- ций и переменных корректные опережающие ссылки. Пример Мы определяем две функции f и g, которые ссылаются друг на друга. В обычных ус- ловиях использование функции f внутри g приведет к неправильной опережающей ссылке. Однако поставив перед определением функции g внешнее объявление функ- ции f, мы даем компилятору достаточное количество информации для того, чтобы он мог корректно скомпилировать функцию g. (Без предварительного объявления f, однопроходный компилятор при компиляции функции g не располагает сведения- ми о том, что функция f возвращает значение типа double). extern double f(double z) ; double g(double x, double y) { • • f(x-y) • . } double f(double z) { ... g(z, z/2.0) . . . } Ссылки: определяющие и ссылочные объявления 4.8; класс памяти extern 4.3; опережаю- щие ссыпки 4.2; класс перегрузки 4.2; класс памяти static 4.3. 4.2.6 Дублирование области видимости Так как правила блочной видимости С определяют, что область видимости имени начинается с точки его объявления, а не с начала блока, в котором оно определено, то может возникнуть ситуация, в которой имеют место ссылки на два неконфликтующих объявления в разных частях одного и того же блока. Пример В следующем фрагменте исходной программы использованы две переменных i, на которые ссылаются в блоке, обозначенном меткой В. Переменная i целого типа объ- явлена во внешнем блоке и используется для инициализации переменной j. А затем объявляется переменная i с плавающей точкой, которая скрывает первое объявле- ние i.
96 Глава 1 ( int i 0; в: { int j = i; float i = 10.0; Ссылка на i для инициализации j неоднозначна. Какая из объявленных переменных i используется для инициализации? Большинство компиляторов С поймут текст так, как мы описали выше. Первая ссылка на i в блоке В будет связана с внешним определением i, а затем будет выполнено переопределение i, которое скроет внешнее определение для остальной части блока. Так трактует этот текст Стандарт С. Мы считаем, что это пример плохого стиля программирования, и подобных конструк- ций следует избегать. 4.2.7 Время жизни Для переменных и функций, в отличие от типов, во время исполнения про- граммы определено такое понятие, как существование, то есть для их хране- ния выделяется определенное место в памяти. Время жизни (extent или lifetime) этих объектов — это период времени, в течение которого этим объек- там выделено место для хранения. Стандарт С для обозначения этого понятия использует термин интервал хранения (storage duration). Говорят, что объект является статическим или принадлежит статическо- му экстенту (static extent), если он хранится в памяти с момента начала вы- полнения программы (или и до этого момента) и вплоть до окончания. Все функции в С статические. К этому же экстенту принадлежат переменные, объ- явленные на верхнем уровне объявлений. Переменные, объявленные в блоках, также могут быть статическими в зависимости от объявления. Говорят, что объект является локальным или принадлежит локальному экстенту (local extent), когда он создается при входе в блок или функцию и уничтожается при выходе из данного блока или функции. Если у перемен- ной есть инициализатор, то она инициализируется при каждом создании. Формальные параметры принадлежат локальному экстенту, а переменные, объявленные в начале блока могут быть локальными в зависимости от объяв- ления. В С переменные, принадлежащие локальному экстенту, называются автоматическими (automatic). Наконец, в С допустимо создание объектов, принадлежащих динамическо- му экстенту (dynamic extent), то есть объектов, которые создаются и уничто- жаются по явному указанию программиста. Однако динамические объекты создаются с помощью специальных библиотечных функций, таких как malloc и поэтому не рассматриваются как часть языка С. Ссылки: класс памяти auto 4.3; инициализаторы 4.6; функция malloc 16.1; класс памяти static 4.3; функции для работы с памятью 16.1 4.2.8 Инициализирующие значения Выделение памяти для переменной не обязательно устанавливает ее на- чальное значение. Большинство объявлений переменных в С допускают нали-
Объявления 97 чие инициализатора (initializer) — выражения, используемого для задания начального значения переменной во время выделения этой переменной памя- ти. Если для локальной переменной инициализатор не указан, то ее значение после создания непредсказуемо. (Статические переменные инициализируются по умолчанию нулем.) Важно помнить, что статическая переменная, однажды получив инициали- зирующее значение, сохраняет его даже когда программа выполняется вне об- ласти видимости данной переменной. Пример Во фрагменте исходной программы, приведенном ниже, в начале блока объявлены две переменные L и S, и обе проинициализированы нулевыми значениями. Обе пе- ременные имеют локальную область видимости, но S является статической, a L — локальной (автоматической). При каждом вхождении в блок значения обеих пере- менных увеличиваются на единицу и на экран выводятся новые значения. { static int S = 0; auto int L=0; L = L +1; L = L +1; S = S + 1; printf("L = %d, S = %d\n", L, S) ; } Какие же значения будут выведены на экран? Если блок будет выполнен многократ- но, то вывод будет следующим: L = 1, S = 1 L = 1, s = 2 L = 1, S = 3 L = 1, S = 4 У С есть одна опасная особенность, которая проявляется при инициализа- ции автоматических переменных, объявленных в начале блока. Инициализа- ция гарантированно выполняется только тогда, когда был осуществлен нор- мальный вход в блок, то есть управление было передано на начало блока. Од- нако с помощью использования меток операторов и оператора goto можно перейти в середину блока. Если такой переход был выполнен, то гарантии того, что автоматические переменные были проинициализированы, отсутству- ют. По сути, большинство реализаций, отвечающих и не отвечающих Стандар- ту, в такой ситуации инициализацию этих переменных не выполнят. В случае с оператором switch переход в блок, обозначенный меткой case или default, является нормальным, поэтому автоматические переменные, объявленные пе- ред первой меткой, не будут проинициализированы. Пример Инициализация переменной sum (возможно) не будет осуществлена, когда оператор goto выполнит переход к метке L. Это приведет к тому, что переменная sum будет иметь неопределенное значение. goto L; { static int vector[10] = {1,2,3,4,5,6,7,8,9,10};
98 Глава 1 int sum = 0; /* Суммирование элементов вектора "vector”. */ for ( i=0; i<10; i++ ) sum += vector[i]; printf("Сумма: %d", sum); Ссылки: оператор goto 8.10, инициализация переменных 4.6; классы памяти 4.3; оператор switch 8 7 4.2.9 Внешние имена Внешние (external) идентификаторы, или как их иногда называют — иден- тификаторы с внешним связыванием (external linkage), относятся к особой разновидности области видимости. Все экземпляры внешнего идентификатора среди всех файлов, составляющих программу на С, ссылаются на один и тот же объект или функцию и должны быть объявлены с совместимыми типами, иначе результат не определен. Внешние имена должны быть объявлены явно или неявно со спецификато- ром класса памяти extern, но не все имена, объявленные со спецификатором extern, относятся к внешним. Внешние имена часто объявляются на верхнем уровне программы и поэтому их областью видимости является файл. Однако реализации, не отвечающие Стандарту, различаются способом обработки внешних имен, объявленных внутри блока. Пример Приведенный ниже фрагмент программы применим для большинства компилято- ров С. В нем объявлено внешнее имя внутри блока и затем это имя используется вне данного блока: { extern int Е; ) Е = 1; Согласно обычным правилам блочной области видимости, объявление не должно быть видимым вне блока, но большинство реализаций С неявно присвоят Е файло- вую область видимости и скомпилируют данный фрагмент без ошибок. Стандарт С требует, чтобы объявление обладало блочной областью видимости, но не говорит о том, что фрагмент программы, приведенный ранее, следует считать ошибочным. Формально, поведение реализации в этом случае не определено, что позволяет соот- ветствующей Стандарту реализации считать такую программу корректной. Мы ду- маем, что программистам следует рассматривать этот фрагмент как содержащий ошибку программирования даже в том случае, когда компилятор считает его син- таксически правильным, и поведение этого фрагмента во время исполнения про- граммы корректно. Бесспорно ошибочными являются два объявления, определяющих несо- вместимые типы для одного и того же идентификатора в одном или разных файлах одной и той же программы.
Объявления 99 Пример В приведенной ниже программе два объявления переменной х не конфликтуют в ис- ходном файле, однако поведение этой переменной во время выполнения программы не определено: int f() ( extern int X; return X; ) double f () { extern double X; return X; } Ссылки: соглашения внешних имен 2.5; ссылки и определения внешних имен 4.8; область ви- димости 4.2,1; совместимость типов 5.11; видимость 4.2.2. 4.2.10 Имена времени компиляции До сих пор обсуждение было сфокусировано на переменных и функциях, которые существуют во время исполнения программы. Однако правила види- мости и области видимости аналогичным образом применимы и к идентифика- торам, связанным с объектами, которые не обязательно существуют во время исполнения программы. К ним относятся имена typedef, теги типов и констан- ты перечисления. При объявлении любого такого идентификатора его область видимости такая же, как и у переменной, которая была бы объявлена в том же месте. Макросы и метки также относятся к именам времени компиляции, но их правила области видимости отличны. Ссылки: константы перечисления 5.5; область видимости 4.2.1; структурный тип 5.6; имена typedef 5. Ю; видимость 4,2.2. 4.3 Спецификаторы класса памяти и функций Пришло время обсудить такие детали объявления, как спецификаторы класса памяти, квалификаторы и спецификаторы типа, спецификаторы функ- ций, описатели и инициализаторы. Спецификатор класса памяти (storage class specifier) определяет экстент объявляемого объекта (за исключением typedef, который является особым случаем). В объявлении может быть задано не более одного спецификатора класса памяти. Обычно в объявлениях спецификаторы класса памяти (если они задаются) ставят перед спецификаторами и квалификаторами типа. спецификатор-класса-памяти: один из auto extern register static typedef Назначение этих спецификаторов приведено в таблице 4.3. Обратите вни- мание на то, что не все спецификаторы разрешены в произвольном контексте объявления. Таблица 4.3. Спецификаторы класса памяти Спецификатор Использование auto Разрешен талька в объявлениях переменных внутри' блоков, Озночоет, что переменная является локальной (автоматической). (Так кок спецификатор класса памяти auto подразумевается по умолчанию, та он встречается в программах достаточно редко.)
100 Глава 1 Спецификатор Использование extern Может появляться как в объявлениях функций и переменных но верхнем уровне программы, так и в блоках*. Указывает, что объявленный объект является статическим и его имя известна компоновщику. См. раздел 4.8 register Может быть использован для объявлений локальных переменных или аргументов функций Этот спецификатор эквивалентен спецификатору auto, за исключением следующей особенности: он указывает компилятору на то, что данный объект будет часто использоваться, и его следует разместить с учетом минимизации времени доступа. static Может появляться в объявлениях функций и переменных. В определениях функций данный спецификатор используется только для тога, чтобы указать, что данное имя функции не будет передаваться компоновщику. В объявлениях функций донный спецификатор указывает, что объявленная функция будет определена с этим же спецификатором далее в файле. В объявлениях данных такой спецификатор указывает на то, что эти объявления не будут передаваться компоновщику. Переменные, объявленные с этим спецификатором, являются статическими (в отличие от локальных, обозначенных спецификатором auto). typedel Сообщает о том, что объявление определяет новое имя для типа данных, а не имя для переменной или функции. Имя типа данных появляется там, где должно появиться имя переменной в объявлении переменной. А сам па себе тип данных является типом, который присваивается имени переменной (см. раздел 5 Ю). ' Стандарт С99 разрешает объявления внутри блока в любом месте. Предыдущие версии Стандарта С разрешали объявления только до появления первого оператора. Стандарт С позволяет использовать спецификатор register для любого типа переменной или параметра, но для таких объектов не разрешено вычисление адреса, как явное (с помощью операции &), так и неявное (например, преобра- зованием имени массива к типу указателя при индексации массива). Различ- ные реализации С, не отвечающие Стандарту, ведут себя по разному: • Они могут ограничивать использование спецификатора register только объектами скалярных типов. • Они могут разрешать использование & для объектов со спецификатором register. • Они могут неявно расширять небольшие объекты, объявленные со специ- фикатором register (например, трактовать объявление register char х как register int х). Реализациям разрешено трактовать спецификатор класса памяти register как спецификатор, идентичный спецификатору auto. Однако программисты вправе ожидать, что использование спецификатора register для одной-двух часто используемых переменных в функции повысит производительность. Ис- пользование спецификатора register для многих объявлений скорее всего бу- дет неэффективным или будет снижать производительность. Использование спецификатора register с большинством современных компиляторов не приво- дит к явно выраженному эффекту, так как они сами помещают при необходи- мости переменные в регистры. С сылки: адресная операция & 7.5.6; объявления формальных параметров 9.3, инициализа- торы 4.6; индексы 7.4.1; объявления верхнего уровня 4.1; имена typeclef 5.10.
Объявления 101 4.3.1 Спецификаторы класса памяти, устанавливаемые по умолчанию Если в объявлении не указан спецификатор класса памяти, то используется один из спецификаторов в зависимости от контекста, в котором появилось объ- явление, как показано в таблице 4.4. Таблица 4.4. Устанавливаемые по умолчанию спецификаторы класса памяти Расположение объявления Тип объявления Класс памяти по умолчанию Верхний уровень Все extern Параметры функции Все нет (т.е. "не register") Внутри блока Функции extern Внутри блока Не-функции auto Отсутствие спецификатора класса памяти на верхнем уровне может не быть эквивалентным использованию спецификатора extern (см. раздел 4.8). Авто- ры полагают, что программисты, исповедующие принципы хорошего стиля программирования, должны задавать спецификатор extern при объявлении внешней функции в блоке. Спецификатор класса памяти auto редко встреча- ется в программах на С, так как он обычно устанавливается по умолчанию. Ссылки: блоки 8.4; объявления параметров 9.3; объявления верхнего уровня 4.1, 4.8. 4.3.2 Примеры спецификаторов класса памяти Ниже приведен алгоритм пирамидальной сортировки. Детальное разъясне- ние принципов работы данного алгоритма выходит за рамки книги. Пример Алгоритм рассматривает массив как бинарное дерево, причем такое, что у элемента b[k] есть поддеревья — элементы b[2*k] и Ь[2*к+1]. Использованная здесь куча (heap), — это дерево, каждый узел которого содержит число, которое не меньше, чем любое из чисел, содержащихся в потомках этого узла. #define SWAP(x, у) (temp = (х) , (х) = (у), (у) = temp) static void adjust (int v[], int m, register int n) /* Если v[m+l] через v[n] уже в куче, то это помещает в хучу v[m] через v[n] */ { register int *b, j, k, temp; b = v - 1; /* b - массив с отсчетом от 1, поэтому, например v[j] то же самое, что и b[j-l] ★/ j = m; k = 2 * m; while (k <= n) ( if (k < n && b[k] < b[k+l]) ++k; if (b[j] < b[k]) SWAP(b[j], b[k]); j = k; k *= 2; ) }
102 Глава 1 /* Сортирует v[0]...v[n-l] в порядке увеличения значений. */ void heapsort(int v[], int n) { int *b, j, temp; b = v - 1; /★ Преобразует массив в форму кучи. */ for(j = n/2; j > 0; j--) adjust(v, j, n); /* Многократно извлекает наибольший элемент и помещает его в конец неупорядоченной области */ for(j = п-1, j > 0; j--) { SWAP(b[l] , b[ j+1]) ; adjust(v, 1, j) ; 1 1 Для вспомогательной функции adjust нет необходимости быть видимой извне и по- этому она объявлена со спецификатором static. Скорость работы функции adjust критична для выполнения сортировки и поэтому ее локальные переменные объяв- лены со спецификатором register. Так как на формальный параметр п в функции adjust ссылаются многократно, то он также объявлен со спецификатором register. Два других формальных параметра функции adjust обозначены как «не-register». Так как основной функцией является heapsort, то она должна быть видима пользо- вателям библиотеки сортировки и поэтому она имеет спецификатор класса памяти по умолчанию — extern. Локальные переменные функции heapsort не влияют на производительность и поэтому имеют спецификатор класса памяти по умолча- нию — auto. 4.3.3 Спецификаторы функций Спецификаторы функций являются нововведением стандарта С99. спецификатор-функции: (С99) inline Спецификатор функции inline может появляться только в объявлениях функций. Функции, обозначенные данным спецификатором имеют общее на- звание встраиваемые функции (inline function). Спецификатор inline может упоминаться более одного раза без изменения смысла объявления. Наличие спецификатора inline указывает реализации С, что вызовы данной функции должны осуществляться максимально быстро. Детальные правила для встраиваемых функций обсуждаются в главе 9. Ссылки: встраиваемые функции 9.10. 4.4 Спецификаторы и квалификаторы типа Спецификаторы типа содержат некоторую информацию о типах данных идентификаторов, которые объявляются в программе. Дополнительную ин- формацию о типе содержит описатель (declarator). Спецификаторы типа могут также определять (как побочный эффект) теги типа, имена компонентов объе- динений или структур, а также константы перечисления. Квалификаторы типа const, volatile и restrict определяют дополнительные свойства типов, но эти свойства важны только при доступе к объектам типа с помощью 1-значений:
Введение в компьютеры, Internet и World Wide Web 103 спецификатор-типа: спецификатор-типа-перечисления спецификатор-типа-с-плавающей-точкой спецификатор-целого-типа спецификатор-типаструктура имя-typedef спецификатор-типа-объединение спецификатор-типа-void квалификатор-типа: const volatile restrict Пример Ниже приведены некоторые примеры спецификаторов типа: void union {int a; char b,} int enum {red,blue,green} unsigned long int char my_s truet_type float Мы отложим дальнейшее обсуждение спецификаторов типа до главы 5, где спецификаторы типа описываются детально. Однако некоторые основные во- просы, касающиеся спецификаторов типа будут обсуждены сейчас, в следую- щих разделах. Ссылки: описотели 4.5; спецификотор типо перечисления 5.5; спецификатор типа с плаваю- щей точкой 5.2; спецификотор целого типа 5.1; 1-зночение 7.1; спецификатор типа структуры 5.6; квалификаторы типо 4.4.3; имя typedef 5.10; спецификатор типа объединения 5.7; специ- фикатор типа void 5.9. 4.4.1 Спецификаторы, устанавливаемые по умолчанию Изначально, С разрешал не указывать спецификаторы в объявлении пере- менной или определении функции. В таком случае подразумевалось, что для этой переменной или функции по умолчанию используется спецификатор int. В современном С такой подход считается плохим стилем программирования, и Стандарт С99 рассматривает это как ошибку. В старых версиях компилято- ров не был реализован тип void и отсутствие указания спецификатора в опре- делении функции должно было показать просматривающему исходный текст программисту, что функция не возвращает значение (хотя компилятор все-та- ки делал функцию возвращающей значение). Пример В С-программах, написанных до Стандарта, часто можно было увидеть следующее объявление функции: /* Сортирует v[0]...v[n-l] в порядке увеличения. */ sort(v, п) int v[] , п;
104 Глава 4 { } В современном стиле Standard С эти функции объявляются со спецификатором типа void: /* Сортирует v[0] ,v[n-l] в порядке увеличения. */ void sortfint v[], int n) { } Пример При использовании компилятора, в котором не реализован тип void, лучше не опускать такой спецификатор, а определить void самостоятельно и затем явно использовать: /* Сделаем ’’void" синонимом "int”. */ typedef int void; Авторам известен как минимум один компилятор, который резервирует идентифика- тор void, но не реализует его. Для этого компилятора, препроцессорное определение #define void int является одним из немногих случаев, когда использование зарезервированного сло- ва в имени макроса оправдано. Пример Синтаксис объявления (раздел 4.1) требует, чтобы объявления содержали специфи- катор класса памяти, спецификатор типа, квалификатор типа или их комбинацию. Это требование позволяет избежать в языке некоторой двусмысленности. Если все спецификаторы и квалификаторы примут значения по умолчанию, то объявление extern int f(); будет иметь вид f О ; что синтаксически эквивалентно оператору, содержащему вызов функции. На наш взгляд хороший стиль программирования отличается тем, что требует, чтобы специфи- катор типа был включен всегда, а для спецификатора класса памяти использовалось бы значение по умолчанию, как минимум в случае, когда он имеет значение auto. Пример И последняя заметка приверженцам грамматики LARL(l): в определении функции могут быть опущены как спецификатор класса памяти, так и спецификатор типа, что часто можно видеть: main() ( } В этом случае нет синтаксической двусмысленности, так как описатель в объявле- нии функции должен быть с последующей запятой или точкой с запятой, тогда как описатель в определении функции должен стоять перед левой фигурной скобкой. Ссылки: объявления 4.1; определения функции 9.1, спецификатор типа void 5.9.
Объявления 105 4.4.2 Пропущенные объявители Этот раздел обсуждает тонкий вопрос, связанный со спецификаторами типа и объявлениями. Спецификаторы типа, которые являются определениями структур, объединений или перечислений, определяют новые типы и констан- ты перечисления. Если вы хотите только определить тип, то имеет смысл опус- тить в объявлении все описатели и задать только спецификатор типа. В Stan- dard С в определении должен присутствовать описатель, определяющий тег структуры или тег объединения, или описатель, определяющий константы пе- речисления. В традиционном С бессмысленные объявления часто игнорирова- лись без выдачи соответственного сообщения. Пример Следующее объявление состоит из одного спецификатора типа. Оно определяет но- вый структурный тип S, содержащий компоненты а и Ь. struct S { int а, Ь; }; /* Определяет структурный тип S */ В дальнейшем можно ссылаться на тип простым указанием спецификатора struct S х, у, z; /* Определяет 3 переменных */ Однако следующие объявления являются бессмысленными и, соответственно, не- разрешенными (в Standard С): struct ( int а, Ь; }; /* Отсутствует тег */ int ; /* отсутствует описатель */ static struct Т { int a, b; }; /* Лишний спецификатор класса памяти */ В первом случае отсутствует тег структуры, поэтому далее в программе на этот структурный тип нельзя будет ссылаться. Во втором случае объявление бессодержа- тельно. В третьем случае был добавлен спецификатор класса памяти, который в данном случае будет проигнорирован. Вы можете предположить, что в последую- щем объявлении struct т х, у; переменные х и у также будут относиться к классу памяти static, но это не так. Ссылки: типы перечисления 5.5; описатели 4.5; типы структур 5.6; спецификаторы типа 4.4; типы объединения 5.7. 4.4.3 Квалификаторы типа Квалификаторы типа, обозначаемые const и volatile, были утверждены в С стандартом С89. Квалификатор типа restrict добавлен стандартом С99. Гово- рят, что идентификатор, объявленный с использованием любой комбинации этих квалификаторов, имеет квалифицированный тип (qualified type). Для ка- ждого простого типа существует семь квалифицированных вариантов типов. (Порядок квалификаторов типа значения не имеет.) Эти семь квалифициро- ванных типов не совместимы ни с простым типом, ни между собой. Если в объ- явлении появится один и тот же квалификатор типа более одного раза, то соот- ветствующие реакции компиляторов, отвечающих разным стандартам, будут различны. Компилятор Standard С99 проигнорирует дополнительное возник- новение квалификатора, a Standard С89 выдаст сообщение об ошибке. Квалификаторы типа определяют дополнительные свойства типов, которые имеют смысл только при доступе к объекту через 1-значения (указатели) соот-
106 Глава 4 ветствующего типа. Если объект используется в контексте, который требует значения, а не 1-значения, то квалификаторы типа игнорируются. То есть в выражении L = R тип правого операнда операции "=" всегда является про- стым типом, даже если он был объявлен с квалификаторами типа. Однако ле- вый операнд сохраняет квалификацию до тех пор, пока он используется в кон- тексте 1-значения. Квалификаторы типа могут присутствовать на верхнем уровне объявлений, но могут также фигурировать в описателях указателей и (в Standard С99) мас- сивов. Пример Если вы используете компилятор, который не поддерживает квалификаторы типа, вы можете вставить следующие макроопределения для того, чтобы использование этих квалификаторов не вызвало ошибки при компиляции. Конечно, при этом ква- лификаторы никак не будут влиять на программу. ffifndef _STDC__ #define const /* ничего */ #define volatile /* ничего */ ffdefine restrict /* ничего */ ffendif Ссылки: директива #ifndef 3.5.3;_STDC_ 1 1.3; описатели массива 4.5.3; описатели ука- зателей 4.5.2; совместимость типов 5.11. 4.4.4 Квалификатор типа const 1-значение типа, обозначенного квалификатором const, не может быть исполь- зовано для модификации объекта. Поэтому такое 1-значение не может выступать в роли левого операнда в выражении присваивания или операндом в операции инкремента или декремента. Цель использования квалификатора const состоит в обозначении объектов, чьи значения неизменны. Это (обозначение) также по- зволяет компилятору отследить попытки изменения этих объектов. Пример Приведенное ниже объявление определяет ic как целое с константным значени- ем 37: const int ic = 37; ic = 5; /* Недопустимо */ ic++; /* Недопустимо */ В описателях указателей квалификатор const используется, как для объяв- ления «константных указателей», так и для объявления «указателей на кон стантные данные»: int * const const_pointer; const int *pointer_to_const; Такой синтаксис может сбивать с толку: в объявлениях константных указате- лей и в константных целых квалификатор const размещается в разных мес- тах. Его размещение также меняется при использовании имен typedef. Кон-
Объявления 107 стантный указатель const_pointer из предыдущего примера может быть объ- явлен следующим образом: typedef int *int_pointer; const int_pointer const_pointer; Это делает константный указатель const_pointer похожим на «указатель на константу int_pointer», но это не так — это по-прежнему константный указа- тель на (неконстантное) целое. По сути, так как порядок спецификаторов и квалификаторов типа не имеет значения, последнее объявление может быть записано следующим образом: int_pointer const const_pointer; Вы можете менять переменную типа «указатель на константные данные», но вы не можете менять объект, на который ссылается эта переменная. Объект типа «указатель на константные данные» может быть создан применением ад- ресной операции & к объекту типа, обозначенного квалификатором const. Для защиты целостности константных данных присвоение значения типа «указа- тель на const Т» объекту типа «указатель на Т» разрешено только с помощью явного приведения типов. Пример const int *рс; /* указатель на целую константу */ int *р, i; const int ic; pc = p = Si; /* Верно */ pc = Sic; /* Верно */ *p = 5; /* Верно */ *pc =5; /* Неверно */ pc = Si; /* Верно */ pc = p; /* Верно */ p = Sic; /* Неверно */ p = pc; /* Неверно */ p = (int *)Sic; /* Верно */ p = (int *)pc; /* Верно */ Правила языка для квалификатора const не идеальны — при достаточных усилиях со стороны программиста их можно обойти. Например, адрес кон- стантного объекта может быть передан внешней функции, у которой нет про- тотипа и эта функция сможет изменить константный объект. Однако реализа- циям разрешено размещать статические объекты, обозначенные квалификато- ром const, в области памяти, помеченной как «только для чтения», поэтому попытки модификации объекта могут вызвать ошибки времени исполнения. Пример Этот фрагмент программы иллюстрирует некоторые опасности попыток обхода ква- лификатора const: const int *рс; int *р; const int ic = 0; pc = Sic; /* Верно */ p = (int *)pc; /* Корректно, но опасно */ *p = 5; /* Корректно, но может вызвать ошибку времени исполнения */
108 Глава 4 И, наконец, объявления верхнего уровня, снабженные квалификатором const, но без явного обозначения спецификатора класса памяти, считаются обозначенными спецификатором класса памяти extern. Ссылки: выражения присвоения 7.9; выражения инкремента и декремента 7.4, описатели указателей 4.5 2. 4.4.5 Квалификатор типа volatile и специальные маркеры Квалификатор типа volatile сообщает в Standard С, что указанные объекты могут изменять свои значения не по правилам реализации. Непостоянные (volatile) объекты (например, любые объекты, доступ к которым осуществля- ется с помощью 1-значения выражения, обозначенного квалификатором vola- tile) не должны быть задействованы в оптимизации, которая предполагает ка- кие-либо побочные эффекты. Для уточнения ситуации в Standard С вводится понятие специальных мар- керов (sequence points). Специальный маркер (по сути, это просто позиция в тексте программы — прим, ред.) находится в конце каждого выражения, но при этом не является частью большего выражения, то есть находится в конце оператора. Он позиционируется после управляющих выражений таких опера- торов управления, как if, switch, while и do; после каждого из трех управляю- щих выражений в операторе for; после первого операнда логического И (&&), логического ИЛИ (| |), условной операции (?:) и операции запятая (,); после вы- ражения, стоящего за оператором return и за инициализаторами. Дополни- тельные специальные маркеры размещены в конце полного описателя, в вызо- вах функций непосредственно за списком аргументов, перед возвратом биб- лиотечных функций, за действиями, связанными со спецификаторами преобразования printf/scanf, и слева/справа от вызовов функций сравнения, использующих функции bsearch и qsort. Модификации и ссылки на непостоянные объекты не должны быть оптими- зированы при переходе через специальные маркеры, несмотря на то, что опти- мизация между специальными маркерами разрешена. Стандартом С разрешены дополнительные ссылки и модификации непостоянных объектов вне их появле- ния в исходном тексте программы. Однако опыт подсказывает, что программи- сты предпочитают, чтобы реализации получали доступ и модифицировали не- постоянные объекты именно так «как написано». Хотя для осуществления оп- тимизации достаточно просто скопировать значение непостоянного объекта. Пример Согласно следующему фрагменту программы, переменной j было присвоено некото- рое значение еще до цикла: extern int f(int), auto int i, j; i = f (0) ; while (i) { if (f(j*j)) break; 1 Если переменная i не была использована в течение своего времени жизни, то тради- ционный С разрешит переделать приведенный выше фрагмент следующим образом:
Объявления 109 if (f(0)) ( i = i*j; while( !f(i) ); } Первое присвоение переменной i было опущено и затем переменная i была использо- вана как временная переменная для хранения выражения j*j, которое вычисляется вне цикла. Если бы объявления переменных i и j были следующего вида auto volatile int i, j ; то тогда описанная выше оптимизация недопустима. Однако мы могли бы перепи- сать цикл, как показано ниже, опуская одну ссылку на j перед специальным марке- ром в конце выражения условного оператора if: i = f (0) ; while (i) { register int temp = j; if (f(temp*temp)) break; } Новый синтаксис описателей указателей разрешает объявления типа «ука- затель на volatile ...». Ссылки на указатели такого типа могут быть оптимизи- рованы, но нельзя изменять ссылки на объекты, адресуемые этими указателя- ми. Присвоение значения типа «указатель на volatile Т» объекту типа «указа- тель на Т» разрешено только при явном приведении типов. Пример Ниже приведены некоторые примеры правильного и неправильного использования объектов, обозначенных квалификатором volatile: volatile int *pv; int *p; pv = p; /* Верно */ p = pv; /* Неверно */ p = (int *)pv; /* Верно */ Наиболее часто квалификатор volatile используется для обеспечения на- дежного доступа к специальным областям памяти, используемым аппаратны- ми средствами компьютера, или асинхронными процессами, такими как обра- ботчики прерываний. Пример Рассмотрим следующий типичный пример. У компьютера есть три специальные ап- паратные области памяти: Адрес 0xFFFFFF20 0xFFFFFF24 0xFFFFFF28 Использование Буфер входных данных Буфер выходных данных Управляющий регистр Как буфер входных данных, так и управляющий регистр могут быть прочитаны программой, но не могут быть ею изменены; в выходной буфер данных можно запи- сать информацию, но он не может быть прочитан программой. Третий младший зна- чащий бит управляющего регистра является флагом входных данных. Он устанав- ливается в единицу, когда данные поступили из внешнего источника, и в ноль, ко- гда эти данные были прочитаны из входного буфера программой (после чего
110 Глава 4 содержимое этого буфера не определено до тех пор, пока значение флага входных данных опять не установится в единицу). Второй младший значащий бит управляю- щего регистра является флагом выходных данных. Когда внешнее устройство гото- во для принятия данных, этот бит устанавливается в единицу. Когда данные уже помещены в выходной буфер программой, этот бит автоматически устанавливается в ноль, а данные выводятся. Помещение данных в буфер выходных данных при ну- левом значении соответствующего бита управляющего регистра приведет к непред- сказуемым результатам. Приведенная ниже функция copy_data копирует данные со входа на выход до тех пор, пока на входе не появится нулевое значение. Возвращается значение, представ- ляющее количество скопированных символов. Эта функция не предусматривает возможности переполнения или появления каких-либо других ошибок: typedef unsigned long datatype, controltype, counttype; ((define CONTROLLER \ ((const volatile controltype * const) 0xFFFFFF28) ((define INPUT_BUF \ ((const volatile datatype * const) 0xFFFFFF20) ((define OUTPUTJBUF \ ((volatile datatype * const) 0xFFFFFF24) ((define input_ready ((«CONTROLLER) & 0x4) ((define output_ready ((«CONTROLLER) & 0x2) counttype copy_data(void) { counttype count = 0; datatype temp; for(;;) ( while (!input_ready); /« Ожидание ввода «/ temp = *INPUT_BUF; if (temp = 0) return count; while (!output_ready); /« Ожидание вывода «/ *OUTPUT_BUF = temp; count++; } } Ссылки: функция bsearch 20,5; спецификации преобразования 15.8.2, 15 11.2, описатели 4.5; инициолизаторы 4.6; описатели указателей 4 5.2, функция qsor* 20 5. 4.4.6 Квалификатор типа restrict Квалификатор типа restrict относится к нововведениям Standard С99. Он может использоваться только для указателей на объекты или для неполных типов и сообщает компилятору об «отсутствии алиасов (псевдонимов)». Это оз- начает, что в некоторый период времени указатель является единственным способом доступа к объекту, на который он указывает. Нарушение этого огра- ничения приводит к неопределенному поведению. Выражение «в некоторый период времени» означает, что при некоторых обстоятельствах внутри блока или функции для снабженных квалификатором restrict указателей могут быть созданы алиасы, при условии, что они будут уничтожены в конце данно- го блока или функции. Standard С99 дает квалификатору restrict точное мате- матическое определение. Ниже приведены основные ситуации, в которых ис- пользуется данный квалификатор.
Объявления 111 1. Указатель с областью видимости файла объявляется с квалификатором restrict для того, чтобы быть единственным средством для доступа к объекту, на который он указывает. Это может рассматриваться как подходящий способ для объявления указателя с глобальной областью видимости, который инициализируется функцией malloc во время ис- полнения программы. extern double * restrict ptr; void initialize(void) ( ptr = my_malloc( sizeof(double) ); } 2. Указатель, снабженный квалификатором restrict и объявляемый в па- раметрах функции, используется как единственный способ доступа к некоторому объекту с начала выполнения функции, и другие указате- ли для изменения данного объекта создавать нельзя. Например, функ- ция memcpy (в отличие от функции memmove) требует, чтобы исходная и целевая области памяти не пересекались. С выходом Standard С99 это требование стало возможным задать следующим образом в прототипе функции: ((include <string.h> void *memcpy( void * restrict si, const void * restrict s2, size_t n); 3. Два указателя, снабженных квалификатором restrict, или два указате- ля, один из которых снабжен квалификатором restrict, а другой — нет, могут ссылаться на один и тот же объект, если во время жизни restrict-указателей этот объект не будет модифицирован. Например, рассмотрим следующую функцию, которая суммирует два вектора, за- писывая сумму в третий: void add(int n, int * restrict dest, int * restrict opl, int restrict op2) ( int i; for(i = 0; i < n; i++) dest[i] = opl[i] + op2[i]; } Если а и b — непересекающиеся массивы длины N, то вызов функции add(N, а, b, Ь) вполне корректен: в нем в качестве массивов opl и ор2 будет использован массив Ь, так как массив b никогда не изменяется. Конечно, возможность такого вызова зависит от того, знаем ли мы реа- лизацию функции add. Программист, который видит только прототип такой функции не может быть уверен в том, что приведенный выше пример вызова безопасен. 4. Член структуры также может снабжаться квалификатором restrict. ЭтО означает, что когда создается экземпляр такой структуры, член, уточненный квалификатором restrict, является единственным спосо- бом ссылки на обозначенный объект.
112 Глава 4 До того как в язык С был добавлен квалификатор restrict, программисты вынуждены были использовать ключи компилятора или непереносимые праг- мы, чтобы разрешить только те способы оптимизации указателей, которые разрешены в случае, когда одновременно на объект ссылается только один указатель. Такая оптимизация может привести к значительному сокращению времени выполнения программы. Отбрасывание квалификатора restrict не влияет на смысл программы. Реа- лизации С могут игнорировать квалификатор restrict. В этой книге многие прототипы библиотечных функций описаны с использованием квалификатора restrict. Программистам, использующим предыдущие реализации С (до С99) квалификатор restrict следует опускать или игнорировать. Ссылки: функция malloc 16.1; функция memcpy 14 3. 4.5 Описатели Описатели (declarator) представляют объявляемое имя и некоторую допол- нительную информацию о типе. До появления С ни у одного языка программи- рования не было ничего похожего: описатель: описатель-указатель прямой-описателъ прямой-описателъ: простой-описатель ( описатель ) описатель-фу нкции описателъ-массива В следующих разделах описываются различные виды описателей. 4.5.1 Простые описатели Простые описатели (simple declarator) используются для определения ариф- метических типов, а также типов перечисления, структуры и объединения: простой-описатель: идентификатор Предположим, что S — это спецификатор типа, a id — какой-либо идентифи- катор. Тогда объявление S id; свидетельствует о том, что идентификатор id имеет тип S. Идентификатор id называется простым описателем. Пример Объявление Тип переменной х __ _____ int х; целое float х; с плавоющей точкой struct S { int a; float b; } х; структура, состоящая из двух компонентов
Объявления 113 Простые описатели могут быть использованы при объявлении в том случае, когда спецификатор типа содержит всю информацию о типе. Это верно для арифметических типов, перечисления, структур, объединений, типа void, а также типов, представленных именами typedef. Типы указателей, массивов и функций требуют использование более сложных описателей. Однако каж- дый описатель включает в себя идентификатор и поэтому мы говорим, что в описатель «входит» идентификатор. Ссылки: спецификаторы типа 4.4; типы структуры 5.6; имена typedef 5.10. 4.5.2 Описатели указателей Описатели указателей используются для объявления переменных типа ука- зателей. Синтаксическая конструкция список-квалификаторов-типа, приве- денная ниже, относится к нововведениям Standard С. В более ранних компи- ляторах она отсутствует. описатель-указатель: указатель прямой-описателъ указатель: * список-квалификаторов-типаогщ * список-квалификаторов-типаОГ1Ц указатель список-квалификаторов-типа: ( С89 ) квалификатор-типа список-квалификаторов-типа квалификатор-типа Предположим, что D — это любой описатель, содержащий идентификатор id и что объявление "S D;" говорит о том, что идентификатор id имеет тип "... S". Тогда объявление S *D ; говорит о том, что идентификатор id имеет тип «... указатель на >$». Опцио- нальный элемент в описании указателя — список-квалификаторов-типа — разрешен только в Standard С. При его наличии, квалификаторы относятся к указателю, а не объекту, на который указатель ссылается. Пример В трех объявлениях х, приведенных ниже в таблице, id — это х, S — это int, а соответственно, «массив типа», «возвращаемое функцией значение типа». (Это труднее объяснить, нежели выучить). Объявление Тип х int *х; укозотель на int int *х[]; массив типа указатель но int int *хД; возвращаемое функцией значение типо указатель на int
114 Глава 4 Пример В следующих объявлениях, ptr_to_const — это (неконстантный) указатель на кон- станту типа int, тогда как const_ptr — константный указатель на (неконстантый) int: const int * ptr_to_const; int * const const_ptr; Ссылки: описатели массива 4.5.3; квалификатор типа const 4.4.4, описатели функции 4 5.4; типы указателей 5.3; квалификаторы типа 4.4.3. 4.5.3 Описатели массива Описатели массива используются для объявления массивов: описатель-массива: прямой-описатель [ константное-выражениеопц ] (до появления С99) прямой описатель [ список-квалификаторов-массива огщ выражение-размер-массиваопц ] (С99) прямой-описателъ [список-квалификаторов-массиваипц * ] (С99) константное-выражение: условное-выражение список-квалификаторов-массива: квалификатор-массива список-квалификаторов-массива квалификатор-массива квалификатор-массива: static restrict const volatile выражение-размер-массива: выраже.ние-присвое.ния ★ Пусть D — это любой описатель, содержащий идентификатор id. Тогда объяв- ление "S D;" говорит о том, что id имеет тип "... S", а объявление 5(D) [ е ] ; говорит о том, что id имеет тип «... массив типа S>>. (Круглые скобки могут быть опущены согласно правилам предшествования для составления описателей; см. раздел 4.5.5). Тип S не может быть неполным типом или типом функции. В подавляющем большинстве случаев внутри квадратных скобок задается целое константное выражение е, определяющее количество элементов масси- ва. Это выражение должно быть целым числом, причем большим нуля. Нуме- рация в массивах С всегда начинается с нуля. Это означает, что объявление int А[3] определяет элементы А[0], А[1] и А[2]. В С массивы с числом размерно- стей более единицы определяются как «массивы массивов» (см. раздел 5.4.2).
Объявления 115 Пример В приведенных ниже трех объявлениях id это ?, S это int, а "..." это, соответствен- но, «указатель на» и «массив элементов типа». Объявление Тип х int (x)[5J; массив целых int (*х)[5]; указатель но массив целых int (х[5])[5]; массив массивов целых int х[5][5]; массив'моссивов целых (та же самое) Внутри квадратных скобок описателя массива не обязательно должно при- сутствовать целое константное выражение. Возможны три случая, когда оно отсутствует: неполные типы массивов, массивы переменной длины, и исполь- зование квалификатора-массива (квалификаторы типа или static) внутри описателя-массива. Неполные типы массивов. Если квадратные скобки пусты, то описатель за- дает неполный (incomplete) тип массива. Объекты неполных типов созданы быть не могут, так как неизвестен их размер. Но на неполные типы можно соз- давать указатели. Ниже приведены некоторые случаи, в которых размеры массивов могут быть опущены: 1. Объявляемый массив является формальным параметром функции. Так как массивы, используемые в качестве параметров, преобразуются к указателям, то их размер не нужен. Если массив является многомер- ным, то опущенным может быть только самый левый его размер. На- пример, int f(int ary[]); /* массив неопределенной длины */ 2. Вместе с описателем указан инициализатор, с помощью которого мож- но вычислить длину массива. После обработки инициализатора тип уже не относится к неполным. Например, char promt[] = "Yes or No?"; 3. Описатель не является определяющим, а ссылается на объект, опреде- ленный в другом месте, где тип определяется не как неполный. Для многомерных массивов может быть опущенным только его самый ле- вый размер. На неполные типы можно создавать указатели. Например, extern int matrix[][10]; /* неполный тип */ static int matrix[5][10]; /* теперь уже не неполный */ 4. В С99 последний компонент структуры может быть гибким членом, от- носящимся к типу массив, объявленным без размера. Объявление любого n-мерного массива должно включать в себя последние п-1 размерностей для того, чтобы определить алгоритм доступа к нему. Массивы переменной длины. В С99, если выражение-размер-массива внутри квадратных скобок описателя-массива равно * или не является константным выражением, то описатель описывает массив переменной длины. Символ * мо-
116 Глава 4 жет появляться только в объявлениях параметров прототипов функции, кото- рые не являются частью ее определения. Массивы переменной длины не отно- сятся к неполным типам. Для более детального ознако.мления с массивами пере- менной длины и их использованием в прототипах функций см. раздел 5.4.5. Квалификаторы массива. В С99, в описателе-массива в квадратных скоб- ках разрешен список-квалификаторовмассива, но только при объявлении па- раметра функции типа массива. Этот вопрос обсуждается в разделе 9.3. Ссылки: типы массивов 5,4; выражения присваивания 7,9; условные выражения 7.8; констант- ные выражения 7,1 1; гибкий член типо массив 5.6,8; формальные параметры 9.3; инициализа- торы 4,6; ссылочные и определяющие объявления 4.8; квалификаторы типа 4,4.3; массивы пе- ременной длины 5.4.5. 4.5.4 Описатели функций Описатели функций используются для объявления или определения функ- ций и объявления типов, которые имеют указатели функции в качестве ком- понентов: описателъ-функции: прямой-описателъ ( список-типов-параметров ) (С89) прямой-описателъ ( список-идентификаторовопц ) список-типов-параметров: список-параметров список-параметров , ... список-параметров: объявление-параметра список-параметров , объявление-параметра объявление-параметра: спецификаторы-объявления описатель спецификаторы-объявления абстрактный-описательогщ список-идентификаторов: идентификатор список-идентификаторов , идентификатор Если D — произвольный описатель, содержащий в себе идентификатор id, и если объявление "S D;" означает, что id относится к типу ”... S", то объявле- ние S (D) (Р) ; означает, что идентификатор id имеет тип «... функция с аргументом типа Р, возвращающая значения типа S». Круглые скобки, обрамляющие D, могут быть опущены в большинстве случаев согласно правилам предшествования при формировании описателей (см. раздел 4.5.5). Наличие списка типов-пара- метров в синтаксисе описателя означает, что описатель записан в виде прото- типа Standard С. Его отсутствие говорит о том, что описатель записан в тради- ционной форме, которая применима как для традиционного компилятора, так и для компилятора Standard С.
Объявления 117 Пример Ниже приведены некоторые примеры описателей функций: Объявление Тип x int х(); Функция с неопределенными параметрами, возвращающая значение целого типа. int x(double, float); Функция, принимающая в качестве параметров значения типо float и double и возвращающая значение целого типо (прототип). int x(double d, float f); int (*x)(); То же самое, что и предыдущий описатель. Указатель но функцию с неопределенными параметрами. Функция возвращает значение целого типа. int (*x[])(int, ...); Массив указателей функций, которые принимают переменное числа аргументов, начиная с аргумента целого типа, и возвращают значение целого типа (прототип). int (* const x) (void) Константный указатель на функцию, которая не принимает параметров и возвращает значение целого типа. На описатели функций накладывается ряд ограничений в зависимости от того, где они возникают — в определении функции или как часть объявления объекта или типа функции. В таблице 4.5 показаны все возможные виды опи- сателей функций с учетом того, является ли данная синтаксическая конструк- ция прототипом Standard С или традиционной для С, может ли конструкция появляться в определении функции или объявлении типа функции, и показы- вает, какая информация о параметрах включена. Таблица 4.5 Описатели функций Синтаксис Вид Возникает в Заданные параметры Ф традиционный определениях параметров нет Ф традиционный объявлениях типо любое число параметров f(x, у, z) традиционный определениях фиксированные1 ffvoid) прототип и том и там параметров нет f(Tx, Ту, TJ прототип объявлениях типа фиксированные f(Tx, Ту, ..., Т„ ...) прототип объявлениях типа фиксированные, плюс дополнительные2 f(Tx х. Ту у, Tz z) прототип и том и там фиксированные f(Tx х. Ту у, Tz z, . ..) прототип и том и там фиксированные, плюс о дополнительные2 1 До появления Stondard С было возможно использовать дополнительные, неопределенные параметры. 2 Количество и тип дополнительных порометров не определены. Нотация Тх х, представленная в таблице, ссылается на синтаксическую конструкцию спецификаторы-объявления описатель (например, объявление типа аргумента, которое включает имя аргумента х). Тх ссылается на спецификаторы-объявления абстрактный-описательопц Это объявление типа аргумента, в котором опускается имя аргумента.
118 Глава 4 Объявление и использование функций обсуждается более детально в Гла- ве 9. Списки параметров переменной длины обрабатываются с помощью меха- низмов, определенных в заголовочных файлах stdarg.h и varargs.h. Ссылки: обстрактный описатель 5.12; описатели массивов 4.5.3; определяющие и ссылочные объявления 4.8; типы функций и объявления 5.8; определения функций 9 1; описатели указате- лей 4.5; заголовочные файлы varargs.h и stdarg.h 11.4. 4.5.5 Сложные описатели Описатели можно компоновать для задания более сложных типов, таких как «5-элементный массив указателей на функции, возвращающие значения целого типа». Пример такого описателя приведен в следующем объявлении: int (*агу[5]) (); Единственное ограничение, накладываемое на сложные описатели состоит в том, что результирующий тип должен быть корректным (valid) в С. Ниже пе- речислены типы, которые не являются таковыми: 1. Любой тип, включающий void, за исключением функции вида «... функция, возвращающая значение типа void» или (в Standard С) «ука- затель на void». 2. «Массив функций типа ...». Массивы могут содержать указатели на функции, но не сами функции. 3. «Функция, возвращающая массив ...». Функции могут возвращать ука- затели на массивы, но не сами массивы. 4. «Функция, возвращающая функцию ...». Функции могут возвращать указатели на другие функции, но не сами функции. При компоновке сложных описателей важно учесть предшествование (pre- cedence) выражений описателя. Описатели функций и массивов имеют более высокий уровень предшествования, чем описатели указателей, поэтому конст- рукция "*х()" эквивалентна конструкции "*(х())" («функция возвращает ука- затель ...»), а не "(*х)()" («указатель на функцию, возвращающую ...»). Для правильного группирования описателей могут использоваться круглые скоб- ки. Старые компиляторы С позволяли использовать до шести уровней вложен- ности описателей. Компиляторы Standard С должны допускать не менее 12 уровней вложенности описателей. Хотя описатели могут быть произвольно сложными, следование правилам хорошего стиля программирования требует разделения сложного описателя на несколько более простых. Пример Объявление Тип х int х(); функция, возврощающая значение целого типа int (*х)(); укозотель на функцию, возвращающую зночение целого типа void (*х)(); указатель на функцию, не возвращающую никаких значении void *х(); функция, возвращающая «указатель на void»
Объявления 119 Пример Вместо того, чтобы писать int *(*(*(*х) ()) [10]) () ; лучше написать typedef int *(*print_function_ptr) () ; typedef print_function_ptr (*digit_routines) [10] ; digit_routines (*x)(); Переменная x имеет тип указателя на функцию, возвращающую указатель на 10-элементный массив указателей функций, возвращающих указатели на целые. Пример Смысл применяемого синтаксиса описателей, состоит в том, что этот синтаксис очень похож на синтаксис использования описываемого идентификатора. Для ил- люстрации симметрии объявления и использования, приведем следующую конст- рукцию int *(*х) [4] ; Тогда выражение *(*х) [i] имеет тип int. Ссылки: типы массивов 5.4; типы функций 5.8; указатель на void 5.3.2; типы указателей 5.3; спецификатор типа void 5.9. 4.6 Инициализаторы Объявление переменной может быть дополнено инициализатором (initi- alizer), задающим этой переменной значение, которая она должна получить в начале времени ее жизни. Полный синтаксис инициализаторов приведен ниже: инициализатор: выражение-присвоения { список-инициализаторов 90пц } список-инициализаторов: инициализатор список-инициализаторов , инициализатор именованная-инициализация инициализатор (С99) список-инициализаторов , именованная-инициализация инициализатор (С99) именованная-инициализация: список-именованных-инициализаторов — список-именованных-инициализаторов: именованный-инициализатор список-именованных-инициализаторов именованный-инициализатор
120 Глава 4 именованныйинициализатор: [ константное-выражение ] . идентификатор Опциональная запятая в конце списка внутри фигурных скобок не влияет на смысл инициализатора. Standard С99 вводит понятие именованные инициализаторы (designated initializer). В таких инициализаторах программист может указать отдельные компоненты, подлежащие инициализации (раздел 4.6.9). Допустимость использования тех или иных разновидностей инициализато- ров для конкретного объявления зависит от типа инициализируемого объекта и его класса памяти (статический или автоматический). Опции, представлен- ные в таблице 4.6 более детально рассмотрены в следующих разделах. Для внешних объектов инициализаторы должны присутствовать в объявлениях только тогда, когда эти объявления являются определяющими (см. раздел 4.8). Структура инициализатора — обрамленные фигурными скобками списки инициализаторов, — должна совпадать со структурой инициализируемой пе- ременной. Определение языка указывает, что инициализатор для скалярный переменный может быть обрамлен фигурными скобками, хотя такие скобки необязательны. Мы рекомендуем использовать фигурные скобки для обозначе- ния групповой инициализации. Для сокращения групповых инициализаторов существуют специальные правила. Таблица 4.6. Вид ииициализаторов Класс памяти Тип Инициализирующее выражение Инициализатор по умолчанию статический скалярный константа 0, 0.0, false или нулевой указатель статический массив1 или структура константы (или неконстантные выражения (С99)|, обрамленные фигурными скобками рекурсивное значение по умолчанию для каждого компонента статический объединение2 константа (или неконстантное выражение (С99)) значение по умолчанию для первого компонента автоматический скалярный любое Нет автоматический массив •-2 константы, обрамленные фигурными скобками Нет автоматический структура2 обрамленные фигурными скобками констонты или одно неконстантное выражение структуры того же типа Нет автоматический объединение2 константа или одно неконстантное выражение объединения того же типо Нет 1 Если у массива не задон размер, то инициализатор определяет этот размер. Массивы переменной длины не могут быть проинициолизировоны. ? Standard С. Более ранние реализации могут не разрешать инициализацию этих объектов. Историческая справка: изначально, в С для инициализаторов использовал- ся синтаксис, в котором опускался символ присваивания =. Некоторые совре- менные компиляторы С поддерживают и сейчас такой синтаксис для совмес- тимости. Когда пользователи этих компиляторов случайно пропускают в объ- явлении запятую или точку с запятой (например, "int а Ь;"), то получают
Объявления 121 туманное сообщение об ошибке «был использован неправильный инициализа- тор». Standard С не поддерживает этот устаревший синтаксис. Следующие разделы объясняют специальные требования для каждого типа переменных. Ссылки: статический и автоматический экстент 4.2; объявления 4.1; внешние объекты 4.8; статический класс памяти 4.3. 4.6.1 Целые Инициализатор для переменной целого типа выглядит следующим образом описатель = выражение Инициализирующее выражение должно иметь тип, который разрешен в про- стом присвоении инициализируемой переменной. К инициализирующему зна- чению применимы обычные правила преобразования присвоения. Если пере- менная относится к классу памяти static или extern, то выражение должно быть константным. Если переменная относится к классу памяти auto или register, то разрешено любое корректное выражение. Инициализатором по умолчанию для статической переменной целого типа является 0. Пример В приведенном ниже фрагменте исходного текста переменная Count инициализиру- ется константным выражением, а переменная ch — результатом вызова функции. ((include <stdio.h> static int Count = 4*200; int main(void) { int ch = getchar(); } Ссылки: константное выражение 7.11; целые типы 5.1; статический и автоматический экстент 4.2; обычные правила преобразования присвоений 6.3.2. 4.6.2 Переменные с плавающей точкой Инициализатор для переменной с плавающей точкой выглядит следующим образом описатель = выражение Инициализирующее выражение должно иметь тип, который разрешен в про- стом присвоении инициализируемой переменной. К инициализирующему зна- чению применимы обычные правила преобразования присвоения. Если пере- менная относится к классу памяти static или extern, то выражение должно быть константным. Если переменная относится к классу памяти auto или register, то разрешено любое корректное выражение.
122 Глава 4 Пример static void process data(double K) ( static double epsilon = 1.0.e-6; auto float fudge_factor = K*epsilon; } Standard С явно разрешает в инициализаторах использование константных выражений с плавающей точкой. Некоторые более старые компиляторы С от- казывались компилировать сложные инициализирующие выражения с пла- вающей точкой. Инициализирующее значение по умолчанию для статических переменных с плавающей точкой равно 0.0. На целевом компьютере этому значению может соответствовать объект, не все биты которого равны нулю. Компиляторы Standard С должны инициализировать переменную значением, которое будет корректно отображать величину 0.0 на целевом компьютере. Однако старые компиляторы С всегда инициализировали объект, относящийся к статическо- му классу памяти нулевыми битами. Ссылки: арифметические типы гл. 5; константные выражения 7.11; константы с плавающей точкой 2.7.2; типы с плавающей точкой 5.2; статический и автоматический экстент 4 2; унар- ный минус 7.5.3; обычные преобразования присвоения 6.3 2. 4.6.3 Указатели Инициализатор для переменной типа указатель выглядит следующим образом описатель = выражение Инициализирующее выражение должно иметь тип, который разрешен в про- стом присвоении инициализируемой переменной. К инициализирующему зна- чению применимы обычные правила преобразования присвоения. Если пере- менная относится к автоматическому классу памяти, то разрешено любое вы- ражение соответствующего типа. Если переменная относится к статическому классу памяти или является внешней, то выражение должно быть константным. Константные выражения, используемые для инициализаторов типа указатель РТ (указатель на Т) могут быть сформированы из следующих элементов. 1. Целое константное выражение со значением 0 или такое значение, пре- образованное к типу void *. Такие константы нулевых указателей часто фигурируют под именем NULL в стандартной библиотеке. #define NULL ((void *) 0) double *dp = NULL; 2. Имя статической или внешней функции типа «функция, возвращаю- щая значение типа Т>> преобразуется в константу типа «указатель на функцию, возвращающую значение типа Г». extern int f(); static int (*fpl() = f; 3. Имя статического или внешнего массива типа «массив типа Т•> преобра- зуется в константу типа «указатель на Т».
Объявления 123 char ary[100]; char *cp = ary; 4. Операция &, примененная к имени внешней или статической перемен- ной типа Т, порождает константу типа «указатель на Т». static short s; auto short *sp = £s; 5. Операция &, примененная к внешнему или статическому массиву типа «массив типа Т» с заданным индексом, представленным константным выражением, порождает константу типа «указатель на Г». float PowersOfPi[10]; float ‘PiSquared = SPowersOfPi[2]; 6. Целая константа, приведенная к типу указателя, порождает константу типа указателя, к которому приводили. Однако такое приведение типов непереносимо. long *PSW = (long *) OxFFFFFFFO; He все компиляторы допускают приведение в константных выражени- ях, но оно разрешено в Standard С. 7. Когда строковый литерал появляется как инициализатор переменной типа указатель, он порождает константу типа «указатель на char». char ‘greeting = "Для запуска нажмите клавишу Enter "; 8. Сумма или разность любого выражения из числа рассмотренных в пунктах с 3 по 7 и целого константного выражения. static short s; auto short *sp = &s + 3, *msp = &s — 3; В общем случае инициализатор типа указателя должен задавать целое, при- водимое к типу указателя или адресу, плюс (минус) целая константа. Это огра- ничение отражает возможности большинства компоновщиков. Для статического указателя инициализатором по умолчанию является ну- левой указатель. В (редких) случаях, когда нулевые указатели не представле- ны объектом с нулевыми битами, Standard С требует использования такого ну- левого указателя, который являлся бы корректным. Большинство старых компиляторов С просто инициализировали статическую область памяти нуле- выми битами. Ссылки: адресная операция & 7.5.6; типы массивов 5.4; преобразования, относящиеся к ука- зателям Ь2т. типы функций 5.8; целые константы 2.7; объявители указателей 4.5; типы указа- телей 5.3; строковые константы 2.7; обычные преобразования присвоения 6.3.2. 4.6.4 Массивы Если Ij является разрешенным инициализатором для объектов типа Т, тогда ( Io < *1 ' • ' *п-1 I является разрешенным инициализатором для типа «n-элементный массив типа Т». В отличие от предыдущих версий С, которые требовали, чтобы Jj был константным выражением, С99 разрешает использовать здесь неконстантные выражения. Инициализатор 7, используется для инициализации элемента j
124 Глава 4 в массиве (с нумерацией элементов, начинающейся с нуля). Многомерные мас- сивы инициализируются аналогично, при этом список инициализаторов запи- сывается по строкам. (Последний индекс меняется быстрее всех.) Пример Одномерный массив инициализируется перечислением его элементов: int агу[4] = {0, 1, 2, 3}; Многомерный массив инициализируется перечислением всех подмассивов: int агу[4] [2] [3] = { ( { 0, 1, 2 >, { 3, 4, 5} }, { { 6, 7, 8 }, { 9, 10, 11 } }, { { 12, 13, 14 }, ( 15, 16, 17 } }, { { 18, 19, 20 }, { 21, 22, 23 } } }; Массивы структур (см. раздел 4.6.6) могут быть инициализированы аналогичным образом: struct {int a; float b;} а[3] = { {1, 2.5}, {2, 3.9}, <0, -4.0} }; Статические и внешние массивы всегда могут быть инициализированы опи- санным способом. Standard С разрешает инициализацию автоматических мас- сивов, но эта возможность отсутствовала в изначальном определении языка С. Инициализация массивов подчиняется ряду специфичных правил: 1. Количество инициализаторов может быть меньше, чем количество эле- ментов массива. В этом случае остальные элементы массива инициали- зируются значениями по умолчанию (используемыми в статических массивах). Ситуация, когда число инициализаторов больше, чем число элементов, считается ошибкой. Пример Объявления float агу[5] ={1,2,3}; int mat[3][3] = { {1, 2}, {3} }; эквивалентны объявлениям int агу[5] = { 1.0, 2.0, 3.0, 0.0, 0.0 }; int mat[3][3] = { {1, 2, 0}, {3, 0, 0}, {0, 0, 0} }; 2. Определение границ массива не обязательно (как в неполных типах). В этом случае границы массива могут быть получены из длины инициа- лизатора. Это верно как для инициализации статических, так и автома- тических массивов. Пример Объявление int squares[] = { 0, 1, 4, 9 };
Объявления 125 эквивалентно объявлению Int squares[4] = ( 0, 1, 4, 9 } ; 3. Для инициализации переменных типа «массив типа char» могут быть использованы строковые литералы. В этом случае первый элемент мас- сива инициализируется первым элементом строки и так далее. Символ окончания строки '\0' записывается в массив в случае, если в массиве достаточное для него количество места, или если размер массива не оп- ределен. Строка может быть опционально заключена в фигурные скоб- ки. Если строка имеет длину больше, чем размер символьного массива, это не является ошибкой, но может сбивать с толку программиста. (В C++ это является ошибкой.) Массив, у которого тип элементов совместим с wchar_t может быть инициа- лизирован широкими символьными литералами аналогичным образом. Пример Объявления char х[5] = "ABCDE"; char str[] = "ABCDE”; wchar_t q[5] = L"A"; эквивалентны объявлениям char x[5] = { 'А', 'В', 'C, 'D', 'E' }; /* Без ' \0' */ char str[] = { 'А', 'B', 'C, 'D', 'E', ' \0' }; wchar_t q[5] = { L'A', L'\0’, L'\0', L'\0', L'\0' }; 4. Для инициализации массива указателей на char может быть использо- ван список строк. Пример char *astr[] = {"John”, ’’Bill", "Susan", "Mary"}; 5. Массивы переменной длины не могут быть инициализированы. Ссылки: типы массивов 5.4; символьные константы 2.7; символьные типы 5.1.3; типы указате- лей 5.3; строковые константы 2.7; массивы переменной длины 5.4.5; широкие строки 2.7.4. 4.6.5 Перечисления Инициализатор для переменной типа перечисление выглядит следующим образом описатель = выражение Инициализирующее выражение должно иметь тип, который разрешен в про- стом присвоении инициализируемой переменной. К инициализирующему зна- чению применимы обычные правила преобразования присвоения. Если пере- менная относится к классу памяти static или extern, то выражение должно быть константным. Если переменная относится к классу памяти auto или register, то разрешено любое выражение.
126 Глава 4 Пример Хороший стиль программирования требует, чтобы инициализирующее выражение имело тот же тип перечисления, что и инициализируемая переменная. Например: static enum Е { а, Ь, с } х = а; auto enum Е у = х; Ссылки: выражения приведения 7.5.1; константные выражения 7.11; типы перечисления 5.5; обычные преобразовония присвоения 6.3.2. 4.6.6 Структуры Если структура типа Т имеет п именованных компонентов типов Т:, где j = 1, ..., пи если Ij — разрешенный инициализатор для объекта типа Tj, то f , 1П } является разрешенным инициализатором для типа Т. Компоненты структуры, относящиеся к неименованным битовым полям, не принимают участия в ини- циализации. Стандарт С99 разрешает инициализаторам Ij не быть констант- ными выражениями, но в предыдущих версиях Стандарта это запрещено. Пример struct S {int a; char b[5] ; double с; } ; struct S x = { 1, "abed", 45.0 }; Статические и внешние переменные структурных типов могут быть инициа- лизированы всеми версиями компиляторов С. Автоматические и регистровые переменные структурных типов могут быть инициализированы в Standard С, и в этом случае может быть использован любой из двух видов инициализаторов структурных типов. Первый вид — обрамленный фигурными скобками список константных выражений, как для статических переменных. Второй — инициа- лизация вида описатель — выражение В этом виде выражение имеет тот же тип, что и инициализируемая перемен- ная. Некоторые старые компиляторы С не разрешают инициализацию струк- тур, которые содержат битовые поля. Как и для инициализаторов массивов, для инициализаторов структур су- ществуют специальные правила сокращенной записи. В частности, если ини- циализаторов меньше, чем компонентов структуры, то остальные компоненты инициализируются их значениями по умолчанию. Если же инициализаторов больше, чем компонентов структуры, то это является ошибкой. Пример Для следующего объявления структуры struct SI {int а; struct S2 {double b; char c; } b; int c[4]; }; инициализация
Объявления 127 struct SI х = { 1, { 4.5 } }; эквивалентна struct SI x = { 1, {4.5, АО' }, {0, 0,0,0}},- Ссылки: битовые поля 5.6.5; константные выражения 7.11; типы структур 5.6. 4.6.7 Объединения Standard С разрешает инициализацию переменных типа объединения. (Традиционный С не разрешает.) Инициализатор для статической, внешней, автоматической или регистровой переменной типа объединения должен быть обрамленным фигурными скобками константным выражением, которое явля- ется корректным инициализатором для объекта того же типа, что и первый компонент объединения. Инициализатор для автоматической или регистровой переменной типа объединения может быть любым единичным выражением того же типа объединения. В С99 для инициализации компонента объедине- ния, отличного от первого, может использоваться специальный именованный инициализатор. Пример Использование обоих видов инициализаторов для переменных типа объединение по- казано на примере инициализации переменных х и у: enum Greek { alpha, beta, gamma }; union U { struct { enum Greek tag; int Size; } I; Struct { enum Greek tag; float Size; } F; }; static union U x = ({ alpha, 42 }}; auto union U у = x; Осталось рассмотреть такие типы С, как типы функций и тип void. Так как переменные этих типов не могут быть объявлены, то вопрос их инициализа- ции носит теоретический характер. Ссылки: именованные инициализаторы 4.6.9; статический экстент 4.2; типы объединения 5.7. 4.6.8 Опускание скобок Несмотря на то, что С в большинстве случаев разрешает опускать фигурные скобки в списках инициализаторов, часто для большей ясности их лучше ос- тавлять. Основные правила для фигурных скобок приведены ниже. 1. Если инициализируется переменная типа массива или структурного типа, то внешняя пара фигурных скобок не может быть опущена. 2. В противном случае, если список инициализаторов содержит коррект- ное число элементов для инициализируемого объекта, то скобки могут быть опущены.
128 Глава 4 Пример Наиболее часто эти правила применяются для опускания внутренних фигурных скобок при инициализации многомерных массивов: int matrix[2][3] = {1,2, 3, 4, 5, 6 }; /* эквивалентно { { 1, 2, 3}, {4, 5, 6} } */ Многие компиляторы С интерпретируют списки инициализаторов не со- всем верно, допуская слишком много или слишком мало фигурных скобок. Мы советуем использовать простые инициализаторы и использовать скобки так, чтобы они явно отражали структуру инициализируемых объектов. 4.6.9 Именованные инициализаторы С99 разрешает давать имена компонентам переменной сложного типа (структура, объединение или массив) в списке инициализаторов. Именован- ные и позиционные (неименованные) инициализаторы могут находится в од- ном списке инициализаторов. В списке инициализаторов массива, именованный инициализатор (desig- nator) имеет вид [ е ], где константное выражение е определяет индекс элемен- та массива. Если массив имеет неопределенный размер, то разрешен любой не- отрицательный индекс. В таком случае размер массива определяет наиболь- ший инициализированный явно индекс. Если позиционный инициализатор следует за именованным, то позиционный инициализатор инициализирует элемент, который следует за элементом, инициализированным именованным инициализатором. Таким образом, заданные позже в списке инициализирую- щие значения могут заменять собой значения, заданные раннее. Пример Для каждого из приведенных ниже инициализируемых массивов дан комментарий, который информирует о всех результирующий значениях инициализируемого мас- сива. int al[5] = { [2] = 100, [1] = 3 }; /* { 0, 3, 100, 0 } */ int а2[5] = { [0] = 10, [2] = -2, -1, -3 }; /* { 10, 0, -2, -1, -3 }; */ int аЗ[] = { 1, 2, 3, [2] = 5, 6, 7 }; /* { 1, 2Z 5, 6, 7 длина массива аЗ равна 5 */ В списке инициализаторов структуры, именованный инициализатор имеет вид .с, где с — имя компонента структуры. Если позиционный инициализатор следует за именованным, то позиционный инициализирует компонент, кото- рый находится непосредственно за тем, который был инициализирован имено- ванным инициализатором. Таким образом, заданные позже в списке инициа- лизирующие значения могут заменять собой значения, заданные раннее. Пример Каждая из приведенных ниже инициализаций сопровождается комментарием, ко- торый дает информацию о значениях инициализированных структур.
Объявления 129 struct S {int a; float b; char c[4]; }; struct S si = { .c = “abc" } ; /* {0, 0.0, "abc"} */ struct S s2 = { 13, 3.3, "xxx", .b = 4.5 }; /* {13, 4.5, "xxx"} */ struct S s3 = { .c = {'a', 'b', 'c', '\0' } } ; /* {0, 0.0, "abc"} */ В списке инициализаторов для объединения именованный инициализатор имеет вид .с, где с — это один из компонентов объединения. Это позволяет инициализировать объединение с помощью инициализации любого из его ком- понентов, а не только первого. Пример Каждая из приведенных ниже инициализаций объединений сопровождается ком- ментарием, который содержит информацию о значениях всех инициализированных компонентах объединения. union U {int a, float b; char c[4] ; }; union U ul = { .c = "abc" }; /* Значение ul.c равно "abc\0”. Для других компонентов значения не определены */ union U u2 = { .а = 15 }; /* Значение и2.а равно 15. Для других компонентов значения не определены */ union U u2 = { .Ь = 3.14 } ; /* Значение иЗ.Ь = 3.14. Для других компонентов значения не определены */ Вложенные сложные объекты могут быть инициализированы с помощью именованных инициализаторов соответствующего вида. Именованные ини- циализаторы могут быть объединены для инициализации компонентов сле- дующих уровней вложения. Пример Каждая из приведенных ниже инициализаций сопровождается комментарием, ко- торый содержит информацию о значениях всех проинициализированных компо- нент. struct Point {int х; int у; int z; }; typedef struct Point Pointvector[4]; Pointvector pvl = { [0] .x = 1, [0] .y = 2, [0] .z = 3, [1] = {.x = 11, .y = 12, .z = 13}, [3] = {.y = 3 } }; /* {{1, 2, 3}, {11, 12, 13}, {0, 0, 0}, {0, 3, 0}} */ typedef int Vector[3]; typedef int Matrix[3] [3] ; struct Trio { Vector v; Matrix m; }; struct Trio t = { .m = { [0] [0] = 1, [1] [1] = 1, [2] [2] = 1 }, .v = { [1] = 42, 43 } }; /* {{0, 42, 43}, {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}} */
130 Глава 4 4.7 Неявные объявления До появления С99 для вызова внешней функции ее объявление не было обя- зательным. Если компилятор обнаруживал идентификатор id со следующей за ним левой круглой скобкой, причем id не был заранее объявлен, то в самую внутреннюю область видимости неявным образом вставлялось объявление вида extern int id(); Реализации С99 выдают диагностическое сообщение, если идентификатор id не был объявлен ранее как функция. Но после этой диагностики они могут также как и предыдущие версии делать неявное объявление. Некоторые реа- лизации, не отвечающие Стандарту, могут объявлять идентификатор на верх- нем уровне, а не в самой внутренней области видимости. Пример Использование объявлений функций по умолчанию является плохим стилем про- граммирования и приводит к ошибкам, в частности, к ошибкам неверных возвра- щаемых типов. Если функция, возвращающая указатель, например, malloc (раз- дел 16.1), будет неявно определена как extern int malloc(); вместо правильного определения extern char *malloc(); /* В Standard С данная функция возвращает (void *) */ то вызовы этой функции могут не работать, если типы int и char * имеют разное представление. Предположим, что тип int занимает два байта, а указатель четыре. Когда компилятор обрабатывает int *р; р = (int *) malloc(sizeof(int)); он приводит возвращаемое функцией malloc значение (ожидаемая длина его — два байта) к четырем байтам, которые требует указатель. В результате переменной р присваивается только младшая часть адреса, возвращаемая функцией malloc, и программа будет порождать ошибку, если выделить область памяти, достаточно большую, чтобы функция malloc возвратила адрес памяти более OxFFFF. 4.8 Внешние имена Важный вопрос, связанный с внешними именами, касается согласованности объявлений внешнего имени в нескольких файлах. Что произойдет, если, напри- мер, два объявления одной и той же внешней переменной будут в разных файлах проинициализированы по-разному? Во избежание этой и других ошибочных си- туаций, в группе файлов принято выделять одно определяющее объявление (defining declaration), а другие объявления считать ссылочными (referencing declaration), т.е. теми, которые ссылаются на определяющее объявление. Общеизвестным недостатком С является то, что ссылочные и определяю- щие объявления внешних переменных достаточно сложно различать. Обычно компиляторы используют одну из четырех моделей для определения того, яв- ляется ли объявление верхнего уровня определяющим.
Объявления 131 4.8.1 Модель инициализатора Наличие инициализатора в объявлении верхнего уровня свидетельствует о том, что данное объявление определяющее, а все остальные являются ссы- лочными. Для всех файлов программы у идентификатора должно быть только одно определяющее объявление. Эта модель унаследована от Standard С и име- ет одно дополнительное правило, которое обсуждается в следующем разделе. 4.8.2 Модель с опущенным спецификатором класса памяти В рамках этой модели спецификатор класса памяти extern для каждой внешней переменной должен быть явно включен во все ссылочные объявления и опущен в определяющем объявлении. Определяющее объявление может включать инициализатор. Если в объявлении встречается и спецификатор extern и инициализатор, то это считается ошибкой. В Standard С объявление верхнего уровня, не содержащее спецификатор класса памяти или инициализатор, считается предварительным определением (tentative definition). Это означает, что подобное объявление рассматривается как ссылочное, но если в файле не содержится другого объявления этой пере- менной с инициализатором, то предварительное определение считается дейст- вительным. В C++ при наличии инициализатора спецификатор extern игнорируется. 4.8.3 Соттоп-модель Эта модель называется Common-моделью потому, что использует тот же ме- ханизм, что и в реализациях языка программирования Фортран, где множест- венные ссылки на блок COMMON объединяются в одно определяющее объяв- ление. Определяющие и ссылочные внешние объявления относятся к классу памяти extern (явно или по умолчанию). Среди всех объявлений для каждого внешнего имени во всех скомпонованных объектных файлах программы толь- ко одно объявление может иметь инициализатор. Во время компоновки все внешние объявления одного и того же идентификатора (во всех объектных файлах) преобразуются в одно определяющее объявление, причем не обяза- тельно ассоциированное с некоторым файлом. Если одно из объявлений содер- жит инициализатор, то этот инициализатор используется для инициализации данного объекта. (Если несколько объявлений содержат инициализаторы, то результат не определен.) Эта модель обеспечивает решение, наиболее безболезненное для программи- ста и наиболее удобное для программного обеспечения. 4.8.4 Смешанная Соттоп-модель Эта модель использует возможности моделей с «опущенным спецификато- ром класса памяти» и Common. Она применяется во многих версиях UNIX. 1. Если опущен спецификатор extern и присутствует инициализатор, то это объявление считается определяющим. Если таких объявлений два и более, то это приводит к ошибке во время компоновки программы или ранее.
132 Глава 4 2. Если опущен спецификатор extern и отсутствует инициализатор, то ис- пользуется определение в стиле COMMON языка Фортран. Для одного идентификатора может сосуществовать множество таких определений. 3. Если спецификатор extern присутствует, то объявление считается ссы- лочным на имя, определенное где-либо в другом месте. Такое объявле- ние не должно иметь инициализатор. Если у внешней переменной нет явного инициализатора, то переменная инициализируется так, как если бы у нее был инициализатор, равный целой константе 0. 4.8.5 Резюме и рекомендации В таблице 4.7 представлены интерпретации объявлений верхнего уровня в рамках используемых моделей. Таблица 4.7. Интерпретации объявлений верхнего уровня Модель Объявление верхнего уровня Инициолизотор Опущен клосс памяти (и C++) Common Смешанная Common Standard С int х; Ссылочное объявление Определяющее объявление Определяющее или ссылочное объявление Определяющее или ссылочное объявление Ссылочное объявление* 1 int х = 0; Определяющее объявление Определяющее объявление Определяющее объявление Определяющее объявление Определяющее объявление extern int х; Ссылочное объявление Ссылочное объявление Определяющее или ссылочное объявление Ссылочное объявление Ссылочное объявление extern int x = 0; Определяющее объявление Неверно Определяющее объявление Неверно Определяющее объявление 1 Если в файле не появляются последующие определяющие объявления, то данное объявление считается определяющим Для сохранения совместимости с большинством компиляторов, мы реко- мендуем следовать следующим правилам: 1. Для каждой внешней переменной использовать одну точку определе- ния (один файл). В определяющем объявлении опускать класс памяти extern и включать явный инициализатор: int errcnt = 0; 2. В каждом исходном или заголовочном файле ссылаясь на внешнюю пе- ременную, где-либо определенную, использовать класс памяти extern и не включать в объявление инициализатор: extern int errcnt; Вне зависимости от того, является ли объявление ссылочным или опреде- ляющим, все они должны быть одного типа во всех файлах, составляющих программу. Компилятор С не может проверить объявления в разных файлах программы на совместимость, а платой за подобные ошибки будет неустойчи-
Объявления 133 вое поведение программы во время ее исполнения. Программа lint, которая часто поставляется с компилятором С в Unix системах, может проверить фай- лы программы на совместимость объявлений. Для операционных систем Windows и Unix существует также несколько коммерческих продуктов, кото- рые выполняют те же действия. 4.8.6 Безссылочные внешние объявления Хотя язык С не требует этого, обычно объявления внешних переменных или функций, на которые нет ссылок в программе, игнорируются. Например, если объявление «extern double fft();» появляется в программе, но функция fft не используется, то компоновщику это внешнее объявление не передается. Поэтому функция fft не будет загружена с программой и соответственно не бу- дет бесцельно занимать оперативную память. 4.9 Совместимость с C++ 4.9.1 Область видимости В C++ определения сложных типов struct и union имеют блочную область видимости. Это означает, что те объявления типов, которые находятся в опре- делениях типов struct и union не видны снаружи, в отличие от Standard С (раздел 5.6.3). Для сохранения совместимости надо просто переместить любое объявление типов за границы структуры. (Некоторые реализации C++ могут разрешить это как анахронизм в случае, если не возникает двусмысленности.) Пример В приведенном ниже фрагменте исходного текста программы, структура t определе- на внутри структуры s, хотя ссылаются на нее вне этой структуры. Это недопустимо в C++. struct S { struct t (int a; int b; } fl; /* определяет здесь t */ } xl; struct t x2; /* здесь используется t. Это верно для С, но является ошибкой в C++ */ Ссылки: область видимости 4.2.1; компоненты структуры 5.6.3. 4.9.2 Теги и имена typedef Структуры и имена тегов объединений не следует использовать как имена typedef за исключением таких же отмеченных тегами типов. В C++, имена те- гов неявно объявляются с помощью typedef как теги. (Однако они могут быть спрятаны последующими объявлениями переменных или функций с тем же именем и в той же области видимости.) Это может приводить к выдаче диагно- стических сообщений, или — в редких случаях — к несколько отличному по- ведению.
134 Глава 4 Пример Ниже приведены некоторые примеры, которые могут приводить к выдаче диагно- стики в С или C++. typedef struct nl {..} nl; /* Корректно как в С, тах и в C++ */ struct п2 { ..} ; typedef double n2; /* Корректно в С, но не в C++ */ struct пЗ (...}; пЗ х; /* Корректно в C++, но не в С */ Однако имя тега может быть свободно использовано как имя переменной или функ- ции. Приведенная ниже последовательность объявлений применима как в С, так и в C++, хотя подобных двусмысленностей все же следует избегать: struct п4 {...}; int п4; struct п4 х; Объявление тега struct во внутренней области видимости в C++ может скрыть объ- явление переменной из внешней области видимости. Это может привести к тому, что поведение программы поменяется без предупреждения. В приведенном ниже фрагменте исходного текста выражение sizeof(ary) ссылается на размер массива, если это С, или на размер структуры, если это C++. int агу[10]; void f(int х) { struct ary { ... }; /* В C++, это скрывает предыдущее объявление агу */ х = sizeof(агу); /* В С и C++ значения х будут разные! */ } См. раздел 5.13.2 в отношении совместимости переопределений typedef в C++. Ссылки: пространство имен 4,2.4; переопределение имен typedef 5.10.2. 4.9.3 Спецификаторы класса памяти в типах В объявлениях типов не следует помещать спецификаторы класса памяти. Они игнорируются в традиционном С, но недопустимы в C++ и Standard С. Пример static struct s {int a; int b; ]; /* Неверно */ Ссылки: классы памяти в типах 4.4.2. 4.9.4 Квалификатор типа const Объявление верхнего уровня, которое имеет квалификатор типа const, но не имеет явного объявления класса памяти, считается static в C++, но extern в С. Для сохранения совместимости следует всем объявлениям верхнего уров- ня с квалификатором const явно указывать класс памяти. В C++ строковые константы неявно имеют квалификатор const. В С это не так.
Объявления 135 Пример Приведенное ниже объявление имеет разный смысл в С и C++: const int cl = 10; Однако приведенные ниже объявления будут иметь одинаковый смысл в С и C++: static const int с2 = 11; extern const int c3 = 12; За исключением ссылочных внешне-определенных констант, все объявления в C++ с const должны иметь инициализатор. Ссылки: квалификатор типа const 4.4.4. 4.9.5 Инициализаторы В C++, при инициализации символьного массива фиксированного размера строковым литералом (или широким строковым литералом для массива типа wchar_t) необходимо наличие места для хранения всей строки, включая тер- минальный символ окончания строки. Пример char str[5] = "abcde"; /* Верно в С, но не в C++ */ char str[6] = "abcde"; /* Верно как в С, так и в C++ */ 4.9.6 Неявные объявления Неявные объявления функций (раздел 4.7) не разрешены ни в C+ + , ни в С99. Все функции должны быть объявлены явно до их использования. Ссылки: неявные объявления 4.7. 4.9.7 Определяющие и ссылочные объявления В C++ нет предварительных определений переменных верхнего уровня. То, что в С считается предварительным определением, в C++ считается действи- тельным определением. То есть последовательность объявлений int i ; int i ; будет считаться корректной в Standard С, но вызовет ошибку повторного опре- деления в C++. Пример Данное правило также применимо к статическим переменным. Это означает, что нель- зя создавать взаимно-рекурсивные, статически инициализированные переменные. struct cell {int val; struct cell ‘next;); static struct cell a; /* Предварительное определение ★/ static struct cell b = {0, Sa}; static struct cell c = {1, Sb};
136 Глава 4 Это не составляет проблемы для глобальных переменных: первый спецификатор static заменяется спецификатором extern, а второй и третий спецификаторы static удаляются. (Вы можете объявлять взаимно-рекурсивные, статически инициализи- рованные переменные в C++, но совместимость с С при этом будет отсутствовать.) Ссылки: ссылки структурных типов 5.6.1; предварительное объявление 4.8 2. 4.9.8 Связывание функции При вызове функции С из C++ функция должна быть объявлена со связы- ванием в стиле «С». Более детально этот вопрос обсуждается в главе 10. Пример Если в программе, написанной на C++, необходимо вызвать функцию f, которая была скомпилирована реализацией С, то надо задать следующее объявление: /* Это программа, написанная на C++. */ extern "С" int f(void); /* функция f написана на С */ 4.9.9 Функции без аргументов В C++, функция, объявленная с пустым списком параметров считается не принимающей аргументы, в отличие от С, где такая функция считается функ- цией с неопределенными аргументами. То есть объявление в C++ int f() экви- валентно объявлению в С int f(void). 4.10 Упражнения 1. Ниже приведено определение статической функции Р. Каким будет значение функции Р(6), если функцию до этого не вызывали? Каким оно будет после второго вызова функции? static int P(int х) { int i = 0; i = i + 1; return i*x; ) 2. Приведенный ниже фрагмент программы демонстрирует блок, содер- жащий различные объявления идентификатора f. Конфликтуют ли приведенные объявления? Если да, то вычеркните часть из них так, чтобы осталось максимальное количество разных объявлений, и при этом программа была корректной. ( extern double f(); int f; typedef int f; struct f (int f,g;); union f (int x, y;J;
Объявления 137 enum {f, b, s}; f: ... } 3. В приведенном ниже фрагменте программы объявлено три переменных i типов int, long и float. В каких строках каждая переменная объявля- ется и используется? 1 int i; 2 void f(i) 3 long i; 4 ( 5 long 1 = i; 6 ( 7 float i; 8 i = 3.4; 9 } 10 1 = i + 2; 11 1 12 int *p =fii; 4. Напишите объявления в С, которые соответствуют следующим требова- ниям. Для объявления функций используйте прототипы. (а) Р — внешняя функция, не принимающая аргументов и не возвра- щающая результат своего выполнения. (b) i является часто используемой локальной переменной целого типа, для которой необходима оптимизация по скорости. (с) LT является синонимом для типа «указатель на символ». (d) Q — внешняя функция, принимающая два аргумента и не возвра- щающая результат. Первый аргумент, i, — переменная целого типа, а второй, ср, — строка, которая не будет изменятся. (е) R является внешней функцией с одним аргументом р. Этот аргу- мент представляет собой указатель на функцию, которая принимает один 32-битный аргумент целого типа i и возвращает указатель на зна- чение типа double. R возвращает целое значение. Предположим, что размер типа long составляет 32 бита. (f) STR представляет собой статическую, неинициализированную символьную строку, которая может быть изменена и содержать до 10 символов без учета терминального символа окончания строки. (g) STR2 является символьной строкой, инициализированной строко- вым литералом, который является значением макроса INIT_STR2. По- сле инициализации строка не будет изменятся. (h) IP — указатель на целое, инициализированный адресом перемен- ной i. 5. Матрица m объявлена как int m[3][3];. Первый индекс определяет но- мер строки, а второй — номер столбца. Напишите инициализатор для ш, который помещал бы единицы в первый столбец, двойки во второй столбец и тройки в третий. 6. Даны объявления const int * cip; int * const cpi;
138 Глава 4 int i ; int * ip; какие из следующий присвоений разрешены? (a) cip = ip; (b) cpi = ip; (c) *cip = i; (d) *cpi = i; 7. Используя именованные инициализаторы C99, напишите объявление и инициализатор для матрицы identify с элементами типа int и размер- ностью 3 на 3. Инициализатор должен присваивать единицу элементам identify! 1 ][ 1 ], identifyt2][2] и identify[3][3], Остальным элементам мат- рицы должны быть присвоены нули. 8. Напишите объявления для двух структур со структурными тегами left и right. Структура left должна содержать поле типа double с именем data и указатель с именем link на структуру right. Структура right должна содержать поле типа int с именем data и указатель с именем link на структуру left. 9. Вы только что купили новый компилятор, отвечающий стандарту С99, и перекомпилируете с его помощью все имеющееся у вас программное обеспечение. Компилятор С89 компилировал его без ошибок, но компи- лятор С99 сообщает об ошибках. Для каждой из приведенных ниже ошибок объясните, что могло их вызвать: (а) Компилятор С99 не принимает вызов функции, сообщая о том, что функция не была определена. (Ь) Компилятор С99 не принимает объявление локальной переменной register i; 10. Предположим, что в вашей программе определены два макроса: fm — как функциональный, и от — как объектный (раздел 3.3). Если в ва- шей программе также определены локальные переменные int fm; int от; будут ли конфликты между этими объявлениями и макросами? Обсу- дите результат компиляции программы.
Глава 5 Типы Тип — это совокупность двух множеств: множества допустимых значений (value) и множества операций (operation) над ними. Например, целый тип со- стоит из множества целых значений из некоторого диапазона (range) и множе- ства операций над этими значениями, состоящего из сложения, вычитания, проверки на равенство и т.д. Тип чисел с плавающей точкой включает в себя набор чисел с представлением, отличным от целых чисел, а также набор опера- ций: сложение чисел с плавающей точкой, вычитание чисел с плавающей точ- кой, проверка на равенство чисел с плавающей точкой и т.д. Говорят, что переменная или выражение «имеет тип Т», когда ее значения ограничены областью определения Т. Типы переменных определяются их объ- явлениями. Типы выражений задаются определениями операций выражения. В языке С предусмотрен обширный набор встроенных типов, включая не- сколько целых типов, числа с плавающей точкой, указатели, перечисления, массивы, структуры, объединения и функции. Типы С удобно разделять на категории, как показано в таблице 5.1. Целые шипы (integral type) включают все виды целых, символы и перечисления. Ариф- метические типы (arithmetic type) включают целые типы и типы с плавающей точкой. Скалярные типы (scalar type) включают арифметические типы и указа- тели. Функциональные типы (functional type) представляют собой тип «функ- ция, возвращающая значение типа ...». Составные типы (aggregate type) вклю- чают массивы и структуры. Типы объединений (union type) создаются с помощью спецификатора union. Тип void не имеет ни значений ни операций. Типы _Bool, _Complex и —Imaginary являются нововведениями Стандарта С99. Булев тип (_Bool) представляет собой беззнаковый целый тип, в отличие от шести комплексных типов, которые представляются типами с плавающей точкой. Стандарт С99 классифицирует арифметические типы по областям оп- ределения (domain): шесть комплексных типов имеют комплексную область определения; все остальные арифметические типы находятся в вещественной области определения и являются вещественными типами (real type). В данной главе обсуждаются все типы С. Для каждого типа показано объяв- ление объектов соответствующего типа, диапазон значений типа, ограничения на размер или представление типа, а также набор операций, определенный на множестве значений данного типа. Ссылки: типы массивов 5.4; булев тип 5.1.5; символьные типы 5.1.3; комплексные типы 5.2.1; объ- явления 4.1; типы перечисления 5.5; типы с плавающей точкой 5.2; функциональные типы 5.8; це- лые типы 5.1; указательные типы 5.3, структурные типы 5.6; типы объединений 5.7; тип void 5.9.
140 Глава 5 Таблица 5.1. Тины и категории С Типы С Категории типов short, int, long, long long (зноковые и беззноковые) char (знаковый и беззнаковый) Целые типы Арифметические типы1 Скалярные типы _Воо12 епит {...} float, double, long double Типы с плавающей точкой float -Complex, double -Complex, long double -Complex, float -Imaginary, double -Imaginary, long double -Imaginary2 T* Тилы указателей T [...] Типы массивов Составные типы struct {...} Типы структур union {...} Типы объединений T (...) Типы функций void Тип void 1 Зо исключением типов комплексных чисел, все арифметические типы относятся к вещественным типам, z Является нововведением Стандарта С99. Тип Imaginary является опциональным. 5-1 Целые типы Язык С предусматривает большее разнообразие целых типов и операций, чем большинство других языков программирования. Это разнообразие отражает разнообразие длин слов и видов арифметических операций различных компью- теров, что позволяет говорить о точном соответствии программ, написанных на С, используемому аппаратному обеспечению. В языке С целые типы данных ис- пользуются для представления следующих разновидностей значений: 1. знаковые или беззнаковые целые значения, для которых используются обычные арифметические операции и операции отношения; 2. битовые векторы с операциями «НЕ», «И», «ИЛИ», «ИСКЛЮЧАЮ- ЩЕЕ ИЛИ», а также левый и правый сдвиг; 3. булевы значения, для которых ноль считается «ложью», ненулевые значения считаются «истиной», а целое число 1 считается канониче- ским значением «истины»; 4. символы, которые представлены на компьютере целыми кодами. Типы перечисления являются целочисленными или похожими на целочис- ленные типами. Они рассматриваются в разделе 5.5.
Типы 141 Для целых чисел Standard С требует от реализаций использование двоично- го кодирования. Этот факт является признанием того, что многие низкоуров- невые операции С невозможно реализовать переносимыми для компьютеров, в которых представления не являются двоичными. Целые типы удобно делить на четыре класса: знаковые типы, беззнаковые типы, булев тип и символы. Каждый из этих классов имеет набор специфика- торов типа, который используется для объявления объектов класса. спецификатор-целого-типа: спецификатор-знакового-типа спецификатор-беззнакового-типа спецификатор-символьного-типа спецификатор-бу лева-типа 5.1.1 Знаковые целые типы Язык С обеспечивает программиста четырьмя стандартными знаковыми ти- пами, обозначенными спецификаторами типа short, int, long и long long в по- рядке неубывания их размера. Тип signed char также является знаковым це- лым типом, но обсуждается в разделе 5.1.3. Стандарт С99 представляет тип long long, а также расширенные целые типы (раздел 5.1.4). Каждый тип может быть обозначен несколькими эквивалентными способа- ми. В приведенном ниже синтаксисе эквивалентные обозначения приведены для каждого из четырех типов. спецификатор-знакового-типа : short или short int или signed short или singed short int int или signed int или signed long или long int или singed long или signed long int long long или long long int или singed long long или signed long long int Ключевое слово signed было введено Стандартом С89. Для совместимости с бо- лее ранними версиями С оно может быть опущено. Единственным случаем, ко- гда наличие спецификатора signed может повлиять на смысл программы, яв- ляется случай, когда signed используется совместно с типом char и с типами битовых полей в структуре. В этом случае можно различать знаковое целое и «простое целое» (т.е. записанное без спецификатора signed). Standard С определяет для большинства целых типов минимальную точ- ность. Тип char должен иметь размер как минимум 8 бит, тип short как мини- мум 16 бит, тип long как минимум 32 бита, а тип long long как минимум 64 бита. (То есть С99 требует наличия 64-битовых целых типов и полный набор 64-битовых арифметических операций.) Фактические диапазоны целых типов записаны в заголовочном файле limits.h. Точный диапазон значений, который может быть представлен знаковым це- лым типом, зависит не только от числа бит, используемых в представлении, но также и от способа кодирования. Наиболее часто используемый двоичный ме- тод кодирования для целых чисел называется двоичным дополнительным ко- дом (twos-complement notation), в которой знаковое число целого типа, пред- ставленное п битами будет иметь диапазон от -2П-1 до 2п-1-1 и будет закодиро- вано следующим образом:
142 Глава 5 1. Старший бит (самый левый) слова является битом знака. Если знако- вый бит равен 1, то число отрицательное. В противном случае число по- ложительное. 2. Положительные числа используют обычную двоичную последователь- ность: о = ооо...оооо2 1 = 000...00012 2 = 000...00102 3 = ООО...ОО112 4 = 000...01002 В «-битовом слове, если не рассматривать знаковый бит, п-1 значащих битов, которые позволяют представить целые числа от 0 до 2'1-/-1 3. Для получения отрицательного целого из положительного, в нем дополня- ют все биты, и затем к результату добавляется единица. Таким образом, для вычисления числа -1 берется 1 (00...00012), выполняется дополнение битов (11...11102) и затем добавляется единица (11...11112 = -1). 4. Максимальное отрицательное значение 10...00002 или -2П-1 не имеет положительного эквивалента. Отрицание этого числа приводит к этому же числу. Существуют также другие способы бинарного кодирования. Это код с допол- нением до единицы (ones-complement notation), в котором отрицательные числа формируются простым дополнением всех битов слова и код модуля со знаком (sign magnitude notation), в котором отрицательные числа формируются про- стым дополнением знакового бита. Эти альтернативные способы кодирования имеют диапазон от -(2п1-1) до 2п-1-1. Эти способы кодирования имеют на одно отрицательное значение меньше и два представления нуля (позитивное и негативное). Представление положительных целых чисел во всех трех нота- циях идентично. В Standard С допустимы все три нотации. Standard С требует от реализации указания диапазонов целых типов в заго- ловочном файле limits.h; он также определяет для каждого целого типа макси- мально представимый диапазон, который предполагается во всех реализаци- ях, соответствующих Стандарту ISO. Идентификаторы, которые должны быть определены в заголовочном файле limits.h приведены в таблице 5.2. Реализа- ции могут присваивать этим идентификаторам свои собственные значения, но они не должны быть меньше по абсолютной величине значений, приведенных в таблице. Их знаки также должны совпадать. Следовательно, реализация, со- ответствующая Стандарту ISO, не может представлять тип int только восемью битами, а в строго соответствующей стандарту программе, значение -32768 не может быть представлено типом short. (Такой подход принят для согласова- ния с компьютерами, которые используют представление двоичных целых ко- дами с дополнением до единицы.) Программисты, использующие реализации, не соответствующие Стандарту ISO, могут создать для своей реализации собст- венный заголовочный файл limits.h. Перечисленные в этом файле диапазоны могут не отвечать размерам типов из-за возможного наличия битов заполне- ния (см. раздел 6.1.6). Дополнение 1 к Стандарту С89 добавляет идентификаторы WCHARMAX и WCIIAR_MIN для указания минимального и максимального значений, представимых типом wchar_t. Однако эти идентификаторы определены в заго-
Типы 143 ловочном файле wchar.h, а не в limits.h. Стандарт С99 добавил новый заголо- вочный файл stdint.h, который содержит ограничения дополнительных целых типов. Таблица 5.2. Значения, определенные в заголовочном файле limits.h Имя Минимальное значение Смысл CHAR_BIT 8 размер типа char, в битах SCHARMIN -{27-l); -127 минимальное значение для типо signed char SCHARMAX 27-l; 127 максимальное значение для типа signed char UCHARMAX 28-l; 255' максимальное значение для типа unsigned char SHRTMIN -(2'5-l); -32,767 минимальное значение для типа short int SHRTMAX 2'5-l; 32,767 максимальное значение для типа short int USHRTMAX 2'6-l; 65,535 максимальное значение для типа unsigned short int INTMIN -(2'5-1); -32,767 минимальное значение для типа int INTMAX 2'5-1; 32,767 максимальное значение для типа int UINTMAX 2’6—1; 65,535 максимальное значение для типа unsigned int LONGMIN ~(23'~1); -2,147,483,647 минимальное значение для типа long int LONGMAX 23'-l; 2,147,483,647 максимальное значение для типа long int ULONGMAX 232-1; 4,294,967,295 максимальное значение для типа unsigned long int LLONGMIN -(263-1); -9,223,372,036,854,775,807 минимальное значение для типа long long int LLONGMAX 263-l; 9,223,372,036,854,775,807 максимальное значение для типа long long int ULLONGMAX 264-l; 18,446,744,073,709,551,615 максимальное значение для типо unsigned long long CHARMIN SCHAR_MIN или 0" минимальное значение для типа char CHAR_MAX SCHAR MAX или UCHAR_MAX " моксимольное значение для типа char MBLENMAX 1 максимальное количество байт в многобайтовом символе в любой поддерживаемой локализации * UCHARMAX должен быть 2CHAR-BIT-1. Если по умолчанию тип char знаковый, то SCHARMIN, иначе О Если по умолчанию тип char знаковый, то SCHAR_MIN, иначе UCHAR_MAX Пример Ниже приведены некоторые примеры типичных объявлений знаковых целых чисел: short i, j; long int 1; static signed int k;
144 Глава 5 Для того чтобы создавать программу максимально переносимой, не следует исполь- зовать тип int для значений вне диапазона от -32767 до 32767. Если этот диапазон недостаточен, то следует использовать тип long. Определение специальных целых типов с помощью имен typedef для целей каждой частной программы является хо- рошим стилем программирования. Например: /* invdef.h Список определений для компьютера XXX */ typedef short part_number; typedef int order_quantity; typedef long purchase_order; Наилучшим решением в Стандарте С99 является использование имен одного из рас- ширенных целых типов, определяющих нужную точность. /* invdef.h Список определений для компьютера XXX */ ffinclude <stdint.h> typedef uint_least64_t partnumber; // как минимум 64 бита typedef int_fast32_t order_quantity; //32 бита. Быстрый тип typedef int32_t purchase_order; // точно 32 бита Пример Для представления булевых значений в языке С, может использоваться любой це- лый тип. Величина 0 (ноль) представляет значение «ложь», а остальные ненулевые величины представляют значение «истина». Булевы выражения интерпретируются как 0 (ноль) в случае лжи и как 1 в случае истины. К примеру, i = (a<b) присваивает целой переменной i значение 1, если а меньше b и 0 в обратном случае. Аналогично, выражение if {i) statementl; /* Выполнить, если i не равно 0 */ else statement2, /* Выполнить, если i равно 0 */ приводит к выполнению statementl в случае, если i не равно 0 (истинно) и к выпол- нению statement2 в противном случае. Стандарт С99 ввел отдельный булев тип — _Воо1. Он также ввел заголовочный файл stdbool.h, который определяет более удобное имя типа bool и булевы значения true (истина) и false (ложь). Эти имена совместимы с булевым типом C++, но отличны от макросов FALSE и TRUE, которые традиционно использовались в языке С: #include <stdbool.h> bool b; b = (x < у) && {у < z) ; if (b) ... Программисты, у которых нет доступа к компилятору Стандарта С99 могут сами легко определить bool, true и false. Ссылки: битовые .поля в структурах 5.6.5; тип _Воо1 5.1.4; описатели 4 1; расширенные це- лые типы 5.1.4; целые констонты 2.7.1, спецификатор знакового типа 5 11; stdbool.h 11 3, stdint.h гп 21; преобразования типов гл 6: имена typedef 5 10. 5.1.2 Беззнаковые целые типы Для каждого из знаковых целых типов существует соответствующий без- знаковый (unsigned) тип, который имеет такой же размер, но использует дру- гой способ кодирования. Беззнаковый тип определяется как соответствующий знаковый тип с предшествующим спецификатором unsigned (который заменя- ет спецификатор signed при его наличии).
Типы 145 спецификатор-беззнакового-типа'. unsigned short intoni( unsigned intoni< unsigned long intoni4 unsigned long long intonl( (C99) В любом случае, ключевое слово int не является обязательным, но желатель- ным. Выбор определенного беззнакового типа опирается на соображения, ко- торые мы уже рассматривали для знаковых целых типов. В Стандарте С99 вве- дены типы unsigned long long int и _Bool, которые также считаются беззнако- выми, но рассматриваются отдельно. Все беззнаковые типы используют прямое двоичное кодирование, независи- мо от того, какая нотация используется для соответствующего знакового типа — с дополнением до двух, с дополнением до одного или модуль со знаком. Знаковый бит интерпретируется как обычный бит данных. Следовательно, n-битное слово может представить целое число от 0 до 2П-1. Большинство ком- пьютеров могут легко интерпретировать значение слова, используя как знако- вую так и беззнаковую нотацию. Например, если используется двоичный до- полнительный код, то слово II...IIII2 (длиной п бит) может быть представлено как -1 (с использованием знаковой нотации) или как 2П~1 (с использованием беззнаковой нотации). Целые числа от 0 до 2п-1-1 представляются в знаковой и беззнаковой нотации идентично. Точные диапазоны беззнаковых типов реа- лизации Standard С задокументированы в заголовочном файле limits.h. Доступные для числа операции зависят от того, является число знаковым или нет. Все арифметические операции над беззнаковыми целыми числами ве- дут себя согласно правилам модульной (конгруэнтной) арифметики по моду- лю 2П. Поэтому, например, добавление единицы к наибольшему числу беззна- кового типа гарантированно приведет к нулю. В данном случае поведение в случае переполнения четко определено. В выражениях, содержащих как знаковые, так и беззнаковые целые ис- пользуются беззнаковые операции. В разделе 6.3.4 обсуждаются выполняемые преобразования, а в главе 7 рассматриваются результаты операций над беззна- ковыми операндами. Пример Эти преобразования могут вызвать удивление. Например, так как беззнаковые це- лые всегда неотрицательны, вы можете ожидать, что следующий тест будет всегда истинным: unsigned int u; if (u > -1) ... Однако он всегда ложен! Знаковое число -1 преобразуется в беззнаковое целое до сравнения, причем в наибольшее беззнаковое целое и значение переменной и просто не может быть больше этого числа. Изначальное определение языка С предоставляло только один беззнаковый тип — unsigned. На данный момент, большинство реализаций, не отвечающих Стандарту С, обеспечивают полный диапазон беззнаковых типов. Ссылки: тип _Воо1 5.1.5; преобразования целых чисел 6.2.3; константы 2.7; limits.h 5.1.1; знаковые типы 5.1.1.
146 Глава 5 5.1.3 Символьные типы Символьный (character) тип в языке С относится к целым типам, т.е. значе- ния символьного типа являются целыми числами, которые могут быть исполь- зованы в целочисленных выражениях: спецификатор-символьного-типа: char signed char unsigned char Существует три разновидности символьного типа: знаковый, беззнаковый и простой. Все они имеют один размер, но могут отображать разные значения. Использование знакового и беззнакового представления символьного типа аналогично таковым для знакового и беззнакового типов целых чисел. Про- стой символьный тип соответствует отсутствию в спецификаторе типа ключе- вых слов signed и unsigned. Ключевое слово signed является нововведением Standard С, поэтому реализации, не распознающие это ключевое слово, разли- чают только две разновидности символьного типа — беззнаковый и простой. В нотации С массив символов называется «строкой» (string). Пример Ниже приведены некоторые типичные объявления с использованием символов. static char greeting[7]; /* 7-символьная строка */ char «prompt; /* указатель на символ */ char padding_character = ' \0 ' ; /* один символ */ Представление символьных типов зависит от возможностей обработки сим- волов и строк целевого компьютера. Символьный тип имеет некоторые особен- ности, которые ставят его несколько обособленно от обычных знаковых или беззнаковых типов. Например, простой символьный тип char может быть как знаковым, так и беззнаковым или их смесью. Исходя из соображений эффек- тивности, компиляторы С могут интерпретировать символьный тип одним из двух способов: 1. Тип char может быть знаковым целым типом, эквивалентным signed char. 2. Тип char может быть беззнаковым целым типом, эквивалентным unsig- ned char. В некоторых реализациях С, вышедших до появления Стандарта, тип char был «псевдо-беззнаковым» целым типом, т.е. он мог содержать только неотри- цательные значения, но при выполнении обычных унарных преобразований интерпретировался так, как если бы он был знаковым. Пример Если нужен беззнаковый символьный тип, то используется unsigned char. Если ну- жен знаковый символьный тип, то используется signed char. Если тип char исполь- зует 8-битовое представление в дополнительном коде и даны объявления unsigned char uc = -1; signed char sc = -1; char c = -1; int i = uc, j = sc, k = c.
Типы 147 то во всех реализациях Standard С переменной i должно быть присвоено значение 255, a j — значение -1. Однако будет ли к присвоено значение -1 или 255 зависит от реализации. Если реализация не распознает ключевое слово signed или не разреша- ет unsigned char, то вы будете вынуждены использовать двусмысленные простые символы. Наличие знака у символов является их важным свойством, так как функции стандартной библиотеки ввода/вывода, которые обычно возвращают символы из файлов, по достижении конца файла возвращают отрицательное значение. (Отрицательное значение, часто это -1, определено в стандартных заголовочных файлах макросом EOF.) Программисту всегда следует интерпретировать возвра- щаемые этими функциями значения как int, так как тип char может быть без- знаковым. Пример В приведенной ниже программе предполагается выполнение копирования символов из стандартного входного потока в стандартный выходной поток до возвращения функцией getchar индикатора окончания файла. Первые три определения обычно находятся в заголовочном файле stdio.h: extern int getchar(void); extern void putchar(int); ffdefine EOF (-1) /* Любое отрицательное значение */ void copy_characters(void) { char ch; /* Неверно* */ while ( (ch = getchar ()) ’= EOF) putchar(ch); ) Однако эта функция не работает, когда тип char является беззнаковым. Чтобы убе- диться в этом, представим себе, что используется арифметика двоичных дополни- тельных кодов и тип char представляется 8 битами, а тип int — 16. Тогда, если функция getchar возвращает -1, присвоение ch = getchar() присваивает переменной ch значение 255 (младших восемь бит числа -1). Следовательно в цикле выполняет- ся сравнение 255 != -1. Если тип char беззнаковый, то согласно обычным правилам преобразования величина -1 будет преобразована к беззнаковому целому, что в ре- зультате приведет к априори истинному выражению 255 != 65535. Следовательно цикл будет бесконечным. Правильным решением в данном случае будет замена объ- явления переменной ch с "char ch;" на "int ch;". Пример В таких случаях, для улучшения читабельности программы, можно определить «псевдо-символьный» тип. Например, для функции copy_characters «псевдо-сим- вольный» тип будет представлять собой тип int: typedef int character; void copy_characters (void) { character ch; while ( (ch = getchar(} ) • = EOF) putchar (ch); )
148 Глава 5 С символами в С связана также неопределенность, обусловленная их разме- ром. В предыдущем примере мы предполагали, что размер символа равен 8 би- там и это предположение верно практически всегда, хотя остается неопреде- ленность с диапазоном значений символов — от 0 до 255 или от -127 (или -128) до 127. Однако некоторые компьютеры для символов могут использо- вать 9 или даже 32 бита. Поэтому программистам следует быть осторожными. Standard С требует, чтобы реализации документировали диапазоны их сим- вольных типов в заголовочном файле limits.h. Ссылки: битовые поля 5,6.5; символьные константы 2.7.3; наборы символов 2.1; макрос EOF 15.1; функция getchar 15.6; целые типы 5.1; преобразования целых типов 6.2 3; limits.h 5.1.1 5.1.4 Расширенные целые типы Стандарт С99 разрешает, чтобы реализации в дополнение к «стандартным» целым типам имели дополнительные «расширенные» знаковые целые типы (extended integer type). Каждый расширенный знаковый тип должен иметь со- ответствующий беззнаковый тип. Выбранные для этих типов ключевые слова должны начинаться с двух подчеркиваний или с подчеркивания и буквы в верхнем регистре. (Такие идентификаторы зарезервированы в Standard С для соответствующих целей.) Эти расширенные типы считаются целыми типа- ми и все операторы, которые применимы к стандартным целым типам, также применимы и к расширенным целым типам. Доступ к расширенным целым типам осуществляется через заголовочные файлы stdint.h и inttypes.h, рас- смотренные в главе 21. К расширенным типам применяются стандартные правила преобразования целых чисел. Эти правила определяются в главе 6 в рамках рассмотрения ран- га преобразования. Ссылки: ранг преобразования 6 3.3; знаковые целые типы 5 11. 5.1.5 Булев тип В Стандарте С99 введен беззнаковый целый тип _Воо1, который может хра- нить только значения 0 и 1 («ложь» и «истина», соответственно). В контексте булевых операций (например, для проверки в условном выражении) могут ис- пользоваться и другие целочисленные типы, но если реализация отвечает тре- бованиям Стандарта, то лучше использовать тип _Воо1. При преобразовании любого скалярного типа к типу _Воо1 все ненулевые значения преобразуются к 1, а нулевые — к 0. Заголовочный файл stdbool.h определяет макрос bool как синоним для типа _Воо1, а также false и true как 0 и 1 соответственно. Идентификатор bool не является ключевым словом. Это сделано для того, чтобы защитить старые про- граммы, которые могут содержать определенный пользователем тип bool. Преобразования, касающиеся типа _Воо1, описаны вместе с другими правила- ми целочисленных преобразований и продвижений. Ссылки: целочисленные преобразования 6 2.3; целочисленные продвижения 6 3 3, stdbool.h 1 I 3
Типы 149 5.2 Типы с плавающей точкой Традиционно, типы С с плавающей точкой были представлены двумя раз- мерами: единичной и двойной точностью или float и double; Standard С доба- вил тип long double, а Стандарт С99 добавил три комплексных типа с плаваю- щей точкой (раздел 5.2.1). Типы с плавающей точкой, которые не являются комплексными еще называют вещественными. спецификатор-типа-с-плавающей-точкой: float double long double (C89) спецификатор-комплексного-типа ( C99 ) Спецификатор типа long float был разрешен в более ранних реализациях как синоним для типа double, но никогда не пользовался популярностью и в Stan- dard С был убран. Пример Ниже приведены некоторые типичные объявления объектов типов с плавающей точкой: double d; static double pi; float coefficients[10]; long double epsilon; Использование типов float, double и long double аналогично использова- нию целочисленных типов short, int и long. Ранее, до появления Стандарта, реализациям предписывалось преобразовывать все значения типа float в тип double до выполнения каких-либо операций над ними (см. раздел 6.3.4). По- этому использование типа float не было более эффективным, чем типа double. В Standard С определены операции и над типом float. Теперь для него имеется полный набор библиотечных функций. Язык С не определяет размеров, которые должны использоваться для типов с плавающей точкой и даже не говорит о том, что они должны быть различны. В общем случае, программист может предполагать, что значения, представи- мые типом float являются подмножеством значений, представимых типом double, которые в свою очередь являются подмножеством таковых типа long double. Некоторые С-программы исходят из того, что тип double позволяет представить все значения типа long, т.е. конвертирование объекта типа long в объект типа double, а затем обратно в тип long даст исходное значение long. Зачастую это соответствует истине, но полагаться на этот факт не стоит. Standard С требует, чтобы характеристики вещественных типов чисел с плавающей точкой были записаны в заголовочном файле float.h. В табл. 5.3 перечислены идентификаторы, которые должны быть определены. Идентифи- каторы, имена которых начинаются с FLT, относятся к типу float, с DBL — к типу double, и с LDBL — к типу long double. В таблице также приведены ми- нимальные требования для каждой характеристики типов, т.е. требования к диапазону и точности типов чисел с плавающей точкой. К операндам с плавающей точкой применимо большинство арифметиче- ских и логических операций. А именно: арифметическое и логическое отрица-
150 Глава 5 ние; суммирование, вычитание, умножение и деление; проверки на отношение и равенство; логические И и ИЛИ; присвоение; преобразование в и из любого арифметического типа. Вещественное знаковое число с плавающей точкой х в представлении без «скрытых» битов может быть записано в следующей форме ₽ х = s х Ь' х J f. х Ь~к, е . < е < 'к ’ nun щах *=1 где s — знак (±1) b — базис или основание системы счисления представления (обычно 2, 8, 10 или 16) е — значение экспоненты между некоторыми ет,п и етах р — число цифр базиса b в мантиссе fk — цифры мантиссы, 0 < fk < b Если число с плавающей точкой х нормализовано и не равно нулю, то f i > 0. Субнормальным число называется тогда, когда оно не равно нулю, е = e,nin и f! = 0. Ненормализованным число называется тогда, когда е > emill и /у = 0. (Субнормальное число слишком маленькое для нормализации; ненормализо- ванное число может быть нормализовано, но по какой-либо причине нормали- зация выполнена не была.) Типы чисел с плавающей точкой могут допускать специальные значения, которые не являются числами: бесконечность (infinity) и не-число (NaN — Not-a-Number). Тихий NaN (quiet NaN) предполагает выполнение арифмети- ческих выражений без генерации исключения. Результатом выражения, со- держащего NaN, является NaN. Если в выражении возникает сигнализирую- щий NaN (signaling NaN), это вызывает исключение. Бесконечность и NaN мо- гут быть знаковыми. Кроме того, могут существовать различные разновидности NaN. Стандарт С99 расширил стандартную библиотеку для вво- да и вывода этих значений, а также обеспечил некоторые функции для их фор- мирования и тестирования (раздел 17.13 и 17.14). Таблица 5.3. Значения, определенные в заголовочном файле float.h Имя Наименьшее значение Смысл FLT_RADIX' 2 основание системы счисления, b FLTROUNDS отсутствует режим округления' -1 неопределенный; 0: к 0, 1' к ближайшему, 2 к +бесконечности; 3: к -бесконечности2 FLT_EVAL_METHOD3 отсутствует -1, неопределенный; 0 именно к диапазону и точности типа; 1 float и double используют double, для long double используется long double; 2 для всех вычислений используется long double FIT EPSILON 1 Cr‘J минимальное значение х > 0.0, такое, что 1 0 + х > 1.0, DBL EPSILON 10"9 б1"°, приведенные значения являются максимальными, LDBL_EPS1LON IO"9 которые разрешены FLT DIG 6 количество десятичных цифр точности DBL DIG 10 LDBL.DIG 10
Типы 151 Имя Наименьшее Смысл значение FLT MANT DIG отсутствует p, количество цифр мантиссы с основанием b DBL MANT DIG LDBL_MANT_D1G DECIMALDIG3 10 количество десятичных знаков, необходимых для представления значений наибольшего поддерживаемого типа с плавающей точкой; эквивалентно 1 + рмокс:log 106, если 6 не является степенью 10. FLT MIN KT3? минимальное положительное нормализованное число DBL MIN IO"37 LDBL_MIN IO-37 FLT_MIN_EXP о тсутствует еМИН, минимальное отрицательное целое х, такое что 6х”' DBL MIN EXP попадает в диапазон нормированных чисел с плавающей LDBL_MIN_EXP ТОЧКОЙ FLT MIN 10 EXP -37 минимальное числа х, токае, что 10х попадает DBL MIN 10 EXP -37 в диапазон нормализованных чисел с плавающей точкой LDBL_MIN_10_EXP -37 FLT MAX 10+37 максимальное представимое конечное число DBL MAX 10+37 LDBL_MAX 10+37 FLT_MAX_EXP отсутствует емокс, максимальное целое х такое, что б*”' является DBL MAX EXP представимым конечным числом с плавающей точкой LDBL_MAX_EXP FLT MAX 10 EXP 37 максимальное число х такое, что 10х находится в диапазоне DBL MAX 10 EXP 37 представимых конечных чисел с плавающей точкой LDBL MAX10EXP 37 1 FLTRADIX и FLTROUNDS относятся ко всем трем типам с плавающей точкой. 2 Другие значения определяются реализацией. 3 Нововведение Стандарта С99. Пример Общее представление чисел с плавающей точкой, используемое большинством мик- ропроцессоров определено стандартом «Стандарт для двоичной арифметики с пла- вающей точкой» (ISO/IEEE Std 754-1985). Согласно этому стандарту, модели (при- мененные к соглашениям кодирования Standard С) для 32-битных чисел с плаваю- щей точкой одинарной точности и 64-битных чисел с плавающей точкой двойной точности имеют вид 24 хПт, = 8*2' х 4 х 2'*, -125 < е < +128 *=i 53 =sx2'x^fkx 2~k, -1021 < е < +1024 *=i Значения из заголовочного файла float.h, соответствующие этим типам, приведены в таблице 5.4. Константы чисел с плавающей точкой типа float для указания типа используют в Standard С суффикс F. Поддержка IEEE в Standard С опциональна. Ссылки: константы с плавающей точкой 2.7.2; преобразования с плавающей точкой 6.2.4; представления чисел с плавающей точкой 6 1.1; функции, связанные с NaN 17.14, 17.15.
152 Глава 5 Таблица 5.4. Характеристики чисел с плавающей точкой Стандарта IEEE Имя Зночение для FLT_/<w? Значение для DBL_n/u« RADIX 2 не применимо ROUNDS определяется реализацией не применимо EPSILON 1.19209290E-07F 2.2204460492503131Е-16 или OX1P-23F (С99) или 0X1 Р-52 (С99) DIG 6 15 MANT.DIG 24 53 DECIMALDIG 17 17 MIN 1.17549435E-38F 2.2250738585072014Е-308 или OX1P-126F(C99) или ОХ1Р-Ю22 (С99) MIN_EXP -125 -1021 MINJOJEXP -37 -307 MAX 3.40282347E+38F или 1.7976931348623157Е+308 0X1 .fffffeP127F (С99) или OXI .fffffffffffffp 1023 (С99) MAX_EXP 128 1024 MAX_lO_EXP 38 308 1 Этот идентификатор не содержит префикса (ни FLT_ ни DBL ) 5.2.1 -омплексные типы с плавающей точкой Стандарт С99 добавил в язык шесть комплексных типов: float -Complex, double -Complex, long double Complex, float -Imaginary, double Imaginary и long double -Imaginary. Комплексные типы (complex) относятся к арифме- тическим типам с плавающей точкой. Арифметические типы, которые не от- носятся к комплексным, называются вещественными типами (real). Авто- номные реализации не обязаны поддерживать комплексные типы, а чисто мнимые типы -Imaginary опциональны даже для базовых реализаций. спецификатор-комплексного-типа: ( С99 ) float _Complex double _Complex long double —Complex Ключевое слово —Complex было выбрано для того, чтобы не возникали кон- фликты с определенными пользователем в уже существующих программах ти- пами complex. Спецификаторы типа, которые стоят перед ключевым словом -Complex, обозначают соответствующий вещественный тип. Заголовочный файл complex.h определяет макрос complex, как синоним для типа —Complex. Поэтому программисты, у которых не возникают проблемы использования су- ществующего исходного кода, могут применять более простое имя. Каждый комплексный тип представляется двухэлементным массивом, со- стоящим из элементов соответствующего вещественного типа и соответствую- щим этому вещественному типу правилам выравнивания в массиве. Первый элемент представляет вещественную часть комплексного числа, а второй - мнимую. Реализации, отвечающие Стандарту С99, могут опционально поддерживать чисто мнимые типы float Imaginary, double -Imaginary и long double
Типы 153 „Imaginary. Хотя эти типы также считаются комплексными, они представля- ются одним элементом соответствующего вещественного типа. Они удобны для некоторых видов комплексных вычислений, но недостаточно удобны для того, чтобы быть официальной частью Стандарта. Комплексное (или мнимое) значение, которое содержит как минимум одну бесконечную часть, считается бесконечным, даже если другая его компонента является NaN. Чтобы комплексное число было конечным, должны быть ко- нечными обе части (ни одна из частей не может быть ни бесконечной, ни NaN). Комплексное или мнимое число считается нулевым, если все его части равны нулю. Ссылки: преобразования комплексных чисел 6.2.4; заголовочный файл complex.h гл. 23; обычные двоичные преобразования 6.3.4. 5.3 Указательные типы Для любого типа Т может быть создан тип «указатель на Т». На типы указа- телей ссылаются как на указатели на объекты (object pointers) или указатели на функции (function pointer) в зависимости от того, является Т типом объекта или функции. Значением указательного типа является адрес объекта или функ- ции типа Т. Объявление указательных типов обсуждается в разделе 4.5.2. Пример int *ip; /* ip: указатель на объект типа int */ char *cp; /* cp: указатель на объект типа char */ int {*fp} {) ; /* fp: /* указатель на функцию, возвращающую */ значение целого типа */ Двумя наиболее важными операциями для работы с указателями являются операция взятия адреса &, результатом которой является значение адреса, и операция разыменования (опосредования) *, которая разыменовывает указа- тели для доступа к объекту, на который ссылается указатель. Пример В следующем примере указателю ip присваивается адрес переменной i (&i). После этого присваивания выражение *ip ссылается на тот же объект, что и имя i: int i, j, *ip; ip = Si; i = 22; j = *ip; /* значение j теперь равно 22 */ *ip = 17; /* значение i теперь равно 17 */ В число операций с указательными типами входят также присваивание, вычитание, сравнение, проверка на равенство, логические И и ИЛИ, сложение и вычитание целых чисел, а также преобразования в целочисленные и указа- тельные типы и обратно. Размер указателя зависит от реализации и в некоторых случаях — от типа объекта, на который указатель ссылается. Например, указатели на данные мо- гут быть длиннее или короче указателей на функции (раздел 6.1.5). Наличие некоторого соотношения между размерами указателей и размером какого-ли-
154 Глава 5 бо из целых типов необязательно, несмотря на то, что тип long предполагается имеющим длину как минимум такую же, как и любой из указательных типов. В Стандарте С99 используйте тип intptr_t. В Standard С указательные типы могут быть уточнены с помощью использо- вания квалификаторов типа const, volatile и restrict (С99). Уточнение указа- тельного типа (если оно присутствует) может повлиять на операции и преобра- зования, допустимые с его участием, а также на разрешенные для него разно- видности оптимизации. Ссылки: операция взятия адреса & 7.5.6; массивы и указатели 5.4.1; операции присваивания 7.9; выражения приведения типов 7.5.1; преобразования указателей 6.2.7; оператор if 8.5; операция разыменования * 7.5.7; тип intptr_t 21.5; объявление указателей 4.5.2, квалифика- торы типо 4.4.3. 5.3.1 Указатели общего назначения Необходимость в обобщенном указателе на данные, который может быть преобразован в любой тип указателя на объект, иногда возникает в задачах низкоуровневого программирования. В традиционном С для этих целей при- нято использовать тип char *, применяя преобразование этих обобщенных указателей к нужному типу перед их разыменованием. Более детально это описано в разделе 6.2, посвященном преобразованию указателей. Проблема с использованием типа char * для этих целей состоит в том, что компилятор не имеет возможности выполнить проверку корректности осуществляемых про- граммистом преобразований над указателями. Standard С ввел в качестве «указателя общего назначения (generic pointer)» тип void *. Для совместимости со старыми реализациями его представление совпадает с представлением типа char *, но язык ведет себя с ним по-другому. Указатели общего назначения не могут быть разыменованы с помощью опера- тора * или оператора индексации; они также не могут служить в качестве опе- рандов операций сложения и вычитания. Любой указатель на объект или не- полный тип (но не на функциональный тип) может быть преобразован к типу void * и обратно без изменений. Считается, что тип void * не является ни ука- зателем на объект, ни указателем на функцию. Пример Ниже приведены примеры объявлений и преобразований указателей: void *generic_ptr; int *int__ptr; char *char_ptr; generic_ptr = int_ptr; /* Верно */ int_ptr = generic_ptr; /* Верно */ int_ptr = char_ptr; /* Неверно int__ptr = (int *) char_ptr; /* Верно */ /* Верно */ /* Верно */ /* Неверно в Standard С */ Указатели общего назначения обеспечивают дополнительную гибкость в использовании прототипов функций. Если функция имеет формальный па- раметр, который может принимать указатель на данные произвольного типа, этот параметр следует объявлять с типом void *. Если же данный формальный параметр объявлен как имеющий какой-либо другой указательный тип, то фактический аргумент должен иметь тот же тип, т.к. различные указательные типы в Standard С не являются совместимыми для присваивания.
Типы 155 Пример Функция strcpy копирует символьные строки и поэтому требует аргумент типа char *: char *strcpy(char *sl, const char *s2) ; Функция memcpy может принимать указатель на произвольный тип и поэтому ис- пользует void *: void *memcpy(void *sl, const void *s2, size_t n); Ссылки: совместимость для присваивания 6.3.2; спецификатор типа const 4.4; функция memcpy 14.3; функция strcpy 13.3. 5.3.2 Нулевые и недействительные указатели Для каждого указательного типа в языке С предусмотрено специальное зна- чение, называющееся нулевым указателем (null pointer), которое отлично от любого реального значения указателя этого типа. Оно равно константе нулево- го указателя и преобразуется к нулевым указателям других указательных ти- пов. В булевом контексте нулевой указатель имеет значение «false». Констан- та нулевого указателя (null pointer constant) в языке С представляет собой любое целочисленное константное выражение со значением 0 или такое значе- ние, преобразованное к типу void *. Макрос NULL традиционно определяется как константа нулевого указателя в стандартных заголовочных файлах: stddef.h в Standard С, или stdio.h в более ранних реализациях. Обычно в представлении любого нулевого указателя все биты равны нулю, но это не является обязательным. По сути, различные указательные типы мо- гут иметь разные представления для своих нулевых указателей. Если нулевые указатели не представлены числовым нулем, то реализация должна обеспечи- вать правильное взаимное преобразование нулевых указателей и констант ну- левых указателей между различными указательными типами. Пример Запись if (ip) i = *ip; является общепринятым сокращением для следующей: if (ip != NULL) i = *ip; Программы, относящиеся к хорошему программистскому стилю, отлича- ются тем, что все указатели, не ссылающиеся на действительные объекты или функции, имеют в них значение NULL. В результате неосторожности возможно также создание недействительных указателей (invalid pointer), т.е. указателей, не являющихся нулевыми и не ссылающихся на реальный объект или функцию. Недействительный указа- тель чаще всего создается при объявлении переменной указательного типа без последующей инициализации ее действительным значением или константой NULL. Любое использование недействительного указателя, включая сравне- ние его со значением NULL, передачу его в качестве аргумента функции или присваивание его значения другому указателю в Standard С не определено. Не- действительные указатели также могут быть порождены в результате преобра-
156 Глава 5 зования произвольных целочисленных значений к указательным типам, в ходе освобождения памяти, занимаемой объектом, на который ссылается указатель (как, например, при использовании функции free), или в результате использования адресной арифметики для создания указателя, ссылающегося за границы массива. Попытка разыменования недействительного указателя может вызвать ошибку времени выполнения. В соответствии с арифметикой указателей, язык С, кроме всего прочего, требует, чтобы был определен адрес объекта, следующего за последним объек- том массива, хотя такой адрес может не допускать его разыменование. Это тре- бование упрощает использование указательных выражений при работе с мас- сивами. Пример Следующий цикл использует адрес сразу за концом массива. При этом он нигде не пытается его разыменовать: int array[N], /* адрес последнего объекта .* есть fiarray[N-l] */ int *р; for (р = &array[0]; р < fiarray[N], р++) Это требование может представлять дополнительные ограничения для реа- лизаций на некоторых целевых компьютерах, имеющих архитектуры с преры- вистым адресным пространством, уменьшая тем самым на единицу макси- мальный размер массива. На таких компьютерах могут быть неосуществимы арифметические операции над указателями, которые не ссылаются на значе- ния внутри одной непрерывной области памяти, и только явное размещение массива может гарантировать программисту, что память непрерывна. Ссылки: функция free 16.1; целочисленные констонты 2.7.1, арифметика указателей 7.6 2; заголовочный файл stddef.h 11.1, тип void * 5.3 1. 5.3.3 Некоторые предосторожности при работе с указателями Многие программисты на С полагают, что все указательные типы (а на самом деле — все адреса) имеют одинаковое представление. На обыкновенных компь- ютерах с байтовой адресацией все указатели обычно являются просто адресами байтов, занимающими, скажем, одно слово. Преобразования между указатель- ными и целочисленными типами на таких компьютерах не требуют изменения в представлении, и, таким образом, не вызывают потерь информации. На самом деле, язык С не требует такого хорошего поведения. В разделе 6.1 эта проблема обсуждается более детально, но вот ее краткое резюме: 1. Размер указателей часто не совпадает с размером типа int и иногда не совпадает с размером типа long. Иногда их размер является опцией компилятора. В Стандарте С99 тип intptr_t является целочисленным типом, достаточно большим для хранения указателя на объект. 2. Указатели на значения символьного типа и указатели типа void * могут быть больше, чем другие указатели и могут использовать представле- ние, отличное от представления других указателей. Например, они мо-
Типы 157 гут использовать старшие биты, являющимися обычно нулевыми в дру- гих указательных типах. 3. Указатели на функции и на данные могут иметь существенно различ- ные представления, включая разные размеры. Программист всегда должен использовать явные преобразования типов при преобразовании из одного указательного типа в другой, и должен особенно следить за тем, чтобы указатели, передаваемые в качестве аргументов функци- ям, имели корректный тип, ожидаемый функцией. В Standard С в качестве указателя общего назначения на объект можно использовать void *, но анало- гичного указателя общего назначения на функцию не существует. Ссылки: приведение типов 7.5.1; тип intptr_t 21.5; функция malloc 16.1; преобразования указатепей 6.2.7. 5.4 Массивы В языке С можно объявить массив элементов любого типа Т, за исключени- ем недоопределенного типа и типа void. Значениями в этом случае будет вы- ступать последовательность элементов типа Т. Все массивы индексируются с нуля. В Разделе 4.5.3 подробно описаны синтаксис и значение описателей массивов, включая недоопределенные массивы и массивы переменной длины. Пример Массив, объявленный как int А[3]; состоит из элементов А[0], А[1] и А[2]. В приве- денном ниже фрагменте программы объявляются массив целых чисел (ints) и мас- сив указателей (ptrs). Затем каждому указателю из ptrs ставится в соответствие ад- рес соответствующего элемента из массива ints. int ints [10], *ptrs[10], i; for (i = 0; i < 10; i++) ptrs [i] = Sints [i]; Размер массива в памяти (в смысле результата операции sizeof) всегда равен коли- честву элементов умноженному на размер каждого элемента. Ссылки: описатели массива 4.5.3; операция sizeof 7.5.2; единицы памяти 6.1.1; структурные типы 5.6; массивы переменной длины 5.4.5. 5.4.1 Массивы и указатели В языке С существует тесная взаимосвязь между типами «массив типа Т» и «указатель на Г». Во-первых, когда в выражении встречается идентифика- тор массива, то тип идентификатора преобразуется из «массив типа То в «ука- затель на То, значение же идентификатора конвертируется в указатель на пер- вый элемент массива. Это одно из правил обычных унарных преобразований. Единственное исключения из этого правила — это использование идентифика- тора массива в качестве операнда для операции sizeof или операции взятия ад- реса &. В этом случае операция sizeof возвратит размер массива, а операция & — указатель на массив (а не указатель на указатель на первый элемент).
158 Глава 5 Пример Во второй строке приведенного ниже фрагмента значение а преобразуется в указа- тель на первый элемент массива: int а[10], *ip; ip = а ; Это равносильно следующему: ip = Sa [0] ; Результат sizeof(a) будет равен sizeof(int) * 10, но не sizeof(int *) Во-вторых, индексирование массива определено в терминах арифметики ука- зателей. То есть выражение a[i] по определению будет эквивалентно * ((а) + (i)), где а конвертируется в &а[0] согласно обычным унарным преобразованиям. Это определение индексирования также означает, что a[i] будет идентично i[a] и что любой указатель может быть использован для индексирования так же, как и массив. Но в таком случае, программист должен сам удостовериться, что указатель указывает на подходящий элемент массива. Пример Если d имеет тип double и dp — указатель на объект типа double, то выражение d = dp [4] ; определено только тогда, когда dp указывает в данный момент на элемент массива типа double и есть как минимум 4 элемента массива за первым (на который указы- вает указатель dp) Ссылки: операция взятия адреса & 7.5.6; операция сложения + 7.6.2, описатели массивов 4.5.3; преобразования массивов 6.3.3; операция разыменования * 7.5.7, указательные типы 5.3; операция sizeof 5.4.4, 7.5 2; индексация 7.4 1; обычные унорные преобразования 6.3 3. 5.4.2 Многомерные массивы Многомерные массивы объявляются как «массивы массивов», например, строка int matrix(12](10]; объявит имя matrix как массив, состоящий из 12x10 элементов типа int. Язык не накладывает ограничений на количество размерностей массива. Массив matrix может быть также объявлен в два шага для более понятного описания: typedef int vector[10]; vector matrix[12J; Таким образом идентификатор matrix станет 12-элементным массивом, со- стоящим из 10-элементных массивов типа int. Тип переменной matrix — int [12][10]. Элементы многомерного массива содержатся в памяти в виде одного большого одномерного массива. То есть элементы, чьи номера отличаются только последним индексом, будут размещаться в памяти рядом. Преобразование из массива в указатель для многомерного массива будет та- ким же, как и в случае с одномерным массивом.
Типы 159 Пример Элементы массива int t[2][3] будут размещаться в памяти (в порядке возрастания адреса) так: t[0] [0], t[0] [1], t[0] [2], t[l] [0], t[i] [1], t[i] [2] Выражение t[l][2] будет преобразовано в *(*(t+l)+2), что эквивалентно выполне- нию последовательности шагов: 1- Выражение t — массив 2x3, преобразуется в указатель на первый 3-элемент- ный подмассив. 2. Выражение t+1 становится указателем на второй 3-элементный подмассив. 3. Выражение *(t+l), второй 3-элементный подмассив целых чисел, преобразует- ся в указатель на первый элемент этого подмассива. 4. Теперь выражение *(t+l)+2 — указатель на третий элемент второго 3-элемент- ного подмассива. 5. И наконец *(*(t+l)+2) — третий элемент во втором 3-элементном подмассиве, то есть — t[l][2]. В общем случае, любое выражение А типа «ixjx...x/г массива типа Т» немед- ленно преобразуется в «указатель на ;x...x& массива типа Г». Ссылки: операция сложения + 7.6.2; описатели массива 4.5.3; операция разыменования * 7.5.7; указательные типы 5.3; индексирование 7.4.1. 5.4.3 Границы массива При выделении памяти для хранения массива должен быть известен его размер. Однако так как обычно проверка на попадание индекса внутрь границ массива не выполняется, то возможно опускание размеров (т.е. использовать недоопределенный тип массива) при объявлении внешнего одномерного масси- ва, объявленного в другом модуле, или одномерного массива, объявленного в качестве формального параметра функции (см. также раздел 4.5.3). Пример Приведенная ниже функция sum возвращает сумму первых п элементов внешнего массива а с неопределенными границами: extern Int а[]; int sum(int n) { int i, s = 0; for (i = 0; i < n; i++) s += a [i] ; return s; 1 Массив также может быть передан как параметр: int sumfint а[], int n) { int i, s = 0; for (i = 0; i < n; i++) s += a[i] ; return a; 1
160 Глава 5 Параметр а может быть объявлен как int *а без изменения тела функции. В этом случае исходный текст будет лучше отражать реализацию, но хуже суть. При использовании многомерных массивов необходимо указывать границы всех размерностей кроме первой. Это необходимо для правильной работы ад- ресной арифметики: extern int matrix(] (10]; /* ? х 10 массив типа int */ Если данное правило не было выполнено, то описание массива будет считаться ошибочным. В Стандарте С99, как одномерные, так и многомерные массивы могут иметь переменную длину. Ссылки: описатепи массивов 4 5.3; определяющие и ссылочные объявления 4,8; оператор разыменования * 7.5.7; опущенные границы массива 4 5; указательные типы 5.3; индексирова- ние 7.4.1; массивы переменной длинны 5.4.5. 5.4.4 Операции Единственные операции, которые можно применять непосредственно к мас- сиву — это операция sizeof и операция взятия адреса &. При использовании операции sizeof у массива должны быть явно определены границы. В этом слу- чае результатом выполнения sizeof будет количество блоков памяти занимае- мой массивом. Для n-элементного массива типа Т, результат выполнения sizeof всегда будет равен п раз (sizeof(T)). Результатом операции & будет ука- затель на массив (на первый элемент). В других случаях, например, при индексировании массива, имя массива будет трактоваться как указатель. Поэтому операции с указателями могут быть применимы и к имени массива. Ссылки: описатели массива 4.5.3, преобразование массива в указатель 6.2.7; указательные типы 5.3; операция sizeof 7.5.2; индексирование 7 4.1. 5.4.5 Массивы переменной длины Стандарт С99 дает программисту возможность использовать массивы пере- менной длины — массивы, размер которых не известен до выполнения про- граммы. Описание массива переменной длины похоже на описание массива фиксированной длины за исключением того, что размер массива определяется неконстантным выражением. При объявлении такого массива вычисляется его размер и создается массив указанного размера (размер должен быть поло- жительным целым). После создания размер массива переменной длины ме- няться уже не может. Доступ к элементам массива может осуществляться только в рамках выделенной длины. Попытка обращения к элементам вне гра- ниц определения массива приведет к непредсказуемым последствиям, так как при доступе к элементам массива проверка на корректность индекса не выпол- няется. Массив уничтожается тогда, когда выполнился блок, в котором этот массив был определен. Каждый раз при входе в блок выделяется память под новый массив. Если не говорить о передаче массива в качестве параметра функции, то мас- сив переменной длины должен быть объявлен в блочной области видимости и не должен объявляться с классом памяти static или extern. Массивами пере-
Типы 161 менной длины могут быть объявлены только простые идентификаторы (не структуры и не члены объединения). Область видимости переменной типа мас- сив располагается от точки объявления массива и до конца самого вложенного блока, заключающего эту переменную. Время жизни массива длится от мо- мента объявления до точки выхода выполнения программы из области види- мости массива. В Стандарте С99 этот интервал включает завершение блока, переход из блока или переход в место, где блок еще не был объявлен. Создате- ли реализаций С могут использовать для хранения массивов переменой длины стек выполнения после обработки объявления. Гибкие типы (variably modified type) включают в себя массивы переменной длины и другие типы, содержащие массивы переменной длины как их часть, например — указатели на массивы переменой длины. Как гибкие типы могут быть объявлены только простые идентификаторы в блочной области видимо- сти без связей. Это правило оставляет некоторую лазейку — можно использо- вать гибкий тип (это не относится к массивам переменной длины) как тип ста- тического идентификатора с блочной областью видимости. В этом случае, хотя значение статического идентификатора и сохраняется для всего времени вы- полнения блока, но дает возможность менять размерности массива перемен- ной длины для каждого нового выполнения блока. Пример В следующем примере а и Ь — массивы переменной длины, а указатель с — гибкий тип. int a_size; void f(int b size) { int c_size = b_size + a_size; int a[a_size++]; int b[b_size] [b_size] ; static int (*c) [5] [c_size] ; } Ограничения, накладываемые на массивы переменной длины, упрощают реализацию Стандарта С99, оставляя при этом доступной большую часть их возможностей. Без таких ограничений появилось бы множество сложностей. Структуры должны были бы поддерживать дескрипторы скрытого типа для компонентов гибкого типа. Объявление массива переменной длины в области видимости файла может потребовать от языка С дополнительных накладных расходов на «тщательно продуманное» объявление верхнего уровня во время выполнения программы. (C++ и другие языки имеют подобные механизмы, но они не соответствуют духу С.) При использовании массива переменной длины в объявлении typedef, вы- ражение для длины вычисляется единственный раз при встрече такого объяв- ления, а не каждый раз при использовании имени нового типа. Пример /* Допустим, что п = 5 */ typedef int[n] vector; n += 1;
162 Глава 5 vector а, int b[n]; Переменная a — 5-элементный массив целых, с учетом того, что значение п на мо- мент объявления typedef равно 5. В противоположность этому b — 6-элементный массив целых, потому как значение п изменилось (увеличилось) на момент объявле- ния Ь на единицу. Массивы переменной длины в качестве параметров. Массив переменной длины или гибкий тип могут использоваться как параметры функции. Если длина массива также является параметром, то она должна быть передана пер- вой согласно лексическим правилам С блочной области видимости. При вызове функции с массивом переменной длины в качестве параметра, размерности массива должны соответствовать объявленным в описании функ- ции параметрам, иначе результат будет неопределен. Пример Первое определение функции корректно. Второе может быть либо недопустимым, либо для вычисления размера а будут использоваться значения некоторых других переменных г и с. void f( int г, int с, int a(c][r] } {...} /* Корректно */ void f( int a[c][r], int r, int c ) (...) /* Неверно: г, с невидимы для а[с][г] */ В объявлении прототипа функции (не в определении функции) размерность массива переменной длины может быть обозначена при помощи [*]. Любое не- константное выражение, появляющееся в квадратных скобках имени массива в объявлении прототипа функции, считается эквивалентным [*]. Пример Все приведенные ниже прототипы совместимы. Хотя третий прототип подразумева- ет квадратный массив, это ограничение не проверяется во время компиляции. По- следний прототип показывает, что размер внутренней (или единственной) размерно- сти может быть опущен. void f(int, int [*][*]); void f(int n, int [*][m]), void f(int n, int [n] [n] ) , void f(int, int [][*]) , Ссылки: описатели массивов 4.5.3, прототипы функций 9.2, операция sizeof 7.5.2 5.5 Типы перечисления Синтаксис объявления типов перечисления приведен ниже: спецификатор типа-перечисления : определение-типа перечисления ссылочный-тип-перечис ления определение-типа-перечисления :
Типы 163 enum тег-перечисленияопц { список-определений-перечисления} enum тег-перечисленияогщ { список-определений-перечисления , } (С99) ссылочный-тип-перечисления : enum тег-перечисления тег-перечисления : идентификатор список-определений-перечисления : определение-константы-перечисления список-определений-перечисления , определение-константы-перечисления определение-константы-перечисления : константа-перечисления константа-перечисления = выражение константа-перечисления : идентификатор Тип перечисление в языке С является множеством целочисленных значе- ний, представленных идентификаторами, называемыми константами пере- числения. Константы перечисления определяются во время определения типа перечисления и имеют тип int. Каждый тип перечисления представлен в виде определяемого реализацией целого типа и является совместимым с этим ти- пом. Таким образом, с точки зрения проверки типов, тип перечисления явля- ется одним из целых типов. В тех случаях, где язык С допускает в некотором контексте целочисленное выражение, в этом контексте может использоваться константа перечисления или значение типа перечисления. (Для C++ это не так; см. раздел 5.13.1.) Для удобства Стандарт С99 позволяет в список-определений-перечисления ставить запятую в конце. Пример Объявление enum fish { trout, carp, halibut } my_fish, your_fish; создаст тип перечисления fish, принимающий значения: trout, carp и halibut. В этой строке также объявляются две переменных типа перечисления: my_fish и your_fish, которым присвоены значения следующими присвоениями my_fiBh = halibut; your_fish = trout; Переменные или другие объекты типа перечисление могут быть объявлены непосредственно в определении перечисляемого типа либо позднее, ссылаясь на тип перечисления, используя ссылку-на-тип-перечисления. Пример Например, объявление епшп color { red, blue, green, mauve } favorite, acceptable, least_favorite;
164 Глава 5 эквивалентно двум объявлениям enum color { red, blue, green, mauve } favorite, enum color acceptable, least favorite; либо четырем объявлениям enum color ( red, blue, green, mauve }, enum color favorite; enum color acceptable; enum color least_favorite; Тег перечисления color, позволяет ссылаться на тип перечисление после его опреде- ления. Хотя альтернативное объявление enum { red, blue, green, mauve } favorite, acceptable, least_favorite; объявит те же переменные и константы перечисления, отсутствие тега перечисле- ния лишает нас возможности объявить переменные того же типа позднее. Теги перечисления находятся в том же классе перегрузки, что и теги струк- тур и объединений, и их область видимости такая же, как и у переменной, объявленной в исходной программе в том же месте. Идентификаторы, определенные как константы перечисления являются членами того же класса перегрузки, что и переменные, функции и имена typedef. Их область видимости такая же, как и у переменной, объявленной в исходной программе в том же месте. Пример В приведенном ниже фрагменте программы, объявление константы перечисления shepherd, скрывает предыдущее объявление переменной shepherd целочисленного типа. Однако объявление переменной collie типа с плавающей точкой вызовет ошибку времени компиляции, вследствие того, что collie уже была объявлена в той же области видимости константой перечисления. int shepherd = 12; { enum dog_breeds {shepherd, collie}; I* скрывает внешнее описание идентификатора "shepherd" ★/ float collie; /* Недопустимое переопределение "collie" */ } Перечислительный тип реализован путем ассоциирования с константами перечисления целочисленных значений, поэтому присвоение и сравнивание значений типа перечисления может быть реализовано как присвоение и срав- нивание целочисленных значений. Целочисленные значения ассоциируются с константами перечисления следующим образом: 1. Константе перечисления можно задать соответствующее целое число явным образом константа перечисления = выражение в определении типа перечисления. Выражение должно быть констант- ным, целочисленным и может включать выражения, содержащее ранее определенные константы перечисления. Например
Типы 165 enum boys ( Bill = 10, John = Bill+2, Fred = John+2 }; 2. Если явного значения определено не было, то по умолчанию, первая константа перечисления получает значение 0. 3. Последующие константы перечисления, при отсутствии явных при- своений значений, получают значения большие на единицу, чем значе- ние предыдущей константы перечисления. Любое знаковое целое, которое может быть представлено типом int, может быть присвоено константе перечисления. Числа, как положительные, так и от- рицательные, могут выбираться случайным образом. Возможно даже ассоции- рование одного и того же числа двум разным константам перечисления. Пример В приведенном ниже объявлении enum sizes { small, medium=10, pretty_big, large=20 }; значения small, medium, pretty_big, и large будут равны 0, 10, 11 и 20, соответст- венно. Хотя следующее определение корректно: enum people { john=l, mary=19, bill=-4, sheila=l }; оно привет к такому неочевидному результату, как истинность выражения john == sheila. Хотя объявления перечислительного типа по форме похоже на структурные типы и типы объединения, которые предполагают строгую проверку типов, в действительности тип перечисления Standard С (определение которого дается в этой книге) является немного большим, чем просто более удобочитаемый ме- тод именования целочисленных констант. С точки зрения стиля, мы надеемся, что программисты рассматривают тип перечисления отличным от целочислен- ного типа и не смешивают их в целочисленных выражения без использования явного приведения типов. В действительности, некоторые компиляторы С в операционной системе UNIX реализуют слабую типизацию перечисления, что приводит к невозможности преобразований между типом перечисления и цело- численным типом без использования явного приведения типов. Ссылки: приведение выражений 7.5.1; идентификаторы 2.5; классы перегрузки 4.2.4; область видимости 4.2.1. 5.6 Структурные типы Структурные типы в С похожи на записи (record), реализованные в других языках программирования. Они представляют собой поименованный набор компонентов (называемых также членами структуры или полями), которые могут относиться к разным типам данных. Структуры могут быть определены для хранения связных объектов данных. спецификатор-структурного-типа : определение-структурного-типа ссылка-на-структурный-тип
166 Глава 5 определение-структурного-типа : struct тег структуры0Гщ { список-полей } ссылка-на-структурный-тип : struct тег-структуры тег-структуры : идентификатор список-полей : объявление-компонента список-полей объявление-компонента объявление-компонента : спецификатор-типа список-описаний-компонентов; список-описаний-компонентов : описание-компонента список-описаний-компонентов , описание-компонента описание-компонента : простой-компонент битовое-поле простой-компонент описание битовое-поле : описаниеопц : ширина ширина : константное-выражение Пример Программист, желавший реализовать комплексные числа (до появления Стандарта С99) мог объявить структуру complex, содержащую действительную и мнимую час- ти — компоненты real и imag. Первое объявление определит новый тип, а второе — две переменных х и у, этого типа. struct complex { double real; double imag; real image double double struct complex x,y, struct complex Для создания новых объектов этого типа напишем функцию new_complex. Обрати- те внимание, что для доступа к компонентам структуры используется оператор вы- бора (.). struct complex new_complex (double r, double i) { struct complex c, c.real = r; c.imag = i; return c;
Типы 167 Можно также определить операции над данным типом. Например, функцию сошр- lex_multiply для перемножения двух комплексных чисел: struct complex complex_multiply(struct complex a, struct complex b ) { struct complex product; product.real = a.real * b.real - a.imag * b.imag; product.imag = a.real * b.imag + a.imag * b.real; return product; } Пример Объявление struct complex ( double real, imag; } x, y; эквивалентно двум объявлениям struct complex ( double real, imag; }; struct complex x, y; 5.6.1 Ссылки на структурные типы Использование спецификатора типа синтаксических классов определение структурного-muna или определение-типа-объединение (раздел 5.7) вводит определение нового типа, отличного от всех остальных. При его наличии в оп- ределении тег структуры ассоциируется с новым типом и может быть исполь- зован в последующих ссылках на структурный тип. Область видимости определения (и тега типа) распространяется от точки определения до конца самого вложенного блока, содержащего этот идентифи- катор. Во включающем блоке новое определение явно переопределяет (скры- вает) любое предыдущее определение. Использование спецификатора типа синтаксических классов ссылка-на- структурный-тип или ссылка-на-тип-объединение (раздел 5.7) без предшест- вующего определения в этом же либо обрамляющем блоке разрешено тогда, когда нет необходимости в информации о размере структуры, включая объяв- ления: 1. ссылок на структуру, 2. имени typedef для описания синонима для структуры. Использование такого вида спецификатора представляет собой «неполное» определение типа и тега типа в наиболее вложенном блоке, в котором этот тип или тег используется. Для того чтобы данное определение было полным, далее в той же области видимости должно появится определение-структурного-ти- па или определение-типа-объединения. Отдельно следует упомянуть случай, когда ссылка-на-структурный-тип или ссылка-на-тип-объединения в объявлении без объявителей скрывает лю- бое определение тега типа в любой объемлющей области видимости, делая дан- ный тип недоопределенным.
168 Глава 5 Пример Рассмотрим следующее корректное определение двух взаимно ссылающихся струк- тур во внутреннем блоке: { struct cell; struct header { struct cell ‘first; ... }; struct cell { struct header ‘head; ... } ; 1 Неполное определение "struct cell в первой строке необходимо для сокрытия лю- бого определения тега cell в обрамляющих блоках. Определение struct header во второй строке автоматически скрывает любое определение в обрамляющих блоках и корректно использует struct cell для определения указателя. Определение struct cell в третьей строке делает определение cell полным. Объявление неполного типа также существует и внутри определения-струк- турного-типа или определения-типа-объединение от первого упоминания но- вого тега до того момента, как определение считается полным. Это позволяет создавать структуры со ссылкой на саму себя (см. рис. 5.1). Ссылка на неполный тип struct list { struct list ‘next; int data; }; t t Здесь тип неполон. Здесь тип полон. Рис 5.1. Неполный структурный тип внутри объявления Ссылки: объявления 4.1; описатели 4 5; ; дублирование области видимости 4.2 2; область ви- димости 4.2.1, операция выбора . 7.4.2; эквивалентность типов 5.11. 5.6.2 Операции над структурами Допустимые над структурами операции могут зависеть от компилятора. Все компиляторы С поддерживают для структур операции . и ->. Современ- ные компиляторы также позволяют присваивать структуру, передавать ее как параметр функции и использовать как возвращаемое функцией значение. (При использовании старых компиляторов, присваивание должно было вы- полнятся покомпонентно и только указатель на структуру мог передаваться функцией или возвращаться функцией.) Операция сравнения на равенство для структур запрещена. Объект типа структура представляет собой набор компонентов других типов. Так как объ- екты некоторых типов на целевом компьютере могут быть ограничены некото- рыми границами адресации, поэтому объект структурного типа может содер- жать «дыры» — единицы памяти, не принадлежащие ни одному из компонен- тов. Эти дыры делают побитовое сравнение ненадежным, а покомпонентное сравнение может быть слишком трудоемким. (Безусловно, программист мо- жет сам написать функцию для покомпонентного сравнения.) В любой ситуации, где допустимо применение к структуре унарной опера- ции взятия адреса & для получения указателя на нее, также допустимо приме-
Типы 169 нение этой операции к компоненту структуры для получения его адреса. Впол- не допускается использование указателя для ссылки на средину структуры. Исключением являются компоненты, определенные, как битовые поля. В об- щем случае они не лежат в допустимых для адресации границах и поэтому сформировать указатель на них в общем случае невозможно. Язык С не преду- сматривает создание указателей на битовые поля. Ссылки: адресная операция & 7.5.6; присвоение 7.9; битовые попя 5.6.5; операция эквива- лентности == 7.5.6; операция выбора . и —> 7.4.2; эквивалентность типов 5.11. 5.6.3 -омпоненгы Компоненты структуры могут быть любого типа, за исключением гибких типов. Структура не может содержать экземпляры самой себя, но может содер- жать указатели на свои экземпляры. В Стандарте С99 компоненты структуры не могут быть гибких типов. По- следним компонентом структуры может быть недоопределеный массив. В этом случае этот компонент будет называться гибким массивом (flexible array member) (раздел 5.6.8). Пример Следующее определение неверно: struct S { Int а; struct S next; /* Неверно* */ }; Но такое вполне допустимо: struct S { Int а; struct S *next; /* Верно */ }; Имена компонентов структуры определены в отдельном классе перегрузки, ассоциированном со структурным типом. Поэтому имена компонентов в одной структуре должны быть уникальными, но могут совпадать с именами в других структурах или совпадать с именами переменных, функций и типов. Пример Рассмотрим следующую последовательность объявлений: int х; struct А { int х; double у; } у; struct В { int у; double х; } z; Идентификатор х в трех неконфликтных объявлениях: как переменная целого типа, как компонент целого типа структуры А и как компонент типа double струк- туры В. Эти три описания могут соответственно использоваться как: X у.х Z . X
170 Глава 5 Если тег структуры определен в одном из компонентов, то область видимо- сти тега распространяется до окончания блока, в котором определена обрам- ляющая структура. (Если обрамляющая структура определена на верхнем уровне, то тег тоже будет определенным на том же уровне.) Пример В объявлении struct S { struct Т {int a, b; } х; Н тег Т определен от своего первого упоминания до конца области видимости S. Историческая справка: В изначальном определении С, все компоненты во всех структурах относились к одному классу перегрузки и поэтому любые две структуры не могли иметь компоненты с одинаковыми именами. (В качестве исключения допускались компоненты, объявленные с тем же типом и находя- щиеся в структурах на одинаковых позициях!) Такая интерпретация на сего- дняшний день устарела, но ее еще можно встретить в старой документации или реализованной в некоторых старых компиляторах. Ссылки: гибкий массив 5.6.8, недоопределенные типы массивов 5.4; классы перегрузки 4.2 4; область видимости 4.2.1; гибкий тип 5.4.5. 5.6.4 Размещение и выравнивание компонентов структуры Большинство программистов не задумываются о том, как компоненты раз- мещены в структуре. Однако язык С дает программисту некоторый контроль над способом размещения компонентов. Компиляторы С обязаны присваивать компонентам увеличивающиеся адреса памяти в определенном порядке, при- чем адрес первого компонента должен совпадать с адресом начала структуры. Пример Компоненты в структуре struct ( int а, Ь, с; }; и в структуре struct { int a; int b, с;}; размещены совершенно одинаково. В обоих случаях первой идет переменная а, за- тем Ь и наконец с, в порядке увеличения адресов памяти, как показано на рисунке ниже: struct int int int Увеличение адресов памяти Для двух указателей р и q, ссылающихся на компоненты одной и той же структуры, выражение р < q будет справедливо тогда и только тогда, когда
Типы 171 компонент на который ссылается указатель р будет описан в структуре ранее, чем компонент на который ссылается указатель q. Пример struct vector3 { int х, у, z; } s; int *p, *q, *r; p = 4s. x; q = 4s.y; r = 4s. z; /* В данном случае p<q, q < г и p < г. */ Дырки или пустоты могут появиться между двумя любыми последователь- ными компонентами или после последнего компонента в блоке структуры в случае необходимости правильного выравнивания компонентов в памяти. Наборы битов, появляющиеся в таких пустотах — непредсказуемы и могут от- личаться как в разных структурах, так и с течением времени в одной и той же структуре. Занимаемое ими место также включается в значение, возвращае- мое операцией sizeof. Некоторые реализации позволяют контролировать ком- поновку структур при помощи прагм или опций компилятора. 5.6.5 Битовые поля Язык С позволяет программисту размещать целочисленные компоненты так, чтобы они занимали меньше места, чем обычно позволяет компилятор. Такие целочисленные компоненты называются битовыми полями (bit field). Они определяются с помощью описателя компонента с двоеточием и целым константным выражением, которое определяет ширину поля в битах. Пример Приведенная ниже структура содержит 3 компонента: а, Ь и с, занимающие 4, 5 и 7 бит соответственно: struct S { unsigned а: 4; unsigned b: 5, с: 7; }; Битовое поле длиной п бит может представлять беззнаковое целое в диапа- зоне от 0 до 2П-1 или знаковое целое от -2П-1 до 2п-1-1, используя при этом для знаковых чисел представление в дополнительном коде. Изначальное определе- ние языка С позволяло описывать битовые поля только типа unsigned, но Standard С позволяет создавать битовые поля типа unsigned int, signed int или просто int, называемые беззнаковыми, знаковыми или простыми битовыми полями. Подобно простым символам, простое битовое поле может быть знако- вым или беззнаковым. Некоторые реализации С позволяют битовым полям быть любого целочисленного типа, включая тип char. Стандарт С99 позволяет битовым полям иметь тип _Воо1. Битовые поля обычно используются в программах, зависимых от аппарат- ной платформы, там, где есть необходимость точного соответствия организа- ции структуры и ее машинного представления. Конкретный метод размеще- ния в структуре компонентов (и особенно битовых полей) зависит от реализа-
172 Глава 5 ции, но для каждой реализации вполне предсказуем. Суть в том, что битовые поля должны быть размещены в структуру как можно плотнее, но согласно правилам, рассмотрены далее в этом разделе. Использование битовых струк- тур чаще всего означает непереносимость программы. Если программист хочет добиться какого-то определенного размещения структуры в памяти, то он дол- жен сверится с документацией по конкретной реализации и после этого прове- рить, что нужный компилятор действительно упаковывает компоненты ожи- даемым образом. Пример Вот пример того, как битовое поле может быть использовано для создания структу- ры, которая совпадает с предопределенным форматом. Ниже приведено размещение 32-битового слова, интерпретируемого как виртуальный адрес некоторого гипотети- ческого компьютера. Слово содержит номер сегмента, номер страницы и смещение внутри страницы, а также бит S «режима суперпользователя» и один бит, который не используется. S Segment Page Offset Длина поля (в битах) 116 8 16 Чтобы воспроизвести такое размещение компонентов в памяти, прежде всего нам надо знать, располагает ли наш компьютер битовые поля слева направо либо справа налево, то есть используется «тупоконечная» или «остроконечная» архитектура (см. раздел 6.1.2). Если размещение компонентов происходит справа налево, то оп- ределение структуры будет выглядеть следующим образом: typedef struct ( unsigned Offset : 16; unsigned Page : 8; unsigned Segment : 6; unsigned UNUSED : 1; unsigned Supervisor : 1; } virtual_address; Если же используется обратная схема (то есть слева направо), то определение струк- туры следующее: typedef struct { unsigned Supervisor : 1; unsigned UNUSED : 1; unsigned Segment : 6; unsigned Page : 8, unsigned Offset : 16; } virtual_address, Простой целый тип в отношении знаковости аналогичен простому символь- ному типу. Простое целочисленное битовое поле может быть фактически реа- лизовано как знаковый или беззнаковый тип (см. раздел 5.1.3). Для хранения знаковых и беззнаковых значений должны быть реализованы соответствую- щие знаковые и беззнаковые битовые поля. Пример Рассмотрим действие объявлений Standard С на компьютере, работающем в двоич- ном дополнительном коде:
Типы 173 struct S { unsigned ubf:3, signed sbf:3; int bf:3; } x = { -1, -1, -1 }; int i = x.ubf; int j = x.sbf; int к = x.bf; Значение переменной i должно равняться 7 , j = -1, но переменная к может быть равна как 7, так и -1. Компиляторы свободны в определении ограничений на максимальный раз- мер битового поля. Они также могут определять адресные границы, которые не могут пересекать битовые поля. Эти ограничения выравнивания обычно связаны с естественным для целевого компьютера размером слов. Если бито- вое поле для компьютера слишком большое, то компилятор выдаст соответст- вующее сообщение об ошибке. Если битовое поле пересечет границы слова, то оно может быть перенесено в следующее слово. Неименованные битовые поля также могут быть включены в структуру для необходимых выравниваний адресов соседних элементов. На неименованные битовые поля невозможно ссылаться и их содержимое во время выполнения программы непредсказуемо. Пример В следующей структуре компонент а занимает первые 4 бита, следующие 2 занима- ют биты заполнения, а за ними — компонент Ь, состоящий из 6 бит (Для случая, ко- гда размер слова равен 16, последние 4 бита структуры также не будут задействова- ны; см. раздел 5.6.7) struct S { unsigned а : 4; unsigned 2; unsigned b : 6; } ; Указание для неименованного битового поля длины 0 бит имеет особый смысл — это индикатор того, что при наличии предыдущего битового поля в его область памяти не следует размещать другие битовые поля. Здесь под об- ластью памяти следует понимать некоторую определяемую реализацией еди- ницу памяти. Пример В приведенной ниже структуре, компонент b должен начинаться на границе адреса- ции (16 бит) со следующего за компонентом а слова. Эта новая структура будет за- нимать в два раза больше памяти чем предыдущая: struct S { unsigned а 4; unsigned : 0; unsigned b : 6; а ь 10 К битовым полям не стоит применять операцию взятия адреса &, так как мно- гие компьютеры не могут адресовать напрямую поля произвольного размера.
174 Глава 5 Ссылки: операция взятия адреса & 7.5.6; ограничения по выравниванию 6 1.3; тип _Воо1 5.1.4; порядок следования байт 6.1.2; типы перечисления 5.5; знаковые типы 5.1.1; беззнако- вые типы 5.1.2. 5.6.6 Проблемы переносимости Привязка к методам размещения компонентов небезопасна по следующим причинам. Во-первых, разные компьютеры по-разному выравнивают границы объектов. Например, четырехбайтное целое на некоторых компьютерах долж- но начинаться с адреса, кратного четырем, в то время как на других компью- терах целое может (и будет) выровнено по ближайшему байту. Во-вторых, различны ограничения, накладываемые на ширину битового поля. У некоторые компьютеров размер слова равен 16 битам, что ограничивает максимальный размер поля и устанавливает границу, которое поле не может пересекать. У других компьютеров размер слова равен 32 битам, и так далее. В-третьих, компьютеры могут отличаться по способу размещения битовых полей в слове, то есть по «порядку байт». В семействе компьютеров Motorola 68000 символы размещаются слева направо, от старшего бита к младшему. На компьютерах Intel 80x86 символы размещаются справа налево, от младшего бита к старшему. Как видно на примере структуры virtual_address — для компьютеров с разным порядком размещения байтов необходимо по-разному описывать структуры. Мы знаем два случая, в которых использование битовых полей оправданно: 1. Структура должна в точности соответствовать некоему, заранее опреде- ленному типу данных для возможности работы с ним в С. (Такая про- грамма может быть вообще не переносима.) 2. Необходимо поддерживать массив структурированной информации большого объема, из-за чего возникает необходимость в более рацио- нальном размещении компонентов. Используя в С битовые операции сдвига и наложения маски, можно реали- зовать битовые поля, которые не будут чувствительны к порядку байт в слове. Пример Рассмотрим задачу доступа к полю Page в структуре virtual_adress (см. второй при- мер в разделе 5.6.5). Учитывая то, что 8-битовое поле смещено на 16 битов от ниж- ней границы слова, его можно адресовать с помощью следующего фрагмента исход- ного текста: unsigned V; /* форматировано как virtual_address */ int Page; Page = (V & OxFFOOOO) » 16; Этот код эквивалентен более читабельному Page = V.Page, но способ с использова- нием маски и сдвигов не чувствителен к порядку следования байтов, как и опреде- ление virtual_address. Ниже приведены операции сдвига и наложения маски для V== 0xb393352e (Page == 0x93): 10110011100100110011010100101110 00000000111111110000000000000000 00000000100100110000000000000000 00000000000000000000000010010011 V OxFFOOOO V & OxFFOOOO (V & OxFFOOOO) »16
Типы 175 Подобные операции могут быть использованы и для присвоения значения битовому полю. Различие в скорости выполнения этих двух методов доступа невелико. Ссылки: условия выравнивания 6.1.3; битовые операции 7.6.6; порядок адресации 6.1.2; опе- рации сдвига 7.6.3. 5.6.7 Размеры структур Размер объекта типа структуры определяется объемом памяти, необходи- мой для представления всех компонентов экземпляра этого типа, включая все используемое для выравнивания компонентов пространство. Объем занимае- мого структурой места рассчитывается как объем выровненного элемента мас- сива таких структур. (Для любого типа Т, включая структуры, размер «-эле- ментного массива типа Т будет равен размеру типа Т умноженному над.) Ина- че говоря, структура должна заканчиваться на той же границе выравнивания, на которой она началась. То есть если структура должна, например, начинать- ся на четном байте, то и заканчиваться она должна тоже на границе четного байта. Требования выравнивания в памяти для структур должны быть, как минимум, равны самым строгим требованиям для их компонентов. Пример На компьютере, у которого выравнивание осуществляется по четырем байтам, дли- на приведенной ниже структуры будет кратна (или в точности равна) четырем бай- там, даже в том случае, если в действительности используются только 2 байта: struct S { char cl; char c2; 1 1 2 Пример На компьютере, у которого выравнивание для всех объектов типа double осуществ- ляется по восьми байтам, длина следующей структуры будет вероятно равна 24 бай- там, несмотря на то, что в сумме компоненты занимают только 18: struct s { double value; char name[10]; 1> Байты: 8 10 6 Шесть байт выравнивания нужны для дополнения размера структуры до значения кратного 8. Если бы выравнивание не использовалось, то в массиве таких структур не все элементы могли бы удовлетворять требования кратности адреса восьми. value name Пример Требование выравнивания по заданным границам может привести к появлению не- занятого пространства в середине структуры. Если в предыдущем примере порядок компонентов обратить, то длина структуры по-прежнему будет равна 24 байтам, но между компонентами появится неиспользованное пространство для того, чтобы компонент value мог расположиться по адресу, кратному восьми, считая от начала структуры:
176 Глава 5 struct S ( char name [10]; double value; }; name value 6 8 Байты: 10 Так как любой объект этого типа будет находиться по адресу кратному 8, то разме- щение компонента value такого объекта будет удовлетворять требованиям выравни- вания. 5.6.8 Гибкий массив В Стандарте С99 последний компонент структуры может иметь недоопреде- ленный тип массива. В этом случае его называют гибким массивом (flexible array member). Гибкие массивы были введены, чтобы узаконить давно исполь- зуемый (хоть и не безопасный) программистский подход, в рамках которого размер структуры может изменяться во время выполнения программы. Для использования гибкого массива объявим структуру типа S, у которой последний компонент будет массивом F с типом элементов Е. Тип S не может содержать только F; он должен содержать как минимум еще один компонент. К примеру: struct S { int F_len; double F[] ; }; /* E имеет тип double */ Значение sizeof(S) будет равно размеру структуры без элемента F, но, вклю- чая размер необходимого для выравнивания пространства, которое будет нахо- диться перед F. (Чтобы определить объем необходимой для выравнивания па- мяти, следует предположить, что F объявлен как массив постоянной длины с элементами того же типа, и подсчитать объем необходимой для выравнива- ния памяти перед массивом F, если выравнивание в данном случае вообще не- обходимо.) При использовании I-значения типа S для доступа к объекту данных можно интерпретировать F как массив постоянной длины L, но так, чтобы не выйти за пределы объекта данных структуры S. Таким образом, если объект данных имеет длину D, то L — набольшее неотрицательное целое удовлетворяющее ус- ловию sizeof(S) + L*sizeof(E) <= D, и можно ссылаться на элементы F[0], F[1 ], .... F[L-1]. Если вы просто объявили переменную типа S, то массив использо- вать еще нельзя, так как объект данных (переменная) не располагает соответ- ствующим пространством. (D будет иметь размер sizeof(S) следовательно, ве- личина L должна равняться 0.) Но, даже если массиву места не выделено, вы всегда можете ссылаться на &F[0]. При использовании типа S для доступа к объекту данных, большему чем он сам, можно объявить указатель на S и присвоить ему адрес большего объекта или использовать объединение для наложения S на больший объект. Пример Обычно гибкое массивы используются для определения структуры, содержащей вектор переменного размера и его длину. struct Vec { int len; double vec[], } Если длина вектора постоянна, то можно описать структуру статичной. #define N 20 /* Длина вектора */ union(
Типы 177 char data_object[sizeof(struct Vec) + N*sizeof(double)]; struct S v; } u = { .v = (N) }; /* именованный инициализатор Стандарта C99 */ Если длина вектора неизвестна до выполнения программы, то для выделения ему памяти следует использовать функцию malloc. struct Vec *р; int n; /* длина вектора */ р = malloc( sizeof(struct Vec) + n * sizeof (double)); p->len = n; Вот пример использования такой структуры: for (i = 0; i < u.v.len; i++) u.v.vec[i] = 0.0; for (i =0; i < p->len; i++) p->vec[i] = 0.0; До появления Стандарта C99 вам надо было бы описывать структуру следующим об- разом struct Vec ( int len; double vec[l]; } и изменять вызов malloc, к примеру, на р = malloc ( sizeof (struct Vec) 4- (len-1) * sizeof (double)); Хотя этот фрагмент программы обычно работает, его поведение было (и остается) не- определенным. 5.7 Тип данных объединение Синтаксис определения объединения практически идентичен синтаксису определения структуры: спецификатор-типа-объединение : определение-типа-объединение ссылка-на-тип-объединение определение-типа-объединение : union тег-объединенияпп,. {список-полей } ссылка-на-тип-объединение : union тег-объединения тег-объединения : идентификатор Синтаксис для определения компонентов объединения такой же, как и для оп- ределения компонентов структуры. В традиционном С объединения не могли содержать битовых полей, но в Standard С это ограничение устранено. Как и в случае структур и перечислений, каждый тип данных объединение представляет новый тип данных (объединение), отличный от всех других. Тег объединения, если он присутствует в определении, ассоциируется с этим но- вым типом и может использоваться в последующих ссылках на этот тип дан- ных объединение. Опережающие ссылки и неполные определения типов объе- динений разрешаются согласно тем же правилами, что и для структурных ти- пов данных.
178 Глава 5 Компонент объединения может иметь любой тип, который не является гиб- ким. Кроме того, объединения не могут содержать экземпляры самих себя, хотя они могут содержать указатели на такие экземпляры. Как и для струк- тур, имена компонентов объединения определяются в специальном классе пе- регрузки, ассоциирующемся с данным типом объединения. Иными словами, имена компонентов одного и того же объединения обязаны различаться, но они могут, тем не менее, совпадать с именами компонентов других объедине- ний, а также с именами переменных, функций и типов данных. 5.7.1 Размещение (выравнивание) компонентов объединения Каждый компонент объединения представляется выделенной областью па- мяти, начало которой совпадает с началом объединения. В любой момент вре- мени объединение может содержать только одно из значений своих компонен- тов. Объект типа объединение будет выровнен по границе выравнивания, под- ходящей каждому из имеющихся компонентов. Пример Ниже представлен тип объединения с тремя компонентами, эффективно наклады- вающимися в памяти друг на друга: union и { double d; char с [2 ] ; int i ; d (8 байтов) с (2) i (4) Пример Пусть у нас есть следующие определения типа объединения и объекта: static union U { ...; int С, ...; } object, *P = Sobject; Тогда выполняются следующие равенства: (union и *) 4 (Р->С) == Р 4(Р->С) == (int *) Р Более того, эти равенства верны независимо от типа данных компонента и от того, какие компоненты в объединении предшествуют и следуют за ним. Ссылки: условия выравнивания 6.1.3. 5.7.2 Размеры объединений Размер объекта типа объединение равен объему памяти, необходимому для представления наибольшего по размеру компонента этого типа, плюс тот до- полнительный объем памяти, который может понадобиться для увеличения длины до соответствующей границы выравнивания. Общее правило можно сформулировать так: размер объединения будет увеличен до размера, который
Типы 179 данный тип занимал бы, будь он элементом массива такого типа данных. Вспомните, что для любого типа Т, включая объединения, размер массива из п элементов типа Т равен (размер типа Т)‘п. Иными словами, переменная типа объединение должна заканчиваться на той же границе выравнивания, на кото- рой она началась. То есть если, например, переменная типа объединение должна начинаться на четном байте, то и заканчиваться она должна тоже на границе четного байта. Обратите внимание, что требование к выравниванию для типа данных объе- динение будет как минимум таким же строгим, как и для компонента, к кото- рому предъявляются наиболее строгие требования выравнивания. Пример На машине, которая требует, чтобы все объекты типа double имели адрес, кратный 8, длина следующего объединения будет равна 16, несмотря на то, что размер самого длинного компонента составляет лишь 10: (8 байтов) union U { double value; char name [10] ; 1; value (8 байтов) паше (10 байтов) (6 байтов) Шесть дополнительных байтов необходимы для того, чтобы сделать размер объедине- ния соответствующим требованию выравнивания, т.е. кратным 8. Если бы дополни- тельные байты не использовались, то тогда в массиве таких объединений не все ком- поненты valne были бы надлежащим образом выровнены по адресам, кратным 8. 5.7.3 Использование типов объединений Тип данных объединение в С чем-то похож на «вариантную запись» в дру- гих языках. Как и структуры, объединения определяются как объекты, имею- щие несколько компонентов. Однако в отличие от структур, объединение мо- жет содержать не более одного из своих компонентов в каждый конкретный момент времени; компоненты как бы накладываются друг на друга в памяти, отведенной под объединение. В том случае, когда объединение очень велико или имеется большой массив объединений, экономия пространства может быть значительной. Пример Предположим, что мы хотим, чтобы объект был или целым числом, или числом с плавающей точкой, в зависимости от конкретной ситуации. Мы определяем объе- динение datum: union datum { int i; double d; 1; а затем определяем переменную данного типа: union datum u; Теперь, чтобы сохранить целое в данном объединении, мы пишем
180 Глава 5 u.i = 15; Чтобы сохранить в объединении число с плавающей точкой, мы присваиваем значе- ние второму компоненту: u. d = 88.9е4; Обращаться к компоненту объединения следует только тогда, когда послед- нее присваивание значения объединению осуществлялось через данный ком- понент. Язык С не позволяет узнать, которому из компонентов объединения было произведено последнее присваивание, поэтому программист должен либо запоминать это, либо использовать явные информационные теги, ассоциирую- щиеся с объединениями. Информационный тег — это ассоциирующийся с объ- единением объект, который содержит информацию о том, какой компонент хранится в объединении в текущий момент. Информационный тег и (соответ- ствующее ему) объединение могут быть сведены в структуру. Пример Мы можем заменить объединение union widget { long count; double value; char name [10];} x; следующими объектами: enum widget_tag ( count widget, value_widget, name_widget }; struct WIDGET { enum widget_tag tag; union { long count; double value; char name[10], } data; } x, typedef struct WIDGET widget; Размер структуры widget составляет 24 байта, что вызвано предположением о том, что объекты типа double должны быть выровнены по 8-байтовым границам. Воз- можное расположение компонентов приведено ниже: tag (4) data.count > data.value data. name (10) 4 4 10 6 Если, как это часто бывает, объекты типа double могут выравниваться по 4-байто- вым границам, то длина структуры widget будет составлять лишь 16 байт. Чтобы присвоить объединению целое значение, мы пишем х . tag = count_widget; х.data.count = 10000; Чтобы присвоить число с плавающей точкой, мы пишем х.tag = value_widget, х.data.value = 3.1415926535897932384; Для присвоения объединению строки, .мы можем воспользоваться библиотечной функцией strnepy:
Типы 181 x.tag = name_widget; strnepу (x.data.name, "Millard", 10); Ниже приведена переносимая функция, которая позволяет выводить последнее при- своенное переменной значение. Функция print_widget может быть вызвана незави- симо от того, которому из компонентов было произведено последнее присваивание: void print_widget (widget w) { switch (w.tag) { case count_widget: printf ("Count %ld\n", w.data.count); break; case value_widget: printf ("Value %f\n", w.data.value); break; case name_widget: printf ("Name \"%s\”\n", w.data.name) ; break; ) ) Хотя Standard С мало внимания уделяет размещению и выравниванию объ- единений, он, тем не менее, отдельно описывает случай объединений, вклю- чающих в себя компоненты схожих структурных типов. Если все типы этих структур начинаются одной и той же последовательностью своих собственных компонентов, Standard С гарантирует, что эти начальные последовательности будут точно наложены одна на другую. Это, к примеру, дает вам возможность разместить информационный тег в начале каждой из структур и обращаться к этому тегу используя любой структурный член. Ссылки: приведение выражений 7.5.1; перечисления 5.5; перегрузка 4.2.4; область видимо- сти 4.2.1; оператор switch 8.7; функция strnepy 13.3; структуры 5.6; typedef 5.10. 5.7.4 (Не)корректное использование типа объединения Использование объединения можно охарактеризовать как непереносимое, если к компоненту объединения производится обращение при условии, что по- следнее присваивание значения объединению имело место через другой компо- нент. Программисты иногда прибегают к этому приему, чтобы «проникнуть внутрь» системы типов С и узнать что-либо о внутреннем представлении дан- ных в компьютере (что само по себе является признаком непереносимости). Пример Чтобы узнать, как представляется число с плавающей точкой: 1. Создайте объединение с двумя компонентами одинакового размера, причем один компонент должна быть целым, а другой — числом с плавающей точкой: float (4) int (4 байта) 2. Присвойте значение компоненту с плавающей точкой. 3. Прочитайте значение целого компонента и выведите его на печать, как, ска- жем, число в шестнадцатеричной системе счисления. Вот функция, которая выводит число в шестнадцатеричной форме (при условии, что типы float и int имеют одинаковую длину):
182 Глава 5 void print_rep(float f) { union ( float f; int i } f_or_i; f_o r_i.f = f; printf("Представлением %12.7e является %#010x\n", f_or_i.f, f_or_i.i ); } Вот результат выполнения print_rep(1.0) на нашей рабочей станции (процессор Motorola 68020): Представлением 1.0000000е+00 является 0x003f800000 Обратите внимание, что операция преобразования типов не может быть использова- на для получения информации о внутреннем представлении данных. Оператор пре- образования типов в С преобразует свой операнд в ближайшее значение в новом представлении; (int) 1.0 есть 1, а не 0x003f800000. 5.8 Функциональные типы Тип «функция, возвращающая Т>>, является функциональным типом; здесь Т может быть любым типом, кроме «массив из ...» или «функция, возвращаю- щая ...». Другими словами, функции не могут возвращать массивы или другие функции, хотя они могут возвращать указатели на массивы или на функции. Функции могут быть созданы одним из двух способов. В первом определе- ние функции может создать функцию, определить ее параметры и возвращае- мое значение, а также представить тело самой функции. Более подробная ин- формация об определении функций приведена в разделе 9.1. Во втором способе объявление функции может предоставить ссылку на функциональный объект, определенный в каком-либо ином месте. Пример Ниже приведено определение функции возведения в квадрат: int square(int х) { return х*х; } Если бы функция square была определена в ином месте, следующее объявление представило бы ее имя и позволило бы вызывать эту функцию. extern int square(int), Объявление внешней функции может относиться к функции, определенной в другом исходном файле С или к функции, определенной позже в том же ис- ходном файле (так называемая «опережающая ссылка»). Пример Опережающие ссылки можно использовать для того, чтобы создавать взаимно ре- курсивные функции, такие extern int f(void); как f и g в данном примере. int g(void) ( ... f () ; int f (void) { ... g() ; . . .)
Типы 183 Этот же стиль объявления можно использовать и для статических функций: static int f 0; static int g(> { - .. f() ; • • ) static int f 0 { . . . g () : • - -) Некоторые компиляторы, не соответствующие Standard С, могут не допус- кать подобные опережающие ссылки на статические функции. Иногда они предоставляют компромиссное решение, разрешая первому объявлению ис- пользовать спецификатор класса памяти extern, а затем изменяя класс памя- ти на static, когда определение попадает в область видимости. Пример extern int f(void}; /* не совсем extern, сн. ниже... */ static int g{void) { ... f(); ...} static int f(void) ( ... g(); ...} /* теперь делаем f статической */ Эта программистская конструкция как минимум вводит в заблуждение. Standard С требует, чтобы первое объявление функции (а на самом деле и лю- бого идентификатора) указывало, будет она внешней или статической. Это по- зволяет проводить однопроходную компиляцию программ на С в тех случаях, когда реализация должна по-разному вести себя со статическими и внешними функциями. Standard С явно не запрещает стиль «внешняя-затем-статиче- ская», но он не определяет его смысл. Над выражением функционального типа допустимы только такие опера- ции, как преобразование его в указатель на функцию, а также вызов функции. Пример В следующих объявлениях внешние идентификаторы f, fp и apf имеют соответствен- но тип «функция, возвращающая значение типа int», «указатель на функцию, воз- вращающую значение типа int» и «массив указателей на функции, принимающие па- раметр типа double и возвращающие значения типа int»: extern int f(), (*fp) () , (*apf []) (double); Объявление apf включает прототип функции, соответствующий Standard С. Эти идентификаторы можно использовать в выражениях вызова функций следующим образом: int i, j, k; i = f(14); i = (*fp) (j, k) ; i = (*apf [ j]) (k) ; Если имеет место вызов функции, у которой нет видимого прототипа, то к ре- альным аргументам применяются определенные стандартные преобразования, но не предпринимается никакой попытки проверки соответствия типов или ко- личества аргументов типам и количеству известных формальных аргументов функции. Аргументы функций с видимыми прототипами преобразовываются к указанному типу параметра. В предыдущем примере аргумент целого типа к функции, обозначенной как *apf[j], будет преобразован к типу double.
184 Глава 5 В Standard С и некоторых других реализациях выражение типа «указатель на функцию» может быть использовано при вызове функции без явного разы- меновывания; в этом случае вызов (*fp)(j,k) в последнем примере может быть переписан как fp(j,k). Выражение типа «функция, возвращающая ...», которое не используется при вызове функции, в качестве аргумента операции взятия адреса & или в ка- честве аргумента операции sizeof, немедленно преобразовывается к типу «ука- затель на функцию, возвращающую (Отказ от преобразования, когда функция является аргументом sizeof, гарантирует, что выражение sizeof бу- дет верным, и не вернет просто размер указателя.) Единственными выраже- ниями, результатом которых может являться значение типа «функция, воз- вращающая значение типа То, являются имя такой функции и непрямое вы- ражение, состоящее из унарной операции разыменовывания *, примененной к выражению типа «указатель на функцию, возвращающую ...». Пример Следующая программа присваивает указателям fpl и fp2 одно и то же значение: extern int f () , int (*fpl) (), (*fp2) (); fpl = f; /* неявное преобразование к указателю */ fp2 = &f; /* явное создание указателя */ Предполагается, что вся необходимая для вызова функции информация со- держится в объекте типа «указатель на функцию, возвращающую ...». Хотя часто считается, что указатель на функцию является адресом кода функции в памяти, на некоторых компьютерах указатель на функцию на самом деле указывает на некий блок данных, содержащий информацию, необходимую для вызова функции. Такие тонкости представления обычно скрыты от про- граммиста и касаются только создателей компилятора. Ссылки: преоброзавания аргументов функции 6.3 5, вызов функции 7.4 3; описатель функции 4.5 4; определение функции 9.1; прототип функции 9.2; операция разыменования * 7.5.7, опе- рация sizeof 7 5.2; обычные унарные преобразования 6.3 3. 5.9 Тип void Тип void (пустой тип) не имеет ни значений ни операций. спецификатор-muna-void: void Тип void используется: • в качестве типа возвращаемого функцией значения, для указания того, что функция не возвращает никакого значения; • в выражении приведения типа, когда желательно явно избавиться от зна- чения; • для формирования типа void *, «универсального» указателя на данные; • вместо списка параметров в объявлении функции для указания того, что функция не принимает аргументов.
Типы 185 Пример Объявление функции write_line использует тип void как в качестве типа возвращае- мого значения, так и вместо списка параметров. extern void write_line(void) ; write_line(); /* функция ничего не возвращает */ Определение write_line2 говорит о том, что функция возвращает значение, но ее вы- зов использует преобразование к типу void для того, чтобы возвращенное значение явным образом отбросить. extern int write_line2(void); (void) write_line2 (...); /* проигнорировать возвращенное значение */ Ссылки: преобразование типов 7,5.1; отбрасывание значений 7.13; тип void * 5.3.2. 5.10 Имена typedef Когда имеет место определение, в котором в качестве «класса памяти» ука- зан typedef, применяется механизм определения типов. typedef-имя : идентификатор Идентификатор, заключенный в любом объявителе объявления, определя- ется как имя типа («typedef-имя»); типом является то, что было бы присвоено идентификатору в случае, если бы строилась конструкция объявления пере- менной. Как только имя было объявлено как тип, оно может появляться везде, где может стоять спецификатор типа. Это позволяет использовать для слож- ных типов мнемонические аббревиатуры. Пример Взгляните на следующие определения: typedef int *IP; IP: "указатель на значение типа int" */ typedef int (*fp) ( ) ; /* FP: "указатель на функцию, */ /* возвращающую значение типа int" */ typedef int F(int) ; /* F: "функция С одним параметром */ /* типа int, возвращающая значение типа int" */ typedef double A5[5J; /* А5: "5-элементный массив */ /* значении типа double" */ typedef int A[] ; /* А: "массив значений типа int" */ После этих объявлений допустимы следующие: ip ip; /* ip: указатель на значение типа int */ ip fip() ; /* fip : функция, возвращающая указатель на */ /* значение типа int */ FP fp; /* fp: указатель на функцию, возвращающую */ /* значение типа int */ F *fp2; /*fp2 : указатель на функцию, принимающую параметр типа int */ /* и впапращастпум значение типа int */ A5 a5; /* a5: 5-элементный массив значений типа double */ A5 a25[2] ; /* a25 : double [2] [5]: 2-элементный массив */ /* 5-элементных массивов значений типа double */ A a; /* a: i массив значений типа int */ /* (с неуказанной размерностью) */
186 Глава 5 А *арЗ [3]; /* арЗ: 3-элементный массив указателей на */ /* массивы значений типа int */ /* (с неуказанными размерностями) */ Пример Имена typedef не должны смешиваться с другими спецификаторами типа: typedef long int bigint; unsigned bigint x; /* Неверно */ Комбинирование квалификаторов типа с именами typedef разрешается и является полезной возможностью: const bigint х; /* Корректно */ Объявления со спецификатором класса памяти typedef не вводят новых ти- пов данных; новые имена считаются синонимами типов, которые могли быть определены по-другому. Пример После объявления typedef struct S { int a; int b; } sltype, s2type; спецификаторы типа sltype, s2type и struct S могут использоваться заменяя друг друга, для указания одного и того же типа. Хотя typedef только вводит синонимы для типов, которые можно указать по-другому, реализации С могут внутренне сохранять объявленные имена ти- пов для того, чтобы отладчики и другие программные средства могли обра- щаться к типами с использованием имен, введенных программистом. В Стандарте С99, если объявление typedef включает тип массива перемен- ной длины, то размера массива вычисляется тогда, когда обрабатывается объ- явление typedef, а не тогда, когда имя typedef используется для объявления массива. Пример В следующем фрагменте программы массив а является 10-элементпым массивом це- лых чисел, потому что размер типа Array был определен в тот момент, когда в ис- ходном тексте программы встретился typedef, а не тогда, когда а был объявлен. { int п = 10; typedef int Array (n] ; n = 25; Array a; } Ссылки: совместимость типов 5,11; массивы переменной длины 5.4,5
Типы 187 5.10.1 Имена typedef для функциональных типов Функциональному типу может быть присвоено имя typedef, но функции не должны наследовать от имен typedef свою «функциональность». Это в некото- рой мере ограничивает применение имен typedef для функциональных типов. Пример DblFunc становится синонимом для «функция, возвращающая значение типа double» после следующего объявления: typedef double DblFunc (); После этого объявления DblFunc можно использовать для объявления указателей на функциональный тип, массивов указателей на функциональный тип и так далее, применяя обычные правила составления описателей: extern DblFunc *f_ptr, *f_array[]; Придерживаясь общепринятых правил объявлений типов, программист не должен объявлять некорректные типы, такие, как, например, массив функций: extern DblFunc f_array[10]; /* Неверно* */ Тем не менее, DblFunc нельзя использовать для определения функций. Следующее объявление fabs не будет принято компилятором, потому что оно похоже на опреде- ление функции, возвращающей другую функцию: DblFunc fabs (double х) { if (х<0.0) return -х; else return x; ) Опускание скобок за fabs не решает проблему, так как необходимо указать аргу- мент функции. Определение функции должно быть переписано в обычном виде, как если бы DblFunc не существовало: double fabs(double х) { if (х<0.0) return -х; else return x; I В Standard С имена typedef могут включать информацию прототипа функции, включая имена аргументов: typedef double DFuncType( double x ); typedef double (*FuncPtr) ( int, float ); В этом примере, DFuncType — это функциональный тип, a FuncPtr — тип указате- ля на функцию. Ссылки: описатели функций 4.5; определения функций 9.1; прототипы функций 9.2. 5.10.2 Переопределение имен typedef Язык предусматривает возможность переопределения имен typedef во внут- ренних блоках, как обычных идентификаторов. Пример typedef int Т; Т foo;
188 Глава 5 { float т; /* Новое определение для Т */ Т = 1.0, Единственным ограничением является то, что в переобъявлении специфи- каторы типа не могут быть опущены на основании того, что тип по умолчанию будет типом int. Некоторые компиляторы, несовместимые со Стандартом ISO, имели проблемы с переобъявлениями имен typedef, что, возможно, было вы- звано тем давлением, которое имена typedef оказывают на грамматику языка С. Мы сейчас обратимся к этой проблеме. Ссылки: переопределение имен typedef в C++ 5.13.2; область видимости имен 4.2 1. 5.10.3 Замечания о реализации Возможность использования обычных идентификаторов в качестве специ- фикаторов типа делает грамматику С контекстно-зависимой, и следовательно не относящейся к LALR(l). Чтобы убедиться в этом, рассмотрим строку про- граммы А ( *В } ; Если А было определено как typedef-имя, то эта строка является определени- ем переменной В, имеющей тип «указатель на А» (скобки вокруг "^В” при этом игнорируются). Если А не является именем типа, то тогда приведенная строка является вызовом функции А с единственным параметром *В. Эта не- однозначность не может быть разрешена грамматически. Компиляторы С, основанные на компиляторе компиляторов YACC, такие, как, например, Portable С Compiler, справляются с этой проблемой путем на- правления информации, полученной при семантическом анализе, обратно в лексический. Все компиляторы С обязаны производить во время лексическо- го анализа обработку имен typedef. 5.11 Совместимость типов Два типа данных в С являются совместимыми, если они представляют со- бой один и тот же тип или если они «достаточно близки», чтобы считать их одинаковыми для большинства применений. Эта точка зрения на совместимые типы была введена в Standard С, но в значительно мере она просто в более фор- мализованном виде отражает правила, используемые в традиционном С. Для того чтобы обеспечивать некоторые возможности Standard С, такие как прото- типы функций и квалификаторы типа, необходимы, кроме всего прочего, не- которые дополнительные правила. Чтобы два типа были совместимыми, они должны представлять собой один и тот же тип, либо быть указателями, функ- циями или массивами с определенными свойствами. Эти специальные прави- ла обсуждаются в последующих разделах. С любой парой совместимых типов ассоциируется композитный тип (com- posite type), который является общим типом, порожденным данными совмес- тимыми типами. Это похоже на то, как обычные двоичные преобразования ис- пользуют два целочисленных типа и комбинируют их, выдавая в результате
Типы 189 общий тип результата для некоторых арифметических операций. Композит- ный тип, порожденный двумя совместимыми типами, описан вместе с прави- лами совместимости типов. Ссылки: типы массивов 5.4; прототипы функций 9.2; функциональные типы 5.8; указательные типы 5.3; структурные типы 5.6; квалификаторы типа 4.4.3; типы объединений 5.7; обычные двоичные преобразования 6.3 5.11.1 Идентичные типы Два арифметических типа могут быть совместимы только в том случае, если они являются одним и тем же типом. Если тип может быть записан с по- мощью различных комбинаций спецификаторов типа, то все альтернативные формы являются тем же типом. Другими словами, типы short и short int — это один и тот же тип, но типы unsigned, int и short различны между собой. Тип signed int совпадает с типом int (то же касается типов short и long), за ис- ключением случая, когда эти типы используются в качестве типов битовых полей. Типы char, signed char и unsigned char всегда различны. Любые два совпадающих типа совместимы и их композитным типом явля- ется тот же тип. В Standard С присутствие квалификаторов типа меняет тип: тип const int не совпадает и не является совместимым с типом int. Имена, объ- явленные как типы в определениях typedef, являются не новыми типами, а синонимами уже существующих. Пример После приведенных ниже объявлений типы ри q одинаковы; типы х и у также оди- наковы, но тип ни одной из этих переменных не совпадает с типом и; типы TS и struct S совпадают; типы u, w и у одинаковы. char * р, *q; struct {int а, Ь;} х, у; struct S {int а, Ь;} и; typedef struct S TS; struct S w; TS y; Пример После приведенных ниже объявлений тип my_int совпадает с типом int, а тип my_function совпадает с типом "float *()”: typedef int my_int; typedef float *my_function () ; Пример После следующих объявлений переменные w, х, у и z имеют один и тот же тип. struct S { int а, Ь; } х; typedef struct S tl, t2; struct S w; tl y; t2 z;
190 Глава 5 Ссылки: целочисленные типы 5,1; указательные типы 5 3; структурные типы 5.6; имена typedef 5 10 5.11.2 Совместимость перечислений Каждое определение перечислимого типа порождает новый целочисленный тип. Standard С требует, чтобы каждый перечислимый тип был совместим с определяемым реализацией целочисленным типом, который его представля- ет. Этот совместимый целочисленный тип может различаться для разных пе- речислений в одной и той же программе. Композитный тип является перечис- лимым типом. Никакие два различных перечислимых типа, определенных в одном исходном файле, не являются совместимыми. Пример В следующих объявлениях типы Е1 и Е2 несовместимы, но типы Е1 и ЕЗ совмести- мы, т.к. являются одним и тем же типом. enum е {a,b} Е1; enum {с,d} Е2; enum е ЕЗ; Так как перечислимые типы в общем случае интерпретируются как цело- численные, то значения разных перечислимых типов можно свободно смеши- вать, независимо от совместимости данных типов. Пример В приведенном ниже фрагменте программы требование совместимости проявится в том, что Standard С отклонит объявление второй функции из-за того, что тип аргу- мента в прототипе не согласовывается с первым объявлением: extern int f( enum {a,b} x); extern int f( enum {b,c} x); Реализации, несовместимые co Standard С, иногда считают перечислимые типы полностью совместимыми с типом int и наоборот. Ссылки: перечислимые типы 5.5. 5.11.3 Совместимость массивов Два одинаково квалифицированных типа массивов совместимы только то- гда, когда совместимыми являются типы их элементов. Если оба типа специ- фицируют константные размеры, то тогда размеры также должны совпадать. Однако если лишь один из массивов специфицирует константный размер (или это не верно ни для одного из них), то эти типы массивов являются совмести- мыми. Композитный тип двух совместимых типов массивов представляет со- бой тип массива с композитным типом элементов и таким же квалификатором типа. Если один из изначальных типов специфицирует константный размер, то композитный тип будет иметь этот константный размер; в противном слу- чае размер не определен. Если два массива используются в контексте, требую- щем, чтобы они были совместимы, то результаты будут неопределенными, если только размеры массивов не совпадают во время выполнения программы.
Типы 191 Пример Следующие типы массивов совместимы между собой в соответствии с комментария- ми; е является массивом переменной длины (С99). extern Int а[]; /* совместим с Ь, сие, но не с d */ int Ь[5]; /* совместим только с а и е */ int с[10]; /* совместим только с а и е */ const int d[10]; /* несовместим с другими типами */ int е [п]; /* совместим с а, Ь и с, но не с d */ Тип d несовместим с другими типами из-за того, что тип его элементов, const int, не является совместимым с типом элементов int. Композитным типом для типов а и b является int [5]. Во время выполнения программы использование а и b вместо друг друга было бы корректно только в случае, если бы фактическое определение а имело длину 5. Ссылки: типы массивов 5.4; объявители массивов 4.5.3; квалификаторы типа 4.4.3; массивы переменной длины 5.4.5. 5.11.4 Совместимость функций Чтобы две функции были совместимы, они должны специфицировать со- вместимые типы возвращаемых значений. Если оба типа специфицированы в традиционной (непрототипной) форме, то это все, что требуется для совмес- тимости. Композитным типом в этом случае является (традиционный) функ- циональный тип с композитным типом возвращаемого значения. Чтобы две функции, каждая из которых объявлена в прототипной форме, были совместимы, должны выполняться следующие условия: 1. Типы возвращаемых функциями значений должны быть совместимыми. 2. Количество параметров и использование многоточий должны быть со- гласованы. 3. Соответствующие параметры должны иметь совместимые типы. Согласование имен параметров не является обязательным. Композитный тип представляет собой функциональный тип, параметры которого имеют типы, композитные по отношению к типам параметров исходных функций, с тем же использованием многоточий, и возвращаемое значение имеет компо- зитный тип возвращаемых значений. Если же только один из функциональных типов определен в прототипной форме, то для совместимости этих двух типов должны выполняться следую- щие условия: 1. Типы возвращаемых значений должны быть совместимыми. 2. Прототип не должен включать многоточие в конце. 3. Каждый тип Т параметра в прототипе должен быть совместимым с ти- пом, получающимся в результате применения к Т обычных преобразо- ваний аргумента. Композитный тип — это функциональный тип в прототипной форме с ком- позитным типом возвращаемого значения. Ссылки: прототипы функций 9.2; функциональные типы 5.8.
192 Глава 5 5.11.5 Совместимость структур и объединений Каждое появление в тексте программы спецификатора типа, являющегося определением типа структуры или объединения, вводит новый структурный тип или тип объединения, который не совпадает и не является совместимым ни с одним другим таким типом в данном исходном файле. Спецификатор типа, представляющий собой ссылку на структурный, пере- числимый тип или тип объединения, является тем же типом, который был введен в соответствующем определении. Для связи ссылки с определением ис- пользуется тег типа, и в этом смысле он может рассматриваться как имя типа. Пример Типы х, у и и в следующем фрагменте программы различны между собой. Типы и и v совпадают: struct { int a; int b; } х; struct ( int a; int b, } y; struct S ( int a; int b; ) u, struct S v; Ссылки: перечисления 5.5; структуры 5.6; объединения 5.7. 5.11.6 Совместимость указателей Два (одинаковым образом квалифицированных) указательных типа совмес- тимы, если они указывают на совместимые типы. Композитным типом для двух совместимых указательных типов является (одинаковым образом квали- фицированный) указатель на композитный тип. 5.11.7 Совместимость типов между исходными файлами Поскольку определения структурных и перечислимых типов и типов объе- динения порождают новые (несовместимые) типы, для того, чтобы сделать воз- можными перекрестные ссылки в отдельно скомпилированных исходных фай- лах, нужно применить специальный прием. Пример Предположим, в заголовочном файле содержатся следующие объявления: struct S {int а, b,}, extern struct S х; Когда два исходных файла программы включают этот заголовок, подразумевается, что эти файлы ссылаются на одну и ту же переменную х, которая имеет тип struct S. Однако каждый из этих файлов, теоретически, содержит определение своего струк- турного типа, который по чистой случайности был назван struct S в обоих из них. Если только два объявления одного и того же типа не являются совмести- мыми, Standard С указывает, что поведение программы при выполнении не определено, и поэтому:
Типы 193 1. Две структуры или два объединения, определенные в разных исходных файлах, являются совместимыми, если они объявляют одинаковые эле- менты в одном и том же порядке и каждая пара соответствующих эле- ментов имеет совместимые типы (включая длину битовых полей). В Стандарте С99 это правило ужесточено требованием, чтобы теги структур и объединений совпадали (или оба были опущены). 2. Два перечисления, определенные в различных исходных файлах, со- вместимы, если они содержат одни и те же константы перечисления (в произвольном порядке), каждая пара из которых имеет одно и то же значение. В этих случаях композитным типом является тип в текущем исходном файле. Ссылки: перечислимые типы 5,5; структурные типы 5,6; типы объединение 5.7. 5.12 Имена типов и абстрактные объявители При программировании на С могут иметь место две ситуации, когда необхо- димо написать имя типа без объявления объекта этого типа: во-первых, при написании выражений приведения типов и, во-вторых, при применении к типу операции sizeof. В этих случаях используют имя типа, созданное с по- мощью абстрактного объявителя (не путайте «имя типа» с «typedef-именем»). имя-типа : спецификаторы-объявления абстрактный-объявителъопц абстрактный-объявителъ: указатель указатель0щ прямой-абстрактный-объявитель указатель : * список-квалификаторов-типаопц * список-квалификаторов-типаопц указатель список-квалификаторов-типа: ( С89 ) квалификатор-типа список-квалификаторов-типа квалификатор-типа прямой-абстрактный-объявитель : ( абстрактный-объявитель ) прямой-абстрактный объявительОГщ [ константное-выражениеОПц ] прямой-абстрактный-объявительОП1( [ выражение ] (С99) прямой-абстрактный-объявителъопц [ * ] (С99) прямой-абстрактный-объявитель()Пи ( список-типов-параметров1>пц ) Абстрактный объявитель напоминает обыкновенный объявитель, в кото- ром вложенный идентификатор был заменен на пустую строку. Поэтому имя типа выглядит как объявление, из которого был выброшен вложенный иден- тификатор. В синтаксисе спецификаторы-объявления не должны включать спецификаторы класса памяти, список-типов-параметров разрешен только в Standard С, где он используется для объявлений типов в прототипной форме.
194 Глава 5 Старшинство альтернатив абстрактного объявителя такое же, как и в слу- чае обыкновенных объявителей. Пример Имя типа Расшифровка int ТИП int float * укозотель на значение типа float char (*)(int) указатель но функцию, принимающую параметр типа int и возвращающую значение типа char unsigned * [4] int (*(*)())() массив из четырех указателей на значения типа unsigned указатель на функцию, возвращающую укозотель на функцию, возвращающую значение типо int Имена типов всегда появляются внутри скобок, формирующих часть син- таксиса операции приведения типа или операции sizeof. Если спецификатор типа в имени типа является определением типа структуры, объединения или перечисления, то Standard С требует, чтобы реализация определила в том же месте новый тип с включенным тегом типа (при его наличии). Использование этой возможности квалифицируется как плохой стиль программирования (эта возможность отсутствует в C++). Пример Предположим, что тип struct S не определен на момент появления следующих двух операторов (хорошая реализация С на первой строке должна выдать предупреждение). i = sizeof ( struct S {int a,b,}); /* OK, хотя и странно */ j = sizeof( struct S ); /* OK, тип struct S уже определен */ Ссылки: приведения типов 7.5.1; прототипы функций 9.2; операция sizeof 7.5.2 5.13 Совместимость с C++ 5.13.1 Перечислимые типы Отказ от использования перечислимых типов или констант перечислений в качестве целочисленных типов без применения явных приведений типов яв- ляется примером хорошего стиля программирования. В отличие от С, C++ обра- щается с перечислимыми типами как с отличными друг от друга и от целочис- ленных типов, хотя между ними можно проводить взаимные преобразования типов. Язык C++ также разрешает неявные преобразования из перечислимых типов в целочисленные. Пример enum е {blue, red, yellow} e_var; int i_var, i_var = red; /* допустимо как в С, так и в C++ */ e_var =1; /* допустимо в С, но не в C++ */
Типы 195 i_var = (int) red; /* допустимо как в С, так и в C++ */ e_var = (enum е) 3; /* допустимо как в С, так и в C++ */ assert (sizeof (blue) == sizeof (int) ) ; /* всегда выполняется в С; может не выполняться в C++ */ Ссылки: тип перечисления 5.5, 5.13.2 Имена typedef Как и в С, имена typedef могут быть переопределены как объекты во внут- ренних областях видимости. Однако в C++ подобные операции внутри струк- туры или объединения (которые также являются областями видимости) не до- пускаются, если изначальное имя typedef в данном объединении или данной структуре уже использовалось. На практике эта ситуация маловероятна. Пример typedef int INT; struct S { INT i; double INT; /* допустимо в С, но не в C++; */ /* в любом случае - плохая идея */ } Ссылки: переопределение имен typedef 5.10.2. 5.13.3 Совместимость типов Представление о совместимости типов в C++ не совпадает с представлени- ем, имеющимся в С. Для обеспечения более строгой проверки типов, C++ тре- бует наличия одинаковых типов в ситуациях, в которых С потребовал бы на- личия лишь совместимых. В некоторых случаях C++ выдаст диагностическое сообщение, если типы не идентичны. Тем не менее, так как C++ обеспечивает «совместимость размещения» с С, то программа на C++ будет корректно рабо- тать, даже если она содержит нераспознанные применения неидентичных, но совместимых (по Standard С) типов. Ссылки: совместимость типов 5.1 I. 5.14 Упражнения 1. Какой из типов С вы бы выбрали для представления следующих набо- ров значений? Предположим, что на первом месте для вас — переноси- мость между различными компиляторами и компьютерами, а на вто- ром — минимизация используемого пространства. (а) пятизначный почтовый индекс, принятый в США (Ь) телефонный номер, состоящий из трехзначного кода города и семи- значного городского номера (с) значения 0 и 1 (d) значения -1,0, 1
196 Глава 5 (е) какой-либо из символов алфавита, или значение -1 (f) значение баланса на банковском счете в долларах и центах, вплоть до 9,999,999.99 2. Некоторые популярные компьютеры поддерживают расширенный на- бор ASCII, включающий, наряду со стандартными символами ASCII, дополнительные символы, чьи значения приходятся на интервал от 128 до 256. Предположим, что тип char представлен восемью битами. Сле- дующая функция is_up_arrow должна возвращать «true», если введен- ный символ представляет клавишу «стрелка вверх» и «false» в против- ном случае. Будет ли эта функция переносимой на другие компиляторы Standard С, если определение UP_ARROW_KEY имеет соответствую- щее значение для целевого компьютера? Если нет, перепишите ее так, чтобы она была таковой. #define UP_ARROW_KEY 0x86 int is_up_arrow(char c) ( return c == OP_ARROW_KEY; I 3. Если vp имеет тип void *, a ср — тип char *, какие из следующих при- сваиваний верны в Standard С? (a) vp = ср; (Ь) ср = vp; (с) *vp = *ср; (d) *ср = *vp; 4. При условии, что iv имеет тип int [3], a im — тип int [4][5], перепишите следующие выражения без использования оператора индексации: (a) iv[i] (b) im[i][j] 5. Какое целое значение возвращается следующей функцией f? Является ли преобразование к типу int в операторе return необходимым? enum birds (wren, robin=12, blue_jay); int f() ( return (int) blue_jay; } 6. Ниже приведено определение структурного типа и переменной этого типа. Напишите последовательность инструкций, которые присваива- ли бы корректные значения каждому компоненту структуры. Если два компонента структуры накладываются друг на друга, выполните при- своение только одному из накладывающихся компонентов. struct s { int i ; struct T { unsigned s: 1; unsigned e: 7; unsigned m: 24; } F; union U {
Типы 197 double d; char a[6]; int * p; } 0; } x; 7. Создайте два эскиза структуры, определенной в предыдущем задании, используя формат, приведенный в разделах 5.6.5 и 5.7.3. Предполо- жим, что целевой компьютер применяет байтовую адресацию с выделе- нием 8 бит для типа char, 32 бит для указателей типа int и 64 бит для типа double. При создании первого эскиза предположите, что целевой компьютер является «тупоконечным» с битовыми полями, упакован- ными слева направо внутри 32-битовых слов; при создании второго — что тот является «остроконечным» с битовыми полями, упакованными справа налево внутри слов. В обоих случаях предполагается, что ком- пилятор располагает битовые поля так плотно, насколько возможно. (Разновидности архитектур «остроконечная» и «тупоконечная» описа- ны в разделе 6.1.2.) 8. Напишите определение typedef для типа «функция, возвращающая указатель на целое». Напишите объявление переменной, хранящей указатель на такую функцию, и напишите саму функцию, применяя typedef-определение везде, где возможно. 9. Напишите заголовочный файл stdbooLh, чтобы программист мог поль- зоваться булевыми типами в стиле Стандарта С99 в реализации Стан- дарта С89. Имеются ли здесь какие-либо ограничения? 10. Перепишите пример информационного тега из раздела 5.7.3, включая функцию print_widget так, чтобы WIDGET было объединением трех структур, каждая из которых включала бы информационный тег и зна- чение. Является ли ваш результат переносимым на другие реализации, совместимые со Standard С?

Глава 6 Преобразования и представления Особенности реализации большинства языков программирования на опре- деленных компьютерах обычно скрываются. Как правило, программистам С это не доставляет особых неудобств, хотя наиболее привлекательной чертой этого языка является именно возможность опускаться ниже общего уровня, вскрывая особенности представления кода и данных. Эта свобода несет в себе определенную долю риска: некоторые программисты непреднамеренно опус- каются ниже уровня общего представления и вставляют в программы элемен- ты, лишающие их мобильности. Данная глава имеет тройное назначение. Во-первых, мы обсудим некоторые характеристики представления данных и программ и влияние этих характери- стик на программу С. Во-вторых, мы рассмотрим, с определенной мерой основа- тельности, преобразование значений из одного типа в другой, обратив особое внимание на те их особенности, которые обеспечивают мобильность программ. Наконец, мы ознакомимся с «правилами обычных преобразований» С — то есть преобразований, выполняемых автоматически при вычислении выражений. 6.1 Представления В этом разделе мы рассмотрим представления функций и данных, а также влияние этих представлений на программы и реализации языка С. 6.1.1 Единицы памяти и размеры данных Все объекты данных в С, за исключением битовых полей, во время выпол- нения программы располагаются в оперативной памяти, занимая целое число единиц памяти. Каждая единица памяти, в свою очередь, состоит из фиксиро- ванного числа бит-, каждый бит может принимать одно из двух значений, обо- значаемых как 0 и 1. Каждая единица памяти должна иметь уникальный ад- рес и размер, совпадающий с размером данных типа char. Число бит в единице памяти зависит от реализации С, но должно быть достаточным для представ- ления всех символов основного символьного набора. В Standard С единицы па- мяти именуются также байтами, но термином байт обычно обозначается еди- ница памяти размером ровно в восемь бит.
200 Глава 6 По определению, размер объекта данных — это число единиц памяти, кото- рые он занимает. За единицу памяти принимается объем, занимаемый одним символом; следовательно, объем объекта типа char равен 1. Число бит в симво- ле (байте) задается значением CHAR_BIT, определенным в заголовочном фай- ле limits.h. Поскольку все объекты данных одного типа занимают одинаковый объем памяти, мы можем определить размер типа как число единиц памяти, зани- маемых объектом этого типа. Для определения размера объекта или типа мож- но использовать операцию sizeof. Если размер одного типа превышает размер другого, мы говорим, что первый тип «длиннее» или «больше»; соответствен- но, второй — «короче» или «меньше». В Standard С требуется установка опре- деленных минимальных интервалов для целых типов и типов с плавающей точкой, которые определены в заголовочных файлах limits.h и float.h, завися- щих от реализации С. Пример В следующей программе С99 определяются размеры основных типов данных языка С. Чтобы обеспечить совместимость с предыдущими версиями С, модификатор дли- ны z, в выражении %3zd, необходимо заменить символом-модификатором соответ- ствующего типа size_t (тип sizeof): 1 (эль) — для типа long, отсутствие модификато- ра — для int. ((include <stdio.h> int main(void) ( printf("\tType sizes :\nll); printf C'char\tshort\tint\tlong\tllong\t" "float\tdouble\tldouble\n"); printf("%3zd\t%3zd\t%3zd\t%3zd\t%3zd\t" "%3zd\t%3zd\t%3zd\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(long long), sizeof(float), sizeof(double) , sizeof(long double)); return 0; ) Ссылки: Символьные типы 5 1 3, float.h 5.2, limits.h 5 .1.1; минимальные размеры целых ти- пов 5.1.1; операция sizeof 7.5.2; stdio.h (стандартный ввод/вывод) гл. 15. 6.1.2 Порядок следования байт Обозначение в указателях элементов памяти разных размеров определяется схемой адресации, реализованной в компьютере. В модели адресации, наиболее естественной для С, возможна адресация каждого отдельного символа (байта) в компьютерной памяти. Это называется побайтовой адресацией. Адрес элемен- та памяти большего размера — например, содержащего целое число либо число с плавающей десятичной запятой (точкой), — обычно совпадает с адресом его первого символа. Первым считается символ с наименьшим адресом. Однако даже в рамках этой простой модели компьютеры подразделяются на две категории, в зависимости от порядка следования байт — то есть от того, какой из байт некоторой последовательности следует считать «первым». В ар- хитектуре с расположением байт «справа налево» — или «остроконечной», —
Преобразования и представления 201 реализованной в микропроцессорах Intel 80x86 и Pentium, адрес 32-битового целого совпадает с адресом его младшего байта. В отличие от этого, в архитек- туре с расположением байт «слева направо» — или «тупоконечной», — реали- зованной в микропроцессорах семейства Motorola 680x0, адрес 32-битового це- лого совпадает с адресом его старшего байта. Некоторые встраиваемые процес- соры допускают любую из двух конфигураций, в зависимости от требований системы. Пример В обеих архитектурах — Intel (остроконечной) и Motorola (тупоконечной), — приня- та побайтовая адресация с 8-битовыми байтами и 4-байтовыми словами, способны- ми содержать 32-битовые целые значения. На следующей схеме показана последо- вательность слов в каждой архитектуре; каждое слово содержит 32-битовое значе- ние 0x01020304. Как видим, на этом уровне представления обе архитектуры идентичны. Тупоконечная, слева направо 01020304 01020304 А А+4 А+8 Остроконечная, справа налево 01020304 01020304 А А+4 А+8 Ситуация меняется, если взглянуть на содержимое отдельных байт внутри слова. В тупоконечной архитектуре адрес слова совпадает с адресом крайнего левого (стар- шего) байта. Поскольку адрес возрастает слева направо, это полностью соответству- ет способу составления слов, которым мы воспользовались выше. Что же касается остроконечной архитектуры, то здесь адрес слова определяется как адрес крайнего правого (младшего) байта. Это можно представить двояко: либо адрес внутри слова возрастает справа налево, или же байты следуют, друг за другом, в обратном поряд- ке. Оба варианта приведены ниже. Тупоконечная, слева направо Остроконечная, справа налево 01 02 03 04 А А+1 А+2 А+3 А+4 01 02 03 04 А+3 А+2 А+1 А А+7 Остроконечная, справа налево 04 03 02 01 (другое представление) А А+1 А+2 А+3 А+4 Компоненты структурных типов располагаются в порядке возрастания ад- ресов — то есть слева направо или справа налево, в зависимости от порядка следования байт в компьютере. Поскольку битовые поля также упаковывают- ся в порядке следования байт, естественно придерживаться того же направле- ния в нумерации бит. Следовательно, в компьютере с порядком следования байт слева направо, наиболее значимый (крайний слева) бит 32-битового цело- го значения будет иметь номер 0, наименее значимый — номер 31. Что же ка- сается компьютеров с порядком следования байт справа налево, то здесь наи- менее значимый (крайний справа) бит будет- иметь номер 0 и т.д. Учет в про-
202 Глава б грамме определенного порядка следования байт ведет к утере мобильности этой программой. Пример Ниже приведен пример программы, определяющей порядок следования байт в ком- пьютере при помощи немобильной операции объединения. Переменная типа union имеет тот же размер, что и объект типа long, и инициализируется таким образом, что наименее значимый байт содержит 1, остальные — 0. В архитектуре с порядком следования байт справа налево символьный компонент Char в union будет помещен в наименее значимый байт типа Long, в архитектуре с порядком следования байт слева направо — в наиболее значимый. #include <stdio.h> union { long Long; char Char[sizeof(long) ] ; } u; int main (void) { u.Long = 1 ; if (u.Char[Oj == 1) printf("Адресация справа налевоХп"); else if (u.Char[sizeof(long)-1] == 1) printf("Адресация слева направо\п"); else printf("Адресация непонятнаХп"), return 0; ) 6.1.3 Условия выравнивания В некоторых компьютерах данные могут располагаться в памяти по любому адресу, независимо от типа данных, в других на определенные типы данных налагаются условия выравнивания, согласно которым объекты этих типов мо- гут занимать только определенные адреса. К примеру, 32-битовые (4-байто- вые) целые, в компьютерах с побайтовой адресацией, располагаются, как пра- вило, по адресам, кратным четырем. В этом случае, мы говорим о модуле вы- равнивания, равном четырем. Несоблюдение условия выравнивания приводит к ошибке выполнения или непредсказуемым результатам. Даже в условиях отсутствия формальных условий выравнивания их несоблюдение влечет сни- жение эффективности программы, и именно по этой причине выравнивание предусмотрено во многих реализациях языка С. Как правило, программисту С нет надобности заниматься выравниванием данных, так как задача распределения данных в памяти решается компилято- ром. Тем не менее, у программиста есть возможность нарушить условия вы- равнивания при установке указателей на данные различных типов. Эти же на- рушения могут происходить при инициализации указателей. В целом, если условие выравнивания данных типа S не менее строго, чем условие выравнивания данных типа D (то есть модуль выравнивания S не меньше модуля выравнивания D), тогда преобразование указателя типа S в указатель типа D безопасно. Безопасно означает, что полученный в результа- те указатель типа D будет работать должным образом в применении к объекту типа D, а последующее обратное преобразование даст исходный указатель. Из этого следует, что любой указатель может быть безопасно преобразован в тип
Преобразования и представления 203 char * или void * и обратно, поскольку условия выравнивания для этих типов наименее строги. Если условия выравнивания для типа S менее строги, чем для типа D, пре- образование указателя типа S в указатель типа D может привести к одному из двух вариантов непредсказуемого поведения. Во-первых, попытка примене- ния преобразованного указателя для считывания или записи данных соответ- ствующего типа может привести к ошибке с остановом программы. Во-вторых, преобразованный указатель может быть подправлен аппаратными или про- граммными средствами — как правило, смещением к ближайшему предыду- щему правильному адресу. Возврат к исходному указателю в результате обрат- ного преобразования может не получиться. Ссылки: порядок следования байт 6.1.2; функция malloc 16.1, ссылочные типы 5.3. 6.1.4 Размеры указателей В С не определено никаких требований к тому, чтобы целые типы имели размеры, достаточные для размещения в них указателей. Тем не менее, про- граммисты обычно считают, что размер типа long достаточен для этого, и это справедливо для большинства типов компьютеров. В заголовочном файле inttypes.h С99 могут быть определены целые типы intptr_t и uintptr_t, разме- ры которых гарантированно достаточны для размещения указателей. Указатели на функции не превосходят, как правило, по размерам указате- лей типа void *, однако этого нельзя гарантировать во всех случаях, о чем мы поговорим в разделе 6.1.5. В Standard С все преобразования между указателя- ми на функции и на объекты считаются не определенными. Ссылки: типы функций 5.8; преобразовония указателей 6.2.7; ссылочные типы 5.3; размеры типов 6.1.1. 6.1.5 Зависимость от модели адресации В этом разделе мы рассмотрим некоторые особенности конструкции компь- ютерной памяти, которые необходимо учитывать при программировании или разработке реализаций языка С. Модели памяти Некоторые маломощные или специализированные микропроцессоры скон- струированы таким образом, что выбор представления указателей связан с не всегда приемлемой дилеммой «время-объем». Эти процессоры могут работать как с «короткими», так и с «длинными» адресами. Короткие адреса (в преде- лах одного сегмента) быстрее, но ограничены в объеме памяти, на которую могут ссылаться. Большие программы часто требуют применения указателей, способных ссылаться на более чем один сегмент. Чтобы удовлетворить всевозможным требованиям, в компиляторах С, пред- назначенных для таких компьютеров, часто допускается определение про- граммистом модели памяти, устанавливающей соотношение «время-объем» для данной программы. В таблице 6.1 перечислены образцы моделей памяти, поддерживаемых компиляторами, предназначавшимися для ранних ПК. Ва- рианты этих моделей до сих пор действуют в некоторых цифровых процессо- рах сигналов.
204 Глава б Таблица 6.1. Модели памяти в ранних ПК Модель Размер указателя на данные Размер указателя на функцию Описание tiny I 6 бит )6 бит Код, данные и стек размещаются в едином сегменте small )6 )6 Код занимает один сегмент размером 64 Кбайт, данные и стек — другой medium 16- 32 Код может занимать несколько сегментов, данные и стек (вместе) — один compoct 32 )6 Код и стек занимают по одному сегменту размером 64 Кбайт, данные могут занимать несколько сегментов lorge 32 32 Код и данные, вместе, могут занимать несколько сегментов, стек — один huge (32-битовоя линейная) 32 32 То же, что и large, но отдельные элементы данных могут превышать в объеме 64 Кбайт Здесь необходимо обратить внимание на несколько моментов. Код и данные во всех моделях располагаются в отдельных сегментах памяти, и каждый сег- мент обладает собственным адресным пространством. Следовательно, указа- тель на данные и указатель на функцию могут содержать одинаковые значе- ния, не будучи идентичными. В моделях памяти compact и medium указатели на данные и функции отличаются друг от друга размером. Необходима некото- рая осторожность в обращении с нулевыми (NULL) константами-указателями (раздел 5.3.2) типов, предназначенных для ссылок на объекты. Обычные зна- чения NULL в выражениях, содержащих указатели на функции, будут преоб- разованы правильно, указание же этой константы в качестве значения аргу- мента, представляющего указатель на функцию и объявленного без прототи- па, может привести к неправильным результатам. Как правило, эта проблема устраняется в Standard С составлением прототипов, что гарантирует правиль- ное преобразование аргументов. Пример Программист С, незнакомый с сегментной архитектурой, может сделать вывод, что указатель на данные и указатель на функцию могут содержать одно и то же значе- ние только в том случае, когда оба являются нулевыми указателями. А это может привести к неправильному толкованию результатов приведенного ниже теста. В следующем примере, указатели ср и fp ссылаются на разные адресные простран- ства и случайно могут иметь совпадающие ненулевые значения. char * ср; int (*fp) () ; /* Проверка, являются ли оба указателя нулевыми */ if ((int)ср == (int)fp) ... /* Неправильно!! */ Пример В следующем примере из традиционного С, если использовать модель памяти compact или medium, поведение функции f оказывается непредсказуемым, посколь-
Преобразования и представления 205 ку аргументом функции является нулевой указатель на объект, а не на функцию, и потому он имеет другую длину. extern int f(); /* сведения о параметрах отсутствуют */ f(NULL); /* Это неправильно! */ int f ( int (*fp) () ) { ... } Явное определение размеров указателей Альтернативой (или дополнением) использованию определенной модели па- мяти является определение «ближних» или «дальних» указателей на конкрет- ные функции или объекты данных. Такой подход позволяет сводить к мини- муму последствия использования неэффективных моделей памяти — правда, за счет снижения мобильности программы. Пример В некоторых компиляторах С с сегментированной памятью определены новые клю- чевые слова__near (ближний) и_far (дальний), используемые в объявлениях пе- ременных и указателей. Синтаксис предписывает применение этих ключевых слов в месте указания квалификаторов типов в Standard С. Оба ключевых слова начина- ются с двух символов подчеркивания, а это означает, что они зарезервированы для определенных реализаций (раздел 10.1.1). char _near near_char, *ср; int __far (*fp) (), big_array [30000] Дело в том, что дальний указатель занимает 32 бита памяти, ближний — 16. Функ- ции или объекты данных, объявленные как дальние, могут размещаться в удален- ных сегментах, ближние должны быть помещены в «корневой» сегмент. Следует с большой осторожностью подходить к применению данных расширений к указате- лям на функции, объявленные без прототипов. Адресация массивов Вне зависимости от того, сегментирована ли память, некоторые компьюте- ры сконструированы таким образом, что доступ к элементам небольших мас- сивов (как правило, объемом до 64 Кбайт) оказывается быстрее. Реализация массивов больших объемов требует установки специального параметра компи- ляции или применения иного метода. Очень трудные компьютеры Несмотря на успешную реализацию С на множестве типов компьютеров, су- ществуют несколько типов, очень неудобных для реализации этого языка. Ос- новная проблема — естественный для компьютера размер слова, не равный це- лому числу байт. Рассмотрим реальный пример компьютера с 36-битовыми стовами и 7-битовыми текстовыми символами. Каждое слово содержит пять символов плюс один бит. Любые данные несимвольных типов занимают одно или несколько слов. Такая структура памяти очень неудобна для реализации С, поскольку затрудняет отображение любой структуры данных на массив символов. То есть чтобы копировать объект типа Т по адресу А, необходимо, чтобы размер области, начало которой обозначено адресом А, был достаточен для копирования sizeof(T) символов. Альтернативой данному подходу может быть реализация с нестандартным размером символьного типа (например.
206 Глава б 9 или 36 бит), что обеспечит плотную упаковку символов в слове. Такой под- ход может отрицательно сказаться на характеристиках компилированных программ. Подобная проблема возникает в отношении компьютеров с «пословной ад- ресацией» — то есть с единицей памяти, превосходящей размер символа. На этих компьютерах возможно определение адреса особого вида — «байтового указателя», — для ссылки на символ внутри слова. Размер такого указателя, при его наличии, может значительно превосходить размеры указателей на данные несимвольных типов. Иначе оба вида указателей могут иметь одинако- вый размер, но тогда некоторые биты будут использоваться только в специаль- ных указателях и обнуляться в остальных. При планировании реализации придется принимать решение: допускать ли непроизводительное использова- ние памяти, применяя указатели одного размера для всех типов данных, опре- делить ли увеличенный размер только для указателей типа char * (в Standard С — также void *), или же представлять символьный тип словом. Разный раз- мер указателей требует внимательного отношения к их преобразованиям. Ссылки: регулярные типы (массивы) 5.4; символьные типы 5.1.3; преобразования аргументов функций 6.3.5; прототипы функций 9.2; ссылочные типы (указатели) 5.3; единицы памяти 6.1.1. 6.1.6 Представления типов Представление значения определенного типа — это некоторая последова- тельность бит в области памяти, содержащей объект этого типа. Эта последо- вательность определяет некоторое значение, отличное от остальных значений этого же типа. Представление не должно обязательно задействовать все биты объекта; часть битов может составлять «заполнение» с неопределенным значе- нием. Например, тип данных short может составлять всего 16 битов, но содер- жаться в 32-битовом слове. Биты заполнения учитываются при определении размера в операции sizeof. Более точными характеристиками типов, содержа- щих заполнения, являются интервал значений (range) и точность (precision). Возможны ситуации, когда значение определенного типа может иметь бо- лее одного представления. К примеру, в целом типе возможны представления +0 И -о. в каждой реализации допускается выбор любого из эквивалентных значений. Представления одного типа могут быть несовместимы с представлениями другого, даже если оба типа имеют одинаковый размер. Например, если обра- титься к значению типа long так, как если бы это было значение типа float, ре- зультат будет непредсказуемым — возможен даже останов программы. Если воспользоваться терминологией С99, эффективный тип (effective type) объекта — это его представление в данный момент. Как правило, объект дан- ных (например, переменная) объявляется с определенным типом, который и бу- дет его эффективным типом — здесь не возникает никаких проблем. Однако иногда — например, при использовании функции malloc, объекты могут созда- ваться без объявления типа. В этом случае, эффективным типом объекта будет тип 1-значения, которое использовалось в прошлый раз для записи значения в этот объект. Все последующие обращения к этому объекту должны происхо- дить соответственно типу, совместимому с эффективным типом (или квалифи- цированным вариантом совместимого типа) — иначе результат будет непредска- зуемым. Копирование значения в объект, тип которого не объявлен (например, при использовании функции withmemcpy или ссылке на внутренние значения
Преобразования и представления 207 char некоторого объекта) может привести к присвоению конечному объекту эф- фективного типа объекта исходного. Ссылки: 1-значение 7.1; malloc 16.1; memcpy 14.3, квалифицированный тип 4.4. 6.2 Преобразования В языке С предусматривается возможность преобразования значений одно- го типа в значения другого в определенных условиях: • Для явного преобразования значения в другой тип можно использовать выражение приведения. • При подготовке к выполнению арифметической или логической опера- ции, возможно неявное преобразование операнда в другой тип. • Объект некоторого типа может быть присвоен области памяти (1-значе- нию) другого типа, и это повлечет неявное преобразование типа. • Фактический аргумент функции может быть преобразован, неявно, в другой тип перед ее вызовом. • Перед возвратом функцией значения оно может быть преобразовано, не- явно, в другой тип. Любой объект может быть преобразован лишь в ограниченное число типов. Более того, множество преобразований, возможных, к примеру, в операции присваивания, не совпадает с множествами преобразований, допустимых в других условиях. Мы обсудим множества преобразований в последующих разделах, после чего рассмотрим применение этих преобразований в ситуациях, перечислен- ных выше. 6.2.1 Изменение представлений Преобразование значения из одного типа в другой может включать измене- ние представления. Например, изменение размеров происходит, если исход- ный и конечный типы имеют разные размеры. В случае же преобразования це- лого типа в тип с плавающей точкой, изменение представления происходит даже при совпадении размеров обоих типов. Изменение представления не про- исходит при преобразовании, к примеру, из типа int в тип unsigned int. Иногда изменение представления несложно и ограничивается отбрасывани- ем избыточных битов или дополнением битов, заполнением их нулями. Воз- можны и более сложные изменения — например, при преобразовании из цело- го типа в тип с плавающей точкой. При обсуждении в последующих разделах каждого из преобразований типов мы будем рассматривать также и все изме- нения представлений, которые могут понадобиться при этих преобразованиях. 6.2.2 Тривиальные преобразования Любое значение можно преобразовать в тип, совпадающий (или совмести- мый) с его исходным типом — в этом случае, преобразование происходит без изменения представления. О совпадении и совместимости типов см. в разделе 5.11.
208 Глава 6 В большинстве реализаций, преобразование типов struct или union в самих себя невозможно ввиду запрета преобразования в эти типы. 6.2.3 Преобразование в целые типы Возможно преобразование скалярных типов (арифметических и ссылоч- ных) в целые. Преобразование в булев тип В С99 преобразования из типа или в тип _Воо1 несколько отличаются от преобразований других целых типов. При преобразовании арифметического значения в тип _Воо1 преобразованное значение равно нулю, если равно нулю исходное значение, и единице — в остальных случаях. В случае преобразова- ния ссылочного типа в тип _Воо1, нулевые указатели преобразуются в 0, ос- тальные — в 1. При преобразовании из типа _Воо1 в арифметический тип, ре- зультат всегда будет 0 или 1, в зависимости от значения исходного типа. В ос- тальной части этого раздела мы будем предполагать, если не указано иное, что тип _Воо1 не относится к целым типам. Преобразование из целых типов Если исключить из рассмотрения тип _Воо1, общим требованием для преоб- разования из одного целого типа в другой будет равенство исходного и конеч- ного математических значений (если оно возможно). Например, если целое значение без знака равно 15, то после преобразования в целый тип со знаком, оно также должно быть равно 15. Если сохранение исходного значения в преобразованном типе невозможно, принимается одно из двух возможных решений. Если произошло преобразова- ние в тип со знаком, полученное значение считается переполненным и техни- чески неопределенным. В случае преобразования в беззнаковый тип, за конеч- ное должно быть принято уникальное значение, равное (конгруэнтное) mod 2" по отношению к исходному значению, где п — число битов, используемых для представления конечного типа. Если целые со знаком представляются в форме дополнения до двух, преобразования из целых типов со знаком в беззнаковые целые (одинаковых размеров) и обратно не требуют изменения представления. При ином представлении целых со знаком — например, дополнении до одного или «знак-величина», — изменение представления необходимо. При преобразовании беззнакового целого в целое со знаком того же размера, преобразованное значение считается переполненным, если исходное значение слишком велико для представления со знаком (то есть если старший значащий бит беззнакового числа равен 1). Программисты, как правило, полагаются на преобразования, выполняемые без проблем и не требующие изменения пред- ставления для образования отрицательных значений. Если конечный тип длиннее исходного, значение, преобразуемое в этот тип, может оказаться непредставимым только в том случае, если в более длинный беззнаковый тип преобразуется отрицательное значение. В этом случае, преоб- разование должно выполняться таким образом, как если бы исходное значе- ние преобразовывалось сначала в тип со знаком, имеющий длину конечного типа, и уже затем — в конечный тип.
Преобразования и представления 209 Пример Поскольку константное выражение —1 имеет тип int, справедливо следующее утвер- ждение: ((unsigned long) -1) = ((unsigned long) ((long) -1))) Если конечный тип короче исходного и оба типа — беззнаковые, преобразо- вание может быть выполнено обычным отбрасыванием соответствующего чис- ла наиболее значимых битов исходного значения. Конфигурация битов конеч- ного представления будет совпадать с конфигурацией наименее значимых п битов исходного представления, где п — число битов в конечном типе. Это же правило отбрасывания битов применяется при преобразовании целых со зна- ком, в представлении дополнения до двух, в более короткий беззнаковый тип. Кроме этого, правило отбрасывания битов приемлемо вместе с несколькими другими методами к преобразованию целых значений (со знаком или без) в бо- лее короткие типы со знаком в представлении дополнения до двух. Следует иметь в виду, что это правило не предусматривает сохранение знака при пере- полнении; впрочем, само состояние переполнения, в любом случае, неопреде- ленно. Если целые со знаком представлены не в форме дополнения до двух, преобразование оказывается более сложным. Этот вариант не является обяза- тельным для представления целых со знаком в языке С, но, несомненно, ему отдается предпочтение перед другими представлениями. В случае преобразования в тип _Воо1, любое ненулевое исходное значение преобразуется в 1; исходный нуль преобразуется в нуль. Преобразование из типов с плавающей точкой В случае преобразования значения с плавающей точкой в целое, преобразо- ванное значение должно быть (по возможности) равно исходному. Если дроб- ная часть значения с плавающей точкой не равна нулю, она должна быть от- брошена — то есть предполагается усечение значения с плавающей точкой. Результат преобразования оказывается неопределенным, если значение с плавающей точкой не может быть представлено в новом типе даже прибли- женно — например, из-за того, что оно слишком велико, либо вследствие пре- образования отрицательного значения в беззнаковое целое. Определения пере- полнения и потери значимости могут зависеть от реализации. Преобразование из ссылочных типов Если исходное значение имеет ссылочный тип, а конечный тип не равен _Воо1, указатель преобразуется как беззнаковое целое, размер которого равен размеру указателя. В этом случае происходит преобразование беззнакового це- лого по правилам, перечисленным выше. Если нулевой указатель представля- ется ненулевым значением, результату явным образом присваивается нулевое значение. Как правило, программисты С полагают, что указатель, в любом случае, преобразуется в тип long и обратно без потери информации. Это почти всегда верно, однако не является требованием определения языка. В С99, к примеру, типы intptr_t и uintptr_t, если они определены в файле stdint.h — это целые типы, соответственно, со знаком и без знака, которые могут содержать указа- тели. Проблема, однако, в том, что в некоторых компьютерах указатели могут представляться значениями, которые длиннее любого целого типа.
210 Глава б Ссылки: тип _Bool 5,1.5; символьные типы 5.1.3; типы с плавающей точкой 5 2; целые типы 5.1; intptr_t 21.5; переполнение 7.2.2; ссылочные типы 5.3; uintptr_t 21.5; stdint.h гл. 21; беззнаковые типы 5.1.2; тип void * 5.3.1. 6.2.4 Преобразование в типы с плавающей точкой Преобразование в типы с плавающей точкой возможно только из арифмети- ческих типов. В случае преобразования из типа float в тип double или из типа double в long double, конечное значение должно быть равно исходному. Это можно рассматривать как условие выбора представлений для типов с плавающей точ- кой. В случае преобразования из типа double в тип float или из типа long double в тип double — при условии, что исходное значение входит в интервал значе- ний, которые могут быть представлены в новом типе, — конечное значение должно быть равно одному из двух значений с плавающей точкой, ближайших к исходному значению. Выбор округления в большую или меньшую сторону зависит от реализации. Если исходное значение не входит в интервал значений, которые могут быть представлены в новом типе — например, в случае значения типа double, которое слишком велико или слишком мало для представления в типе flo- at, — конечное значение оказывается неопределенным, как в случае перепол- нения или потери значимости. При преобразовании из целых типов в типы с плавающей точкой, если це- лое значение точно представляется в конечном типе, конечное значение долж- но быть равно исходному. Если целое значение не может быть представлено точно, но входит в интервал значений, представляемых типом с плавающей точкой, конечное значение должно быть равно одному из двух значений с пла- вающей точкой, ближайших к исходному значению. Если целое значение не входит в интервал значений, представляемых типом с плавающей точкой, ко- нечное значение оказывается неопределенным. Комплексные типы с плавающей точкой (С99) При преобразовании из одного комплексного типа в другой, действитель- ная и мнимая части преобразуются по правилам преобразования обычных (действительных) типов с плавающей точкой. При преобразовании действительного типа (целого или с плавающей точкой) в комплексный, мнимая часть конечного значения устанавливается равной нулю (+0.0, в случае двойного представления нуля). Преобразование значения действительного типа в действительную часть комплексного типа подчиняется обычным правилам преобразования в (действительные) типы с плавающей точкой. Если же преобразование происходит из комплексного типа в действитель- ный (целый или с плавающей точкой), мнимая часть отбрасывается, а дейст- вительная преобразуется по обычным правилам преобразования из (действи- тельных) типов с плавающей точкой. Мнимые типы —Imaginary, при их наличии — это комплексные типы, в ко- торых действительная часть всегда равна нулю. При преобразовании из дейст- вительного типа во мнимый — так же, как и при обратном преобразовании, — конечное значение всегда равно нулю, — то есть единственному значению, об- щему для обоих типов. При преобразовании из типа —Complex в тип _Imagi-
Преобразования и представления 211 nary, отбрасывается действительная часть. При преобразовании из типа „Ima- ginary в тип „Complex, действительная часть конечного значения устанавли- вается равной нулю. Ссылки: комплексные типы 5.2.1; типы с плавающей точкой 5.2; целые типы 5.1; переполне- ние 7.2.2. 6.2.5 Преобразование в структуры и объединения Преобразования с участием структурных типов или объединений не допус- каются. Ссылки: структурные типы 5.6; объединения 5.7. 6.2.6 Преобразование в перечислимые типы Здесь правила те же, что и при преобразованиях в целые типы. Некоторые допустимые преобразования — например, между перечислимым типом и ти- пом с плавающей точкой, — могут рассматриваться как свидетельства плохого стиля программирования. Ссылки: перечислимые типы 5.5. 6.2.7 Преобразование в ссылочные типы Как правило, в ссылочные типы преобразуются указатели и целые значе- ния. Существуют, однако, особые обстоятельства, когда в указатель преобра- зуется массив или функция. Нулевой указатель любого типа может быть преобразован в указатель любо- го другого типа с сохранением признаков нулевого указателя. Это может по- требовать изменения представления. Значение типа «указатель на S» может быть преобразовано в тип «указа- тель на D», где S и D — любые типы. В Standard С, преобразование указателей на объекты в указатели на функции и обратно невозможно. Тем не менее, в конкретной реализации возможно воздействие на конечный указатель изме- нением представления или наложением условий выравнивания. Целая константа 0 или любая целая константа, равная нулю, так же как и любая подобная константа, приведенная к типу void *, являются константа- ми нулевых указателей и могут быть преобразованы в любые ссылочные типы. Результат такого преобразования — нулевой указатель, отличный от любого другого указателя правильного формата. Нулевые указатели различных типов могут иметь разные внутренние представления и не должны обязательно со- стоять только из нулевых битов. Возможны преобразования целых значений, отличных от константы О, в ссылочные типы, но результаты таких преобразований не будут мобильными (переносимыми в другие реализации). Однако это условие позволяет рассмат- ривать указатель как целое без знака (того же размера); таким образом, вместо преобразования в ссылочный тип можно использовать стандартное преобразо- вание в целый. Преобразование из типа «массив типа Т» в тип «указатель на тип Т» произ- водится подстановкой указателя на первый элемент массива. Это выполняется как часть обычных унарных преобразований (раздел 6.3.3).
212 Глава б Преобразование из типа «функция, возвращающая значение типа Т» (то есть именующее выражение для функции) в тип «указатель на функцию, воз- вращающую значение типа Т» производится подстановкой указателя на эту функцию. Это выполняется как часть обычных унарных преобразований (раз- дел 6.3.3). Ссылки: условия выравнивания 6,1.3; регулярные типы (массивы) 5.4; вызовы функций 7.4.3; именующее выражение для функции 7.1; целые типы 5.1; ссылочные типы (указатели) 5.3; опе- рация sizeof 7.5.2; обычные унарные преобразования 6.3.3. 6.2.8 Преобразование в массивы и функции Преобразование в массивы или функции не допускается. Пример Тем более, не допускается преобразование из массива в массив или из функции в функцию: extern int f(>; double d; d= (( double () ) f)() ; /* Неправильно! */ d = (double) f(); I* Правильно */ d= (* (double (*)()) f) () ; /* Допустимо, но результаты будут непредсказуемыми */ Адрес функции f в последнем операторе преобразуется в указатель на функцию, воз- вращающую значение типа double; затем происходит разыменование ссылки и вы- зов функции. Данная конструкция правильна, но значение, записанное в d, будет бессмысленным, если функция f действительно не будет переопределена как возвра- щающая значение типа double (в начале примера она объявлена как int). 6.2.9 Преобразование в тип void Значение любого типа может быть преобразовано в тип void. Конечно, такое значение нельзя использовать каким бы то ни было образом. Подобное преоб- разование возможно только в условиях, когда значение выражения предстоит удалить — например, в операторном выражении. Пример Наиболее распространенное применение приведения выражения к типу void связа- но с необходимостью игнорирования результата вызова функции. Например, printf вызывается для вывода информации в стандартный выходной поток, но возвращае- мый ею код ошибки часто игнорируется. Для этого вовсе необязательно приводить результат к типу void, но если все же сделать так, будет очевидно, что это не резуль- тат небрежности программиста. (void) printf("До свидания.\п" ) , Ссылки: выражения с отбрасываемыми значениями 7.13; операторные выражения 8 2; тип void 5.9.
Преобразования и представления 213 6.3 Обычные преобразования 6.3.1 Приведение типов Все преобразования, рассмотренные выше, могут быть выполнены явно и без ошибок при помощи приведения типа. Разрешенные варианты приведе- ния типов перечислены в таблице 6.2. Следует иметь в виду, что в Standard С указатель на функцию не может быть приведен к типу указателя на объект данных, как невозможно и обратное преобразование. Тем не менее, такое же преобразование через подходящий целый тип оказывается возможным. Дан- ное ограничение отражает возможность разного представления указателей на объект данных и на функцию. Таблица 6.2. Допустимые приведения типов Приведение в тип Приведение из типа любой арифметический тип любой арифметический тип любой целый тип любой ССЫЛОЧНЫЙ тип указатель но (объект) Т или (void *) о) любой целый тип b) (void *) с) указатель на (объект) Q, для любого Q d) указатель но (функцию) Q, для любого Q1 указатель но (функцию) Т а) любой целый тип Ь) указатель на (функцию) Q, для любого Q с) указатель но (объект) Q для любого Q1 структура или объединение нет, недопустимое приведение массив ТИПО Т или функция, возврощающоя значение типо Т нет, недопустимое приведение void любой тип 1 Недопустимо в Standard С, Наличие или отсутствие квалификаторов не влияет на приведение типов; для устранения их влияния можно использовать некоторые преобразования. Более серьезным ограничениям подвергаются преобразования в операциях присваивания. В Standard С, преобразование указателя на объект в тип void * и обратно га- рантирует восстановление исходного значения. По всей вероятности, это спра- ведливо также и для преобразований через тип char * в других реализациях С. Ссылки: преобразования в операциях присваивания 6.3.2; приведение типов 7.5.1; квалифи- каторы типов 4.4.3; void * 5.3.1. 6.3.2 Преобразования в операциях присваивания Типы выражений в левой и правой частях оператора присваивания должны быть одинаковы. Если это не так, будет предпринята попытка преобразовать значение правой части к типу левой. Допустимые преобразования — подмно- жество преобразований приведения типа, — перечислены в таблице 6.3. Если не указано иное, наличие квалификаторов типов ISO (International Organi-
214 Глава 6 zation for Standardization — Международная организация по стандартизации) не влияет на правильность преобразования, однако в левой части оператора присваивания нельзя использовать 1-значение с квалификатором const. Таблица 6.3. Преобразования, допустимые в операторе присваивания Тип левой части Допустимые типы правой части любой арифметический тип любой арифметический тип JBool (С99) любой ССЫЛОЧНЫЙ ТИП структура или объединение1 совместимые структура или объединение (void *)2 а) константо 0 Ь) указатель на (объект) Tj3 с) (void *) указатель на (объект) Tj2’3 а) константо 0 Ь) указатель на Т2, где Tj и Т2 совместимы с) (void *) указатель на (функцию) F|2 а) константа 0 Ь) указатель на F2, где F, и Fj совместимы 1 В некоторых прежних компиляторох С структуры и объединения не поддерживаются. 2 Тип переменной, но которую ссылается указатель в левой части оператора присваивания, должен иметь все те же квалификаторы типа, что и тип переменной, но которую ссылается указатель в правой мости. 3 Т) может быть неполным типом, если другой указатель имеет тип void * (Standard С). Любое иное преобразование без явного приведения типа в реализациях, со- ответствующих спецификациям ISO, невозможно, однако в традиционных компиляторах С смешение ссылочных типов в операторах присваивания до- пускается почти всегда, а часто допускаются и все типы, которые допустимы в операции приведения типов. Правила, регулирующие присваивание значений указателей, налагают оп- ределенные условия на квалификаторы типов, поскольку сама операция при- сваивания может использоваться для преодоления условий, налагаемых ква- лификаторами. Присваивание значения указателя переменной типа _Воо1 де- лает эту переменную равной 0, если указатель нулевой, и 1 — в остальных случаях. Ссылки: оператор присваивания 7.9 1, приведение типа 63 1: совместимые типы 5.11 6.3.3 Обычные унарные преобразования Обычные унарные (с одним операндом) преобразования определяют, преоб- разуется ли единственный операнд перед выполнением операции и как он пре- образуется. Назначение унарных преобразований — уменьшение числа ариф- метических типов, задействуемых в операциях. Эти преобразования выполня- ются автоматически над операндами унарных операций !, —, +, - и *. а также над каждым из операндов в отдельности в бинарных операциях « и ». Ранг преобразования Ввиду появления дополнительных стандартных целых типов в С99, а также возможности возникновения в некоторых реализациях новых типов, точное (да, к тому же, еще и простое) описание этих неявных преобразований оказы- вается довольно непростой задачей. Чтобы упростить эти описания, в Стандар-
Преобразования и представления 215 те С99 было определено понятие ранга преобразования (conversion rank). Мы также им воспользуемся. В отношении С89 типы long long, _Воо1, а также расширения целых типов следует просто игнорировать. В отношении традици- онного С см. окончание данного раздела. Ранг преобразования — это численное значение, присваиваемое каждому целому типу для определения порядка его преобразования. В таблице 6.4 при- ведены возможные назначения рангов преобразования стандартным целым ти- пам. Перечислимые типы не включены в таблицу; их ранги совпадают с целы- ми типами, на основе которых они образованы. Таблица 6.4. Ранги преобразования Ранг Типы, обладающие этим рангом 60 long long int, unsigned long long int (C99) 50 long int, unsigned long int 40 int, unsigned int 30 short, unsigned short 20 char, unsigned char, signed char 10 JBool В данной схеме важны не абсолютные, а только относительные значения рангов, что позволяет установить приоритеты одних типов над другими. Одна- ко значения выбраны такими, чтобы в будущих реализациях можно было оп- ределить новые ранги между уже существующими. Дополнительные ранги должны подчиняться следующим правилам: значение нового ранга должно быть ниже значения ранга любого стандартного типа той же точности; ника- кие два отдельных целых типа со знаком не могут иметь одинаковый ранг; лю- бой беззнаковый тип должен иметь тот же ранг, что и тип со знаком с тем же представлением. Обычные унарные преобразования, соответствующие приведенным выше, рангам, перечислены в таблице 6.5. Выполняется первое подходящее преобра- зование; если таковых не оказывается, никакого преобразования не происхо- дит. В применении к целым значениям унарное преобразование именуются продвижением целых (integer promotion). Преобразование массивов и функций в некоторых случаях запрещается (см. раздел 6.2). Пример Если S — переменная типа unsigned short в Standard С со значением 1, тогда: если интервал значений типа short меньше интервала значений типа int, выражение (—S) имеет тип int и значение -1; если интервал значений типа short равен интерва- лу значений типа int, выражение (-S) имеет тип unsigned и большое положитель- ное значение. Дело в том, что в первом случае S продвигается в тип int (до выполне- ния унарной операции изменения знака), во втором — в тип unsigned. Если битовое поле имеет тип int, signed int или unsigned int, считается, что ранг этого поля ниже ранга типа int. В традиционных реализациях С эти преобразования выполнялись различ- ным образом. Во-первых, все беззнаковые типы с более низким рангом преоб- разовывались в тип unsigned int. Этим сохранялась «знаковость» операнда
216 Глава 6 (возможно, и его значения). При программировании в Standard С, следует очень внимательно относиться к преобразованиям типов, поскольку знако- вость результата продвижения зависит от реализации и может влиять на дру- гие части выражения. Во-вторых, тип float преобразовывался в тип double, благодаря чему уменьшалось число используемых библиотечных функций, выполняющих операции с плавающей точкой, за счет некоторого возможного понижения точности. Теперь от этой практики отказались, но в некоторых реализациях она, возможно, сохранилась. Таблица 6.5. Обычные унарные преобразования (выполняется первое подходящее) Если операнд имеет тип Standard С — преобразование в тип Традиционный С — преобразование в тип float (без преобразования) double Массив типа Т Указатель на тип Т (как в Standard С) Функция, возвращающая значение типо Т Укозатель на функцию, возвращающую значение типа Т (как в Standord С) Целый тип рангом не ниже int1 (без преобразования) (как в Standard С) Целый тип рангам ниже int int (как в Standard С) Беззнаковый тип ронгом ниже int, все его значения могут быть представлены в типе int int unsigned int Беззнаковый тип рангом ниже int; все его значения не могут быть прелставлены в типе int unsigned int (как в Standard С) 1 Предполагается, что битовые поля типов int, signed int и unsigned int имеют ранг ниже int, а это означает, что преобразование типов этих попей зависит от того, все ли их значения могут быть предстовпены в типе int. Преобразование массивов и функций Согласно правилам обычных унарных преобразований, значение регуляр- ного типа (массива) преобразуется в указатель на первый элемент этого масси- ва, за исключением следующих случаев: 1. массив является аргументом (операндом) операции sizeof или &; 2. символьный массив инициализируется строковым литералом; 3. массив типа wchar_t инициализируется широкосимвольным строко- вым литералом. В С99 эти преобразования происходят с любым значением регулярного типа. В версиях, предшествовавших С99, данное преобразование выполнялось только на 1-значениях регулярных типов. Пример char a [] = "abed"; /* Без преобразования */ char *b = "abed"; /* Преобразование массива в указатель */ int i = sizeof(a); /* Без преобразования; размер всего массива */ b = a + 1, /* Преобразование массива в указатель. */
Преобразования и представления 217 Согласно правилам обычных унарных преобразований, именующее выра- жение для функции преобразуется в указатель на эту функцию, за исключени- ем случаев, когда это именующее выражение является операндом операции sizeof или & (кроме этого, применение функции в качестве операнда операции sizeof недопустимо). Пример extern int f () ; int (*fp) () ; int i; fp = f; /* Правильно, f преобразуется в Sf */ fp = Sf; /* Правильно, неявное преобразование запрещено */ i = sizeof(fp); /* Правильно, результат — размер указателя */ i = sizeof(f); /* Неправильно */ Ссылки: операция побитового НЕ ~ 7.5.5; расширения целых типов 5.1.4; вызовы функций 7.4.3; именующее выражение для функции 7.1; операция разыменования * 7.5.7; инициализа- торы 4.6; операция логического НЕ ! 7.5.4; 1-значение 7.1; операции сдвига « и » 7.6.3; sizeof 7.5.2; унарная операция изменения знака - 7.5.3; строки широких символов 2.7.4. 6.3.4 Обычные бинарные преобразования Бинарные операции выполняются над двумя значениями, преобразуемыми в общий тип — как правило, тип результата. Эти преобразования выполняют- ся над операндами большинства бинарных операций, а также над вторым и третьим операндами условных выражений. Обычные унарные и обычные би- нарные преобразования объединяет общее название обычные арифметические преобразования. В процессе обычных бинарных преобразований каждый из операндов сна- чала подвергается обычному унарному преобразованию, в результате чего уве- личиваются размеры коротких значений, а массивы и функции преобразуются в указатели. Если какой-то из операндов не принадлежит к арифметическому типу, преобразования на этом завершаются, иначе оба операнда подвергаются первому применимому преобразованию из таблицы 6.6. Таблица не действи- тельна для комплексных типов, преобразование которых мы рассмотрим от- дельно. Пример Правила, действующие в Standard С, отличаются от традиционных правил, если один операнд имеет тип long, другой — тип unsigned (причем, размер типа long, оп- ределенно, превышает размер типа unsigned). Ниже приведена программа, опреде- ляющая преобразование, которое выполняется: unsigned int UI = -1; long int LI = 0; int main () { if (UI < LI) printf (”long+unsigned=long\n'') ; else printf("long+unsigned==unsigned\n"); return 0; 1
218 Глава б Таблица 6.6. Обычные бинарные преобразования (выполняется первое подходящее) Если один операнд имеет тип1 А другой — тип1 В Stondord С оба преобразуются в В традиционном С оба преобразуются в long double любой действительный тип long double неприменимо double любой действительный тип double (как в Standard С) float любой действительный тип float double пюбой пюбой беззнаковый тип беззнаковый тип бопее (как в Standard С) беззнаковый тип высокого ранга любой ТИП любой тип со знаком тип со знаком бопее (как в Standard С) со знаком высокого ранга любой тип со знаком того же или беззнаковый тип (кок в Standard С) беззнаковый тип более низкого ранга любой тип со знаком более тип со знаком беззнаковый вариант беззнаковый тип высокого ранго, способный типо со знаком представить все значения беззнакового типа любой тип со знаком более беззнаковый вариант (как в Standard С) беззнаковый тип высокого ранга, который не типа со знаком может представить все значения беззнакового типа любой другой тип^ любой другой тип (без преобразования) (как в Standord С) 1 Данные правила применимы в лредпопожении, что каждый из операндов уже подвергнут обычному унарному преобразованию. 2 Комплексные операнды рассматриваются ниже. Комплексные типы и обычные бинарные преобразования При выполнении обычных бинарных преобразований в С99 необходимо принимать во внимание комплексные типы. В операциях со смешанными (комплексными и действительными) операндами операнд действительного типа не преобразуется в комплексный тип (это привело бы к снижению скоро- сти вычислений). Вместо этого, оба операнда преобразуются в эквивалентные типы достаточной точности, затем операция выполняется так, как если бы действительный операнд был преобразован в комплексный тип. Конечно, воз- можны реализации, в которых подразумеваемые преобразования выполняют- ся реально. Тип результата совпадает с типом комплексного операнда после всех преобразований. Если оба операнда — комплексные, более короткий операнд преобразуется в тип более длинного, и в этом же типе возвращается результат. Это аналогич- но преобразованию двух операндов с плавающей точкой. Если один из операндов комплексный, а другой — целый, последний преобра- зуется в действительный тип с плавающей точкой, соответствующий комплекс- ному типу. Например, в случае комплексного операнда типа f!oat_Complex, це- лый операнд преобразуется в тип float. Результат имеет комплексный тип. Если один операнд имеет комплексный тип, другой — действительный с плавающей точкой, операнд меньшей точности преобразуется до точности другого операнда (внутри комплексного или действительного типа). Напри- мер, в сочетании операндов типов float и double_Complex, операнд типа float
Преобразования и представления 219 преобразуется в тип double. В сочетании long double и double_Complex, опе- ранд double_Complex продвигается в long double_Complex. 6.3.5 Преобразование аргументов функций Если выражение представляет аргумент вызываемой функции, и вызов не управляется прототипом или аргумент относится к части, указанной в прото- типе многоточием, значение выражения преобразуется до передачи функции. Данное преобразование аргументов функций по умолчанию совпадает с обыч- ным унарным преобразованием, однако здесь аргументы типа float всегда про- двигаются в тип double — даже в Standard С. Если вызов функции управляется прототипом, ее аргументы не подверга- ются (обязательно) обычным продвижениям целых, а аргументы типа float не подвергаются (обязательно) обычному продвижению в тип double. Эти преоб- разования могут выполняться или не выполняться в тех или иных реализаци- ях, но изложенные правила помогают оптимизировать последовательность вы- зова. Преобразование массивов и функций в указатели происходит всегда. Если формальный параметр в прототипе С99 имеет список L-квалификато- ров типа в квадратных скобках [], тогда фактический аргумент преобразуется в указатель на элемент типа, определяемого этими квалификаторами. Это об- суждается подробнее в разделе 9.3. Преобразование из float в double в предыдущих версиях традиционного С, как и в Standard С, помогали ограничивать число вызовов библиотечных функций за счет того, что оставались значения только одного типа. В С99 оп- ределен полный набор математических функций типов float и long double, а также double. Ссылки: список квалификаторов массива 4.5.3; вызовы функций 7.4.3; типы математических функций глава 17; прототипы 9.2; обычные унарные преобразования 6.3,3. 6.3.6 Другие преобразования в функциях Объявленные типы формальных параметров и возвращаемого значения функции подвергаются, наряду с преобразованиями, небольшим корректиров- кам. Эти корректировки рассматриваются в разделе 9.4. 6.4 Совместимость с C++ 6.4.1 Преобразования в операторах присваивания В C++, преобразование указателя типа void в любой другой ссылочный тип требует операции приведения типа. В С приведение типа в операторе присваи- вания допускается, но не является обязательным. Пример Функция malloc возвращает указатель типа void * на распределенную область па- мяти. #include <stdlib.h> char * ср; const int SIZE = 10 * sizeof(char),
220 Глава 6 ср = malloc(SIZE); /* Правильно в С, но не в C++ */ ср = (char *) malloc(SIZE); /* Правильно в С и C++ */ Кроме этого, только указатель на неуточненный (не const и не volatile) объ- ект может быть преобразован в тип void * без приведения типа. Пример char * ср; const char * const_cp; void * vp; vp = ср; /* Правильно в С и C++ */ vp = const_cp; /* Правильно в С, но не в C++ */ vp = (void *) const_cp, /* Правильно в С и C++ */ Ссылки: преобразования в операторах присваивания 6.3.2. 6.5 Упражнения 1. В следующей таблице приведены пары исходных и конечных типов для операции приведения типов. Какие из этих преобразований допустимы в Standard С? Какие — в традиционном? (Для традиционного С, заме- ните void на char.) Конечный тип Исходный тип (a) char int (b) char * int * (с) int (*f) () int * (d) double * int (e) void * int (*f)() (f) int * t * (где: typedef int t) 2. Какие из пар типов, перечисленных в таблице упражнения 1, допусти- мы в операторах присваивания в Standard С? В традиционном? (Конеч- ные типы располагаются в левом столбце, исходные — в правом.) 3. Каков тип результата, если применить правила обычных бинарных преобразований традиционного С к следующим парам типов? В каких случаях результат будет иметь другой тип в Standard С? (a) char и unsigned (b) unsigned и long (с) float и double (d) char и long double (e) int [] и int * (f) short () и short () 4. Допустимы ли реализации С, в которых тип char может представлять значения в интервале от -2 147 483 648 до 2 147 483 647? Если да, ка- кое значение возвратит sizeof(char) в этой реализации? Каковы будут минимальное и максимальное значения типа int? 5. Каким должно быть отношение между sizeof (long double) и sizeof(int)?
Преобразования и представления 221 6. Предположим, у нас есть два компьютера А и В с побайтовой адресаци- ей и размером слова в 32 бит (четыре байта), но компьютер А имеет ту- поконечную архитектуру, компьютер В — остроконечную. Целое зна- чение 128 записывается на компьютере А в переменную размером в одно слово, а затем передается на компьютер В, где записывается в та- кую же переменную (первый байт слова А в первый байт слова В и т.д.). Какое значение получим на компьютере В? Каким будет результат, если А — остроконечный, В — тупоконечный?

Глава 7 Выражения Язык С позволяет программировать огромное множество операций, какие только способно поддерживать оборудование. В этой главе мы рассмотрим син- таксис выражений и действие знака каждой операции. 7.1 Объекты, 1-значения и именующие выражения Объект — это область памяти, в которую можно записывать определенную информацию и считывать ее оттуда. 1-значение (lvalue, эль-значение) — это вы- ражение, ссылающееся на объект таким образом, что позволяет считывать или изменять значение этого объекта. В левой части оператора присваивания мо- жет стоять только 1-значение. Выражение, не являющееся 1-значением, иногда именуется r-значением (rvalue, эр-значение, от right — правый), поскольку мо- жет устанавливаться только в правой части оператора присваивания. 1-значе- ние может иметь объектный либо неполный тип, но не тип void. В соответствии с определением этого термина в Standard С, 1-значение не всегда допускает изменение обозначаемого им объекта. Это справедливо в слу- чае, когда 1-значение имеет регулярный либо неполный тип, либо тип, объяв- ленный с квалификатором const, тип структуры или объединения, в которых один из членов (рекурсивно входящий во вложенные структуры и объедине- ния) объявлен с квалификатором const. 1-значение, которое допускает измене- ние обозначаемого объекта, называется модифицируемым (modified). Именующее выражение для функции (function designator) — это значение, имеющее тип функции. Оно не является ни объектом, ни 1-значением. Имя функции является ее именующим выражением, как результат разыменования указателя на функцию. Функции и объекты часто трактуются в С различно, и нам придется быть очень внимательными при различении таких понятий, как «типы функций» и «типы объектов», а также «1-значения», «именующие выражения для функций», «указатели на функции» и «указатели на объек- ты». Фраза «1-значение, обозначающее объект» — избыточна, но мы будем прибегать к такому многословию, по мере необходимости, чтобы подчеркнуть неприменимость термина к именующим выражениям для функций. Выражения языка С, которые могут быть 1-значениями, перечислены в таб- лице 7.1, где указаны также условия, в которых зто возможно. Никакие другие выражения не могут быть 1-значениями, и никакие из значений, перечислен-
224 Глава 7 ных в таблице, кроме строковых литералов, не могут быть 1-значениями, если они объявлены как массивы. В число выражений, которые не могут быть 1-зна- чениями, входят имена массивов, функции, константы перечислимых типов, выражения присваивания, операции приведения типов и вызовы функций. Таблица 7.1. Нерегулярные (ие массивы) выражения, которые могут быть 1-значениями Выражение Дополнительные условия ИМЯ имя должно быть переменной -Ф] нет (е) е должно быть 1-значением е.имя е должно быть 1-зночением е->имя нет *е нет строковая-константа нет Операции, перечисленные в таблице 7.2, могут быть 1-значениями при оп- ределенных операндах. Таблица 7.2. Операции, которым необходимы определенные операнды, чтобы стать 1-значеииями Операция Требование к операнду & (унарная) Операнд должен быть 1-значением или именем функции + + — Операнд должен быть 1-значением (в постфиксной или префиксной форме). А И II + V 11 » II 8° * II II и II и Левый операнд должен быть 1-значением. Ссылки: операция адресации 7.5.6; выражение присваивания 7.9; выражение приведения типа 7.5.1; выбор компонента 7.4.2; выражение отрицательного приращения 7.4.4, 7.5.8; пе- речисления 5.5; вызовы функций 7.4.3; выражение отрицательного приращения 7.4.4, 7.5.8; выражение опосредования 7.5.7; литералы 2.7, 7.3.2; имена 7.3.1, строковая константа 2.7.4; индексы 7.4.1. 7.2 Выражения и приоритет Синтаксис выражений, описываемых в этой главе, полностью определяет порядок выполнения (приоритет) операций в С. Итоговая информация сведена в таблицу 7.3, содержащую перечень операций языка С в порядке снижения приоритета, с указанием ассоциативности. Таблица 7.3. Операции языка С, в порядке снижения приоритета Лексемы Операция имена, литералы простые лексемы oik] индексы Класс Приоритет Ассоциативность первичный 16 нет постфиксный 16 слеза направо
Выражения 225 Лексемы Операция Класс Приоритет Ассоциативность вызов функции постфиксный 16 слева направо • прямой выбор постфиксный 16 слева направо —> опосредованный выбор постфиксный 16 слева направо ++ — положительное и отрицательное прирощение постфиксный 16 слева направо ( имя типа ) {init} составной литерол (С99) постфиксный 16 слева направо ++ положительное и отрицательное прирощение префиксный 15 справа налево sizeof размер унарный 15 справа налево - побитовое НЕ унарный 15 справа налево I логическое НЕ унарный 15 справа налево - + изменение знака, плюс унарный 15 справа налево & адрес унарный 15 справа налево * опосредование (разыменование) унарный 15 справа налево ( имя типа ) приведение типа унарный 14 справа налево * / % мультипликативные операции бинарный 13 слева направо + - аддитивные операции бинарный 12 слева направо « » сдвиг влево и впрово бинарный 11 слева направо <><==>= отношения бинорный 10 слева направо == != ровенство/неровенство бинарный 9 слева направо & побитовое И бинарный 8 слева направо побитовое исключающее ИЛИ бинарный 7 слева направо I побитовое ИЛИ бинарный 6 слева направо && побитовое И бинарный 5 слева направо II логическое ИЛИ бинарный 4 слева направо 7 : условие тринарный 3 справа налево и 11 + > S? II и и । ~ л “ V " н присваивание бинарный 2 справа налево f последовательная оценка бинарный 1 спева направо 7.2.1 Приоритет и ассоциативность операций Каждой операции в языке С присваиваются определенный приоритет и пра- вило ассоциативности. Если порядок выполнения операций не определяется скобками, операнды присоединяются к знакам операций, имеющих более вы- сокий приоритет. Если две операции имеют одинаковый приоритет, операнды присоединяются к левому или правому знаку операции, в зависимости от того, имеет эта операция левую или правую ассоциативность.
226 Глава 7 Приоритеты и ассоциативности определяют смысл выражения, но не опре- деляют последовательности оценки подвыражнеий в выражениях или опера- торах. Последовательность оценки обсуждается в разделе 7.12. Пример Ниже приведены примеры приоритетов и ассоциативностей: Исходное выражение Эквивалентное выражение Пояснение о*Ь+с (о*Ь)+с приоритет * выше приоритета + о+=Ь|=с о+=(Ь|=с) += и |= имеют левую ассоциативность о-Ь+с (о-Ь)+с - и + имеют левую ассоциативность sizeof(int)*p (sizeof(int))*p приоритет sizeof выше приоритете приведения *p->q *(p->q) приоритет -> выше приоритета * Попробуем обобщить правила ассоциативности. Все бинарные операции, кроме операции присваивания, имеют левую ассоциативность; операция при- сваивания имеет правую ассоциативность — как и условие. Унарные и пост- фиксные операции иногда описываются как имеющие правую ассоциатив- ность, но это необходимо только для интерпретации *х++ как *(х++), а не (*х)++. Проще считать, что постфиксные операции имеют более высокий при- оритет, чем (префиксные) унарные операции. Ссылки: операции присваивания 7.9; бинарные операции 7.6; сцеппение строк 2.7 4; усло- вие 7.8; постфиксные операции 7.4.4; унарный + 7.5.3. 7.2.2 Переполнение и другие арифметические исключения При выполнении некоторых операций в С — например, сложения или ум- ножения, — возникает ситуация, когда результат невозможно выразить в фор- ме значения определенного типа (как определяется правилами обычных пре- образований). Эта ситуация именуется переполнением или (в некоторых слу- чаях) потерей значимости. В целом, последствия переполнения в С не определены. Один из возможных вариантов — возврат неправильного значения правильного типа, другой — аварийное завершение программы. Возможно также генерирование опреде- ленного прерывания или исключения, зависящих от типа машины, которые могут быть обнаружены программой каким-то зависящим от реализации обра- зом. Существуют операции, в отношении которых прямо устанавливается неоп- ределенность результата при некоторых значениях операндов или (более стро- го) непременное наличие результата, значение которого оказывается неопреде- ленным при некоторых значениях операндов. Например, если правый операнд операции деления (/) или остатка (%) равен нулю, результат операции оказы- вается неопределенным. Аналогично, если значение правого операнда опера- ции сдвига (<< или >>) слишком велико или отрицательно, результат и этой операции оказывается неопределенным.
Выражения 227 Традиционно, проблема переполнения целых значений со знаком игнори- руется во всех реализациях С — принимается любое значение, полученное в результате выполнения машинной команды, реализующей операцию. Во многих компьютерах, в которых целые значения со знаком представляются в форме дополнения до двух, обработка переполнения при сложении и вычита- нии ограничивается возвратом младших битов дополнения результата до двух. Несомненно, эта схема принимается во многих программах, но она лишает эти программы мобильности. Обработка переполнения и потери значимости значе- ний с плавающей точкой, как правило, соответствует возможностям, реализо- ванным на аппаратном уровне; если архитектура компьютера обеспечивает возможность нескольких вариантов обработки соответствующих исключений, программисту может быть предоставлен выбор из нескольких библиотечных функций. В отношении беззнаковых целых обработка переполнения в С очень специфич- на: результат любой операции над беззнаковыми целыми сравним по модулю 2П с результатом истинной математической операции (здесь п — число битов в пред- ставлении беззнакового целого). Это означает, что младшие п битов результата (до- полнения результата до двух, если правильный результат отрицателен, как в слу- чае вычитания большего числа из меньшего) всегда будут правильными. Пример Предположим, объекты типа unsigned представляются 16 битами. Тогда вычитание беззнаковых 7 из беззнаковых 4 даст 65,533 (216-3), поскольку это значение сравни- мо по модулю 216 с истинным математическим результатом -3. Следствием этого правила является гарантированная мобильность опера- ций над беззнаковыми целыми между реализациями, в которых эти целые представляются в одинаковом числе битов. Беззнаковую арифметику нетрудно моделировать в другой реализации с использованием меньшего числа битов. Ссылки: операция деления / 7.6,1; типы с плавающей точкой 5.2; операция вычисления ос- татка % 7.6.1; операции сдвига « и » 7.6.3; типы со знакам 5.1.1; беззнаковые типы 5.1.2. 7.3 Первичные выражения Существуют первичные выражения трех видов: имена (идентификаторы), литеральные константы и выражения в скобках. первичное-выражение: идентификатор константа выражение-в-скоб ках Вызовы функций, индексы и выбор компонентов традиционно считались в С первичными выражениями, но мы объединили их с постфиксными выра- жениями, рассматриваемыми в следующем разделе. 7.3.1 Имена Значение имени зависит от его типа. Тип имени определяется его объявле- нием (при наличии такового) — см. главу 4.
228 Глава 7 Имя переменной арифметического, ссылочного, перечислимого, структур- ного типа или объединения принимает значение объекта этого типа. Имя яв- ляется 1-значением. Имя перечислимой константы принимает значение соот- ветствующего целого типа и не является 1-значением. Пример В следующем примере, три наименования цветов являются перечислимыми кон- стантами. Оператор switch (рассматриваемый в разделе 8.7) выполняет один из трех операторов, в зависимости от значения параметра color: typedef enum { red, blue, green ) colortype; colortype next_color(colortype color) { switch (color) ( case red return blue; case blue return green; case green : return red; 1 1 Имя массива принимает значение этого массива; это 1-значение, хотя и не модифицируемое. Если массив не является аргументом функции sizeof, опе- рандом адресной операции (&) либо символьным массивом, инициализируе- мым как строковая константа, значение массива преобразуется в процессе обычных унарных преобразований в указатель на его первый элемент. Пример Преобразования имени массива в указатель не происходит, если массив указан в ка- честве аргумента функции sizeof, поскольку функция должна возвращать размер массива, а не размер указателя. extern void PrintMatrix () ; int Matrix [10] [10], total_length, row_length; total_length = sizeof Matrix; row_length s sizeof Matrix[0], PrintMatrix(Matrix); /* передается указатель на первый элемент */ Имя функции принимает значение самой функции и не является 1-значени- ем. За исключением случаев, когда имя функции используется в качестве опе- ранда адресной операции (&) или аргумента функции sizeof, это имя преобра- зуется в процессе обычных унарных преобразований в указатель на функцию. Результат операции &f — это указатель на f (не указатель на указатель на f), тогда как оператор вида sizeof(f) — недопустим. Пример Это пример указания имени функции в качестве аргумента другой функции: extern void PlotFunction(double (*f)(double), double xO, double xl); double fn(double x) ( return x * x - x, } int main(void)
Выражения 229 PlotFunction(fnt 0.01, 100.0); /* fn преобразуется в &fn */ } Имя, как выражение, не может ссылаться на метку, имя typedef, имя ком- понента структуры или объединения, тег структуры или объединения, тег пе- речисления. Имена, используемые для этих целей, располагаются в простран- ствах имен отдельно от имен, на которые можно ссылаться в выражениях. На некоторые из этих имен возможны ссылки при помощи специальных конст- рукций. Например, возможна ссылка по имени на компонент структуры или объединения при помощи операции . или —>, а имена typedef можно указы- вать в приведении типов и как аргументы функции sizeof. Ссылки: регулярные типы (массивы) 5.4; приведение типов 7.5.1; перечислимые типы 5.5; вы- зовы функций 7.4.3; типы функций 5.8; 1-значения 7.1; пространство имен 4.2; операции выбо- ра . и -> 7.4.2; операция sizeof 7.5.2; имена typedef 5.10; обычные унарные преобразова- ния 6.3.3. 7.3.2 Литералы Литерал (лексическая константа) — это численная константа, которая при вычислении ее как выражения дает значение этой константы. Литеральные выражения, за исключением строковых констант, не могут быть 1-значения- ми. Обсуждение литералов, их типов и значений см. в разделе 2.7. 7.3.3 Выражения в скобках Выражение в скобках состоит из левой скобки, любого выражения и правой скобки: выражение-в-скобках: ( выражение ) Тип выражения в скобках совпадает с типом выражения, заключенного в скобки; никаких преобразований типов не происходит. Значение выражения в скобках равно значению выражения, заключенного в скобки. Выражение в скобках является 1-значением тогда и только тогда, когда выражение, за- ключенное в скобки, является 1-значением. Скобки не всегда определяют по- следовательность вычислений (см. раздел 7.12). Назначение выражения в скобках — группирование выражений для изме- нения последовательности вычислений, устанавливаемой приоритетами, дей- ствующими по умолчанию, либо просто для большей наглядности. Пример х! = (-Ь + discriminant)/(2.0 * а) Ссылки: 1-значение 7.1.
230 Глава 7 7.4 Постфиксные выражения Существуют постфиксные выражения шести видов: индексные выражения, два вида выбора компонентов (прямой и опосредованный), вызовы функций и постфиксные выражения положительного и отрицательного приращений. постфиксное-выражение: первичное-выражение индексное-выражение выбор-компонентов вызов-функции приращение отрицательное-приращение составной-литерал (С99) Вызовы функций, индексные выражения и выбор компонентов традицион- но относились к первичным выражениям, однако по своему синтаксису они ближе к постфиксным выражениям. 7.4.1 Индексные выражения Индексное выражение состоит из постфиксного выражения, левой квадрат- ной скобки, произвольного выражения и правой квадратной скобки. Эта кон- струкция применяется для обозначения индексов массивов. Здесь постфикс- ное выражение (как правило, имя массива) определяет указатель на начало этого массива, выражение в скобках — вычисляет целое значение смещения: индексное-выражение : постфиксное-выражение [ выражение ] Выражение ej[e2] в С по определению в точности равнозначно выражению *((е1)+(^2))- Обычные бинарные преобразования применяются к обоим операн- дам, и результат всегда является 1-значением. В операции опосредования (разы- менования) (*) должен присутствовать указатель на операнд, а единственный вариант, при котором результат операции + оказывается указателем — исполь- зование указателя в качестве одного из операндов, когда второй — целый. От- сюда следует, что один из операндов выражения ei[e2] должен быть указателем, другой — целым. По определению, ej — это имя массива, е2 — целое выраже- ние, однако ci может быть также указателем, а порядок операндов — обрат- ным. Следствием такого определения индексов является индексация от нуля. Ссылки на многомерные массивы образуются посредством составных ин- дексных операций. Пример char buffer[100], *bptr = buffer; int i = 99; buffer[0] bptr[i-l] i [bptr] = '\0'; = bptr[0]; = ’\0'; /* индексация массива */ /* индексация указателя */ /* нестандартная индексация */ Ссылка на первый элемент 100-элементного массива — buffer[O], на последний — buffer(99]. Имена buffer и bptr указывают одно и то же место в памяти — первый элемент массива buffer[O], — и каждое из них можно использовать идентичным об-
Выражения 231 разом в индексных выражениях. Однако bptr является переменной (1-значением), а потому может использоваться для обозначения другого места в памяти: bptr = Sbuffer[6]; Теперь выражение bptr[—4] указывает на то же место в памяти, что и buffer[2] (кстати, это иллюстрация возможного применения отрицательных индексов). Кро- ме того, bptr можно установить таким образом, что он не будет указывать на что бы то ни было: bptr = NULL; /* Присвоение указателю нулевого значения. */ Однако имя массива buffer не является 1-значением, а потому не может модифици- роваться. Если это имя рассматривать как указатель, то это будет указатель, всегда ссылающийся на одно место в памяти, как если бы он был объявлен в виде char * const buffer; Пример В следующем примере происходит запись значения 1.0 в диагональные элементы массива размерности 10x10 (матрицы), значения 0.0 — в остальные элементы: int matrix[10][10]; for (i = 0; i < 10; i++) for (j = 0; j < 10; j++) matrix[i][j] = ((i = j) ? 1.0 : 0.0); Следует избегать применения comma-выражений (выражений с запятыми) внутри квадратных скобок, так как программисты, более привычные к другим языкам программирования, могут принять это за обозначение многомерного массива. Пример Выражение commands[k=n+l, 2*k] может показаться ссылкой на элемент двумерного массива с именем commands и индексными выражениями k=n+l и 2*к, однако это ссылка на элемент одномер- ного массива commands с индексом 2*к, где к=п+1. Если без comma-выражения не обойтись (хотя очень трудно представить такую ситуацию), его следует поместить в скобки, чтобы подчеркнуть, что это нечто не совсем обычное: commands[(k=n+l, 2*k)] Указатели и операцию приведения типов можно использовать для ссылок на многомерные массивы как на одномерные. Это позволяет добиваться неко- торого выигрыша в скорости. Следует помнить, что массивы С располагаются в памяти по строкам. Пример В следующем фрагменте исходного текста запрограммировано составление единич- ной матрицы — то есть матрицы, диагональные элементы которой равны 1, осталь- ные — 0. Алгоритм неочевиден, но быстр. Двумерная матрица представляется в виде одномерного вектора с тем же числом элементов, благодаря чему упрощается индексирование и отпадает необходимость во вложенных циклах.
232 Глава 7 tdefine SIZE 10 double matrix[SIZE][SIZE]; int i; for (i = 0; i < SIZE*SIZE; i++) ((double *)matrix)[i] =0.0; /* обнуление всех элементов */ for (i =0; i < SIZE*SIZE; i += (SIZE + 1)) ((double *) matrix)[i] = 1.0; /* запись 1 в диагональные элементы */ Ссылки: опероция сложения + 7.6.2; регулярные типы 5.4; comma-выражения 7.10; операция опосредования * 7.5.7; целые типы 5.1; 1-значение 7.1; ссылочные типы 5.3 7.4.2 Выбор компонента Операции выбора компонентов используются для обращения к полям (ком- понентам структур и объединений): выражение-выбора-компонента: непосредственный-выбор-компонента опосредованный-выбор-компонента непосредственный-выбор-компонента: пос тфиксное-выражение. идентификатор опосредованн ый-выб ор-компонента: постфиксное-выражение -> идентификатор Выражение непосредственного выбора компонента состоит из постфиксного выражения, точки (.) и идентификатора. Постфиксное выражение должно иметь тип структуры или объединения, а идентификатор — представлять имя компонента структуры (объединения) этого типа. Результат выражения выбо- ра компонента — имя члена структуры или объединения. Результат выражения непосредственного выбора компонента является 1-значением, если таковым являются структура или объединение, указанные в выражении. Структура или объединение не является 1-значением только в том случае, если представляет собой значение, возвращенное функцией. Ре- зультат может быть модифицирован, если он является 1-значением и если вы- бранный компонент не является массивом. Пример struct S (int a,b;) х; extern struct S f(); /* функция возвращает структуру */ int i ; x = f(); /* правильно */ i = f().a; /* правильно */ f () a = i; /* Неправильно; f () не является 1-значением */ Последний оператор присваивания не содержит ошибки, однако не имеет смысла. Функция возвращает копию некоторой структуры, затем один из компонентов этой копии модифицируется, после чего вся копия удаляется с завершением оператора. В некоторых реализациях, не относящихся к Standard С, функции не могут возвращать структурные типы. В некоторых из тех, где это разрешено, запре-
Выражения 233 щено применение операции выбора компонента к результату функции, а пото- му конструкция вида f(). приводит к ошибке. Если выражение, стоящее перед точкой, или выбираемый компонент име- ют квалификаторы типов, результат имеет квалификаторы, составляющие объединение обоих множеств квалификаторов. Пример Следующий оператор присваивания содержит ошибку, поскольку х.а имеет тип const int — квалификатор const унаследован от х: const struct {int а,Ь;) х; х.а = 5; /* Ошибка */ Выражение опосредованного выбора компонента состоит из постфиксного выражения, знака операции —> и имени. Значением постфиксного выражения должен быть указатель на структуру или объединение, имя — именем компо- нента этой структуры или объединения. Результат — имя члена структуры или объединения, — является 1-значением и может быть модифицирован, если член не является массивом. Выражение е—>имя равнозначно, по определе- нию, выражению (*е).имя. Пример В следующем примере выполняется (чтобы продемонстрировать указанную выше равнозначность — чрезмерно хитроумным образом) обнуление обоих компонентов структуры Point: struct {float х, у; } Point, *Point_Ptr; Point.x =0.0; /* Присваивание x = 0.0 */ Point_Ptr = SPoint; Point_Ptr->y =0.0; /* Присваивание у = 0.0 */ Если выражение, стоящее перед знаком —>, или выбираемый компонент имеют квалификаторы типов, результат имеет квалификаторы, составляющие объединение обоих множеств квалификаторов. В некоторых реализациях С допускается установка, слева от знака —>, ну- левого указателя. Если выполнить адресную операцию (&) над результатом та- кого выражения, а результат этой операции привести к целому типу, получим смещение в байтах данного компонента внутри структуры. Этот прием не фор- мализован в Standard С, как и не запрещен, и очень часто работает. Пример #define OFFSET(type,field) \ ((size_t) & ( (type *)0)->field) Макрос OFFSET аналогичен макросу offsetof из заголовочного файла stddef.h. Ссылки: адресная операция & 7.5.6; операция опосредования * 7.5.7, 1-значение 7.1; мак- рос offsetof 11.1; size_t 13 1 ; структурные типы 5.6; квалификаторы типов 4.4.3; объедине- ния 5.7.
234 Глава 7 7.4.3 Вызовы функций Вызов функции состоит из постфиксного выражения (выражения-функ- ции), левой скобки, возможно пустой последовательности выражений (аргу- ментов), разделенных запятыми, и правой скобки: вызов-функции: постфиксное-выражение ( список-выраженийнеобямт ) список-выражений: выражение-присваивания список-выражений , выражеиие-присваивания После выполнения обычных унарных преобразований выражение-функция преобразуется в тип «указатель на функцию, возвращающую значение типа Т», где Т — имя некоторого типа. Результат вызова функции имеет тип Т и не является 1-значением. Если тип Т равен void, вызов функции не имеет резуль- тата и не может использоваться в синтаксической конструкции, подразуме- вающей наличие результата. Т не может быть регулярным типом. В компиляторах, предшествовавших Standard С, выражение-функция должно было иметь тип «функции, возвращающей значение типа Т», а потому указатели на функции должны были разыменовываться явным образом. То есть если fp — указатель на некоторую функцию, эту функцию можно вызвать при помощи простой конструкции: (* fp)(...). При указании fp в качестве фор- мального параметра, иногда допускается более простая конструкция: fp (...). При вызове функции, сначала происходит вычисление ее аргументов. По- рядок выбора аргументов для вычисления не определен. Далее, если вызов функции управляется прототипом Standard С (см. раздел 9.2), значения аргументов преобразуются в типы, соответствующие типам формальных параметров, указанным в прототипе. Если преобразование оказы- вается невозможным, происходит ошибка. Если функция имеет переменное число аргументов, дополнительные аргументы преобразуются в соответствии с правилами обычных преобразований аргументов (раздел 6.3.5); никаких других проверок дополнительных аргументов не происходит. Если вызов функции не управляется прототипом, происходит только обыч- ное преобразование аргументов, без каких бы то ни было дополнительных про- верок. Причина в том, что в отсутствие прототипа компилятор не имеет ника- кой информации о формальных параметрах внешних функций. После оценки и преобразования фактических аргументов, происходит их копирование в формальные параметры вызываемой функции; таким образом, все аргументы передаются по значениям. Внутри функции имена формальных параметров являются 1-значениями, однако присваивание им значений приво- дит к изменению только копированных значений и никак не влияет на факти- ческие аргументы, которые могут оказаться 1-значениями. Притер Рассмотрим функцию square, возвращающую квадрат аргумента: double square (double у) ( у = у*у; return у; I Предположим, х — переменная типа double, имеющая значение 4.0, и выполняется вызов функции square(x). Функция возвращает значение 16.0. однако х остается при значении 4.0. Таким образом, присваивание аргументу у нового значения внут- ри функции square изменяет только копию фактического аргумента.
Выражения 235 Вызываемая функция может изменять только те данные функции вызываю- щей, которые видимы для нее независимо от передачи аргументов (например, глобальные переменные), либо если в качестве аргумента используется указа- тель на данные. Аргумент-указатель копируется, однако с данными, на которые он ссылается, этого не происходит. Следовательно, изменения, выполненные опосредованно через указатель, отражаются на данных вызывающей функции. Притер Приведенная ниже функция swap «перекидывает» значения двух переменных, при- нимая, в качестве параметров, указатели на эти переменные: void swap(int *хр, int *ур) ( int t = *xp; *xp = *yp; *УР = t; } Если a — массив целого типа, все элементы которого равны 0, i — целая перемен- ная, равная 4, тогда, после вызова swap(&a[i],&i), i будет иметь значение 0, а[4] — значение 4. Формальные и фактические аргументы регулярных типов (массивов) в С всегда преобразуются в указатели. Следовательно, изменение регулярного формального параметра, как ни удивительно, повлечет изменение фактического аргумента. Притер Рассмотрим следующую функцию f с параметром регулярного типа: void f(int а[10]) { а[4] = 12; /* влечет изменение массива в вызывающей функции */ ] Если vec — массив целого типа, вызов f(vec) приведет к записи в элемент vec[4] зна- чения 12. Указание размерности 10 в параметре не имеет значения; массив а мог быть объявлен как int а[]. Если функция, тип возвращаемого значения которой не равен void, вызы- вается в синтаксической конструкции, в которой зто значение не использует- ся, компилятор может вывести предупреждение об этом. Тем не менее, прак- тика такого использования функций, не принадлежащих к типу void — на- пример, функции printf, — распространена довольно широко, и многие программисты считают подобные предупреждения излишними. Притер Намерение отбросить результат функции можно выразить явно, приведя ее к типу void, как в следующем примере с функцией strcat: (void) strcat(word, suffix); Comma-выражения могут использоваться в качестве аргументов функций заключенными в скобки — таким образом, их части не будут интерпретиро- ваться как отдельные аргументы.
236 Глава 7 Пример Предположим, нам необходимо отследить все вызовы функции f в программе С. Если f вызывается с одним аргументом, следующий макрос будет вызывать перед каждым вызовом f функцию tracef. #define f(х) (tracef (_FILE_, _LINE_), f((x))) Если вызов f происходит из аргумента некоторой функции g — например, g(f(y)), — аргумент функции g является comma-выражением. Ссылки: соглашение аб аргументах и параметрах 9.6; сотта-операция 7.10; отбрасывае- мые выражения 7.13; типы функций 5.8; прототипы функций 9.2; операция опосредова- ния * 7.5.7; 1-значение 7.1; расширение макроса (макрорасширение) 3.3.3; ссылочные типы 5.3; printf 15.11; strcat 13.1; обычные преобразования аргументов 6.3.5; тип void 5.9. 7.4.4 Постфиксные операции положительного и отрицательного приращения Постфиксные операции ++ и — выполняют, соответственно, приращение и отрицательное приращение операнда, возвращая его исходное значение. Это операции с побочным эффектом: постинкрементное-выражение: пос тфиксное-выражение ++ постдекрементное-выражение: постфиксное-выражение — Операнд в обеих операциях должен быть модифицируемым 1-значением лю- бого действительного арифметического или ссылочного типа. Операнд увели- чивается (в случае операции ++) или уменьшается (в случае операции —) на единицу. Результат операции — значение операнда до приращения (положи- тельного или отрицательного). Результат не является 1-значением. Операнд и константа 1 подвергаются обычным бинарным преобразованиям. Тип ре- зультата совпадает с типом операнда до преобразования. Пример Если i и j — целые переменные, оператор i=j—; будет равнозначен последователь- ности двух операторов: 1 = 3; j = j-1; Если операнд имеет тип целого со знаком или действительного с плаваю- щей точкой, переполнение может дать непредсказуемый результат. Прираще- ние максимально представимого беззнакового целого дает результат 0, отрица- тельное приращение нулевого значения этого же типа — максимально пред- ставимое его значение. Если операнд является указателем — например, «указатель на объект типа Т», где Т — имя некоторого типа, результатом операции ++ будет сдвиг указа- теля вперед относительно объекта, на который он указывал, как при сдвиге на следующий элемент массива объектов типа Т. В компьютере с побайтовой ад-
Выражения 237 ресацией это означает сдвиг указателя на sizeof(T) байтов вперед. Аналогично, результатом операции — будет сдвиг указателя на предыдущий элемент мас- сива объектов типа Т. В обоих случаях, результатом выражения будет значе- ние указателя до модификации. Пример Очень часто постфиксная операция приращения используется для просмотра эле- ментов массива или символов строки, как в следующем примере определения числа символов в строке: int string_length(const char *cp) { int count = 0; while (*cp++) count++; return count; ) Ссылки: сложение 7.6.2; регулярные типы 5.4; преобразования в операции присваивания 6.3.2; типы с плавающей точкой 5.2; целые типы 5.1; 1-значение 7.1; переполнение 7.2.2; ссы- лочные типы 5.3; скалярные типы гп. 5; типы со знаком 5.1.1; вычитание 7.6.2; беззнаковые типы 5.1.2; обычные бинарные преобразования 6.3.4. 7.4.5 Составные литералы В С99 введены составные литералы — как способ выражения неименован- ных констант нескалярных типов. Составной литерал складывается из имени типа в скобках, за которым следует список инициализаторов, заключенный в фигурные скобки. Список инициализаторов может заканчиваться запятой. составной-литерал: ( имя-типа ) { список-инициализаторов ,огщ } (С99) Составной литерал создает неименованный объект обозначенного типа и возвращает 1-значение этого объекта. Имя типа может указывать объект или массив неизвестного размера. Массивы переменных размеров не могут исполь- зоваться в составных литералах, поскольку их невозможно инициализиро- вать. Пожалуй, наиболее подходящими типами для составных литералов яв- ляются структуры, объединения, массивы и перечисления. Формат и смысл списка инициализаторов те же, что и в объявлении объекта того же типа и раз- мера. В частности, это означает, что неинициализированным компонентам со- ставного литерала в результате инициализации присваиваются нулевые значе- ния (см. раздел 4.6). Составной литерал, объявленный с квалификатором типа const, доступен только для чтения, иначе его можно было бы изменять. Если два составных литерала, доступных только для чтения, имеют одинаковые типы и значения, в некоторых реализациях они могут занимать одну области памяти — то есть иметь один и тот же адрес, как в случае дублированных строковых литералов. Пример Установка указателя Tempi на модифицируемую строку, указателя Тетр2 — на строку, доступную только для чтения:
238 Глава 7 char ‘Tempi = (char []){"/temp/XXXXXXXX"}; char *Temp2 = "/temp/XXXXXXXX"; Функция POW2 определяет по таблице небольшие степени двух: inline int POW2(int n) { assert( n >= 0 && n <= 7 ); return (const int [])(1, 2, 4, 8, 16, 32, 64, 128}[nJ; } Функция DrawTo принимает в качестве аргумента значение структуры, обозначаю- щей точку на плоскости, функция DrawLine — адреса двух точек: DrawTo( (struct Point)(.х=12, .y=n+3} ); DrawLine( S(struct Point){x,у}, S(struct Point){-x,-y) ); Если составной литерал объявлен на верхнем уровне файла, соответствую- щий неименованный объект оказывается статически размещаемым — то есть существует на протяжении выполнения программы. Список инициализато- ров, в этом случае, может содержать любые константы. Если составной лите- рал объявлен внутри функции, он автоматически размещаем, а область его ви- димости совпадает с размерами самого внутреннего из вложенных блоков, ко- торым он принадлежит. При использовании адреса составного литерала, необходимо учитывать длительность его существования; нельзя использовать этот адрес вне области видимости литерала. Составной литерал распределяется заново при каждом входе в содержащий его блок; многократное обращение к литералу без выхода из блока может тре- бовать лишь реинициализации литерала в той же области памяти. Подобная реинициализация может происходить только в цикле, образованном при помо- щи оператора goto — в случае оператора цикла, инициализированый литерал остается видимым в теле этого оператора и не требует реинициализации. Пример В следующем цикле происходит заполнение массива ptrs указателями на один мас- сив, причем *(ptrs[i|) == 4. int * ptrs[5]; int i = 0; again: ptrs[i] = (int [1] ) {i } ; if (++i<5) goto again; Следующий пример — заполнение массива указателями на разные массивы, причем *(ptr[i])== i. int * ptrs[5]; int i = 0; ptrs[i] = {int [!]){!++}, } ptrsfi] = (int [l])(i++J; } ptrs[i] = (int [l]){i++}; ) ptrs[i] = (int [l])(i++J; } ptrs[i] = (int (l]){i++); } Еще в одном цикле массив ptrs заполняется неопределенными указателями, по- скольку каждый литеральный массив удаляется из памяти в конце каждого прохо- да цикла. int *ptrs[5]; for(int i=0; i<5; i++) { ptrs [i] = (int [1]){1); } Ссылки: инициализатор 4 6; массив переменной длины 5 4 5
Выражения 239 7.5 Унарные выражения В следующих разделах мы обсудим несколько разновидностей унарных вы- ражений. выражение-приведения-типа : унарное-выражение ( имя-пгипа ) выражение-приведения-типа унарное-выражение : постфиксное-выражение выражение sizeof унарное-выражение-«минус» унарное-выражение-«плюс» выражение-логического-НЕ выражение-побитового-НЕ адресное-выражение выражение-опосредования преинкрементное-выражение предекрементное-выражение Приоритет унарных операций ниже приоритета постфиксных выражений, но выше, чем приоритет бинарных и тринарных операций. Например, выра- жение *х++ интерпретируется как *(х++), а не (*х)++. Ссылки: бинарные выражения 7.6; постфиксные выражения 7.4; приоритет 7.2.1; унарная операция «плюс» 7.5.3. 7.5.1 Приведение типов Выражение приведения типа состоит из левой скобки, имени типа, правой скобки и операнда. Синтаксис указан выше (см. унарное-выражение). В результате операции преобразования типа тип операнда преобразуется в тип, указанный в скобках. В этой операции можно указать любое допусти- мое преобразование (раздел 6.3.1). Результат не является 1-значением. Пример extern char *alloc(); struct S *p; p = (struct S *) alloc(sizeof(struct S) ) ; В некоторых реализациях С игнорируются приведения типов, единствен- ным результатом которых является определенное «сужение» значения относи- тельно нормального формата. Это неправильно. Пример Предположим, размер типа unsigned short равен 16 бит, типа unsigned — 32 бит. Тогда значение выражения (unsigned)(unsigned short)OxFFFFFF окажется равным OxFFFF, поскольку, в результате операции приведения типа, зна- чение OxFFFFFF окажется усеченным до 16 бит, затем возвращается к прежнему
240 Глава 7 размеру в 32 бита. Не слишком «разборчивые» компиляторы могут проводить зна- чение OxFFFFFF через эту процедуру неизменным. Те же компиляторы могут со- хранять точность исходного значения в выражении (double)(float)3.1415926535897932384 Чтобы обеспечить максимальную мобильность программ, компилирован- ных в версиях, не относящихся к Standard С, программистам приходится усе- кать значения, помещая их в переменные, или (в случае целых типов) явно за- давая операции маскирования (например, при помощи & — побитового И), не полагаясь на «сужающие» приведения типов. Ссылки: операция побитового И 7.6.6; преобразования типов гп. 6; имена типов 5.12. 7.5.2 Операция sizeof Операция sizeof применяется для определения размера типа или объекта данных: выражение-sizeof : sizeof ( имя-типа ) sizeof унарное-выражение Возможны две формы выражения sizeof: с именем типа в скобках и с опе- рандом. Результат — целая константа, — не может быть 1-значением. В Stan- dard С результат операции sizeof имеет тип беззнакового целого size_t, опреде- ленный в заголовочном файле stddef.h. В реализациях традиционного С ре- зультат может также иметь тип int или long. Согласно рассмотренным выше правилам расстановки приоритетов выражение sizeof(long)—2 интерпретиру- ется как (sizeof(long))—2, а не sizeof((long)(—2)). Результатом операции sizeof над заключенным в скобки именем типа будет размер объекта этого типа — то есть объем памяти (измеряемый в единицах па- мяти), занимаемой объектом указанного типа, включая все внутренние и конце- вые биты заполнения. По определению, результат выполнения операции sizeof над любым символьным типом равен 1. Имя типа не должно обозначать непол- ный регулярный тип (массив с неопределенной длиной), функцию или void. Выполнение операции sizeof над выражением дает тот же результат, что и выполнение ее над именем типа, равного типу значения, возвращаемого вы- ражением. При выполнении операции выражение не подвергается обычным преобразованиям; таким образом, при определении размера массива имя по- следнего не преобразуется в указатель. Однако если выражение содержит опе- рации, подвергаемые обычным преобразованиям, эти преобразования учиты- ваются при определении типа. Операнд операции sizeof не должен иметь не- полный регулярный тип или тип функции, за исключением ситуации, в которой операция sizeof выполняется над именем формального параметра, объявленного как массив или функция. В этом случае, результат равен разме- ру ссылочного типа, полученного в результате обычного преобразования фор- мальных параметров указанных типов. В Standard С операнд операции sizeof не может быть 1-значением, обозначающим битовое поле в структуре или объе- динении, однако это разрешено в некоторых реализациях, не принадлежащих к Standard С; в этих случаях возвращается размер объявленного типа компо- нента (обозначение битового поля при этом игнорируется).
Выражения 241 Пример Ниже приведены несколько примеров применения операции sizeof. Предполагает- ся, что объекты типа short занимают 2 байта памяти, объекты типа int — 4. Выражение Значение sizeof(char) sizeof(int) short s;... sizeof(s) short s;... sizeof(s+0) short sa[10];... sizeof (sa) extern int ia[]; ... sizeof(ia) 1 4 2 4 (результат операции + имеет тип int) 20 ошибка (тип неполный) Если операция sizeof выполняется над выражением, тип этого выражения определяется компилятором, но само выражение не вычисляется. Если аргу- мент sizeof — имя типа, возможно, как побочный эффект, объявление типа. Если в выражении sizeof указано имя типа, соответствующего массиву пе- ременной длины, и размер этого массива влияет на результат выражения sizeof, тогда выражение массива переменной длины оценивается полностью, включая побочные эффекты. Если размер такого массива не влияет на резуль- тат операции sizeof, этот размер может вычисляться либо не вычисляться. Пример В следующих операторах, значение j не меняется, значение п возрастает. Функция f(n) может вызываться либо не вызываться; в вызове функции нет необходимости, поскольку в операции sizeof вычисляется только размер указателя на массив пере- менной длины, а этот размер не зависит от размера самого массива. size_t z = sizeof (j++); size_t x = sizeof(int [n++]); size_t у = sizeof (int (*)[f(n)J); Оператор sizeof(struct S {int a,b;}) создает новый тип в Standard С, хотя примене- ние его вряд ли может считаться признаком хорошего стиля программирования. Возможны ссылки на созданный тип — например, в последующих объявлениях пе- ременных (только не в C++). Ссылки: регулярные типы (массивы) 5.4; совместимость с C++ 7.15; типы функций 5.8; size_t 11.1; единицы памяти 6.1.1; имена типов 5.12; беззнаковые типы 5.1.2; обычные бинарные преобразования 6.3.4; массивы переменной длины 5.4.5; тип void 5.9. 7.5.3 Унарные операции амину с» и «плюс» В унарной операции «минус» меняется знак операнда, унарная операция «плюс» (введенная в Standard С) просто возвращает значение операнда: унарное-выражение-яминус» : — выражение-приведения унарное выражение-яплюс» : (С89) + выражеиие-приведения
242 Глава 7 Операнды обеих операций могут иметь любой арифметический тип и под- вергаются обычным унарным преобразованиям. Результат имеет тип, образуе- мый продвижением целых, и не является 1-значением. Унарное выражение «минус» —е — это укороченный вариант выражения 0-(е); оба выражения выполняют одни и те же действия. Если операнд имеет тип целого со знаком или (действительного) с плавающей точкой, результат может оказаться неопределенным в результате переполнения. Если операнд к является беззнаковым целым, результат также является беззнаковым целым и равен 2П—к, где п — число бит в представлении результата. Поскольку ре- зультат беззнаковый, он не может быть отрицательным. Как ни странно, усло- вие (—х)+х=О выполняется не только для целого х со знаком, ио также и для беззнакового целого х. Унарная операция «плюс» +е — это укороченная запись выражения 0+(е). Ссылки: типы с плавающей точкой 5.2; целые типы 5.1; 1-значения 7.1; переполнение 7.2.2; операция вычитания — 7.6.2; беззнаковые типы 5.1.2; обычные унарные преобразования 6.3.3. 7.5.4 Логическое НЕ Унарная операция ! вычисляет логическое НЕ операнда. Операнд может иметь любой скалярный тип. : выражение-логического-Н Е : ! выражениеприведения Операнд подвергается обычным унарным преобразованиям. Результат опе- рации ! имеет тип int и значение 1 в случае нулевого операнда (нулевого указа- теля либо значения 0.0 с плавающей точкой) и 0, если операнд ненулевой (не нулевой указатель и не 0.0). Результат не является 1-значением. Выражение !(х) идентично, по смыслу, выражению (х)==0. Пример ttdefine assert(x,s) if (!(х)) assettion_failure(s) assert(num_cases > 0, "Тестировать нечего."); average = total j>oints/num_cases ; Макрос assert применяется для предотвращения ошибки в результате деления на нуль, которую, возможно, будет трудно локализовать. Предполагается, что asserti- on_failure — это функция со строковым аргументом, выводимым как сообщение об ошибке. Макрос assert, аналогичный указанному, определен в стандартном заголо- вочном файле assert.h. Ссылки: assert 19.1, операция равенства == 7 6.5, типы с плавающей тсг-кой 5 2, целые типы 5.1, 1-значение 7.1; ссылочные типы 5.3, скалярные типы гп 5, обычные унарные преобразова- ния 6.3 3. 7.5.5 Побитовое НЕ В унарной операции ~ вычисляется побитовое НЕ операнда: выражение-побитового-НЕ : - выражение приведения
Выражения 243 Операнд, который может иметь любой целый тип, подвергается обычным унарным преобразованиям. Каждый бит в двоичном представлении -е имеет значение, обратное значению соответствующего бита в операнде е. Результат не является 1-значением. Пример Если i — 16-битовая целая переменная со значением OxFOFO (11110000111IOOOO2), тогда значение -i равно OxOFOF (00001111000011112). Поскольку представление целых со знаком может зависеть от реализации, результат операции побитового НЕ (~) может оказаться немобильным. Для обеспечения мобильности следует использовать эту операцию только с беззна- ковыми операндами. В случае беззнакового операнда е, если этот операнд пре- образуется в тип unsigned, -е будет равно UINT_MAX—е, если в тип unsigned long — ULONG_MAX-e. Значения UINT_MAX и ULONG_MAX в Standard С определены в заголовочном файле limits.h. Ссылки: целые типы 5.1; limits.h 5.1.1; 1-значение 7.1; типы са знаком 5.1.1; беззнаковые типы 5.1.2; обычные унарные преобразования 6.3.3. 7.5.6 Адресная операция Унарная операция & возвращает указатель на операнд: адресное-выражение : & выражение-приведения Операндом операции & должно быть именующее выражение для функции или 1-значение, обозначающее некоторый объект. Если операнд является 1-значением, обозначаемый им объект не должен быть объявлен с классом па- мяти register и не может быть битовым полем. Если операнд операции & имеет тип Т, тогда результат имеет тип «указателя на объект типа Т». Операнд опе- рации & не подвергается обычным преобразованиям, результат операции не является 1-значением. Выполнение адресной операции над именующим выражением для функции дает указатель на эту функцию. Поскольку, согласно правилам обычных преоб- разований, именующее выражение для функции преобразуется в указатель на нее, необходимость в выполнении операции & над функцией возникает редко. В некоторых реализациях, предшествовавших Standard С, это даже запрещено. Пример extern int f() ; int (*fp)О; fp = sf; /* Правильно; & возвращает указатель на f */ fp = f; / * Правильно; указатель на f получается в результате обычных преобразований */ Указатель на функцию, возвращенный адресной операцией, действителен на протяжении выполнения программы С, указатель на объект — до тех пор, пока этот объект остается в распределенной ему области памяти. Если операнд операции & является 1-значением, обозначающим статически размещенную
244 Глава 7 переменную, указатель действителен на протяжении выполнения программы. Если операнд обозначает переменную автоматического класса памяти, указа- тель действителен на протяжении активности блока, в котором эта перемен- ная объявлена. Если операнд обозначает динамически размещаемый (напри- мер, при помощи функции malloc) объект, указатель действителен до освобо- ждения памяти от этого объекта. Существует одна особенность, отличающая выполнение адресной операции в Standard С от выполнения ее же в традиционном С. В Standard С выполнение адресной операции над 1-значением «массив типа Т» дает значение типа «ука- затель на массив типа Т», тогда как во многих компиляторах, предшествовав- ших Standard С, операция &а трактуется так же, как и а — то есть как указа- тель на первый элемент массива а. Эти две интерпретации несовместимы меж- ду собой, но правило, принятое в Standard С, в большей мере соответствует интерпретации самой операции &. Пример В следующем фрагменте программы Standard С, все операции присваивания значе- ний переменной р, как и переменной i, равнозначны: int а[10], *р, i; р = Sa[0]; р = а; р = *Sa; i = а[0]; i = *а; i = **Sa; Ссылки: регулярный тип 5,4; именующее выражение для функции 7.1; тип функции 5.8; 1-зна- чение 7,1; ссылочный тип 5.3; класс памяти register 4.3. 7.5.7 Опосредование (разыменование) Унарная операция * выполняет опосредование (разыменование) через ука- затель. Операции & и * обратны друг другу: если х — переменная, результатом операции *&х будет х. выражение-опосредования : * выражение-приведения Операнд должен быть указателем. Если это «указатель на объект типа Т>> и тип Т объявлен, возможно, с квалификаторами, результат также имеет тип Т (с теми же квалификаторами). Если указатель указывает на некоторый объ- ект, результатом будет 1-значение, обозначающее этот объект. Если указатель ссылается на функцию, результатом будет именующее выражение для этой функции. Пример int i, const *Р. int *рс; Р Si ; /* р ссылается на переменную i */ *Р = 10; /* переменной 1 присваивается значение 10 */ рс = Si , /* рс также ссылается на переменную i */ *рс = 10; /* ошибка, *рс имеет тип 'const int' */
Выражения 245 Операнд операции опосредования подвергается обычным унарным преобра- зованиям. Выполняются только преобразования именующих выражений для массивов и функций в указатели. Таким образом, если f — именующее выра- жение для функции, выражения *&f и *f оказываются равнозначными. В по- следнем случае, f преобразуется в &f в процессе обычных преобразований. Если операция * выполняется над указателем, содержащим неверную ссыл- ку, либо над нулевым указателем, результат оказывается неопределенным. В некоторых реализациях попытка разыменования нулевого указателя приво- дит к останову программы, в других нулевой указатель воспринимается как ссылающийся на область памяти с неопределенным содержимым. Ссылки: регулярные типы 5.4; именующие выражения для функций 7.1; типы функций 5.8; 1-значение 7.1; ссылочные типы 5.3; обычные унарные преобразования 6.3.3. 7.5.8 Префиксные операции положительного и отрицательного приращения Унарные операции ++ и — применяются, соответственно, для положи- тельного и отрицательного приращения значения операнда и возвращают зна- чение операнда после приращения. Это операции с побочными эффектами. Су- ществуют аналогичные постфиксные операции. преинкрементное-выражение : ++ унарное-выражение предекрементное-выражение : — унарное-выражение Операнды обеих операций должны быть модифицируемыми 1-значениями любого действительного арифметического или ссылочного типа. В операции ++ операнд увеличивается на единицу, в операции — — уменьшается. Обе операции возвращают новое значение операнда. Результат не является 1-значе- нием. Операнд и константа 1 подвергаются до выполнения операции сложения или вычитания обычным бинарным преобразованиям. При записи нового зна- чения выполняются обычные преобразования присваивания. Тип результата совпадает с типом операнда до преобразования. Если операнд является указателем — например, «указателем на объект типа Т», результатом операции ++ будет перемещение указателя вперед отно- сительно объекта, на который он указывал. Если бы указатель указывал на элемент массива объектов типа Т, после выполнения операции он указывал бы на следующий элемент этого массива. В компьютере с побайтовой адресацией, смещение указателя составило бы sizeof(T) байт. В случае операции — указа- тель переместился бы на предыдущий элемент массива объектов типа Т. Пример Функция strrev копирует символы первого аргумента во второй в обратном порядке: int strrev( const char *sl, char *s2 ) { const char *p = si; while (*p++); /* Определение конца первой строки. */ —р; /* Выход за пределы строки: возврат на 1 позицию. *7 /* Теперь копирование символов в обратном порядке. */
246 Глава 7 while (р > si) *s2++ = *—p; *s2 = '\0' ; /* Запись нуля в конец выходной строки. */ ) Результаты выполнения рассмотренных операций над целыми со знаком или действительными с плавающей точкой, в случае переполнения, могут быть непредсказуемыми. Результат (положительного) приращения макси- мального представимого беззнакового целого значения — нуль. Результат от- рицательного приращения беззнакового целого значения 0 равен максималь- ному представимому значению этого типа. Выражение ++е равнозначно, по смыслу, выражению е+=1, выражение —е — выражению е—=1. Если значение, возвращаемое операцией положи- тельного или отрицательного приращения, не используется, префиксная и постфиксная формы операций идентичны — то есть оператор е++; иденти- чен оператору ++е;, оператор е—; — оператору —е;. Ссылки: сложение 7.6.2; регулярные типы (массивы) 5.4; преобразования присваивания 6.3.2; составное присваивание 7.9.2; операторные выражения 8.2; типы с плавающей точкой 5.2; целые типы 5.1; 1-значение 7.1; переполнение 7.2.2; ссылочные типы 5.3; постфиксные вы- ражения положительного и отрицательного приращения 7.4.4; скалярные типы гл. 5; типы со знакам 5.1.1; вычитание 7.6.2; беззнаковые типы 5.1.2; обычные бинарные преобразования 6.3.4. 7.6 Выражения бинарных операций Выражение бинарной операции состоит из двух выражений, разделенных знаком бинарной операции. Термин «бинарная» означает лишь наличие у опе- рации двух операндов и не имеет ничего общего с бинарной (двоичной) систе- мой счисления. Виды бинарных выражений и типы их операндов перечисле- ны, в порядке убывания приоритета, в таблице 7.4. Все операции обладают ле- вой ассоциативностью. Таблица 7.4. Выражения бинарных операций Выражение Операция Операнды Результат мультипликативное 7 арифметические арифметический % целые целый аддитивное + арифметические арифметический указатель + целый или целый + указатель указатель - арифметические арифметический указатель - целый указатель указатель - указатель целый сдвиг « » целые целый отношение <<=>=> арифметические или указатели 0 ИЛИ 1 равенство -= 1 = арифметические или указатели 0 или 1 побитовое И & целые целый
Выражения 247 Выражение Операция Операнды Результат побитовое исключающее ИЛИ целые целый побитовое ИЛИ 1 целые целый Перед выполнением каждой из бинарных операций, рассматриваемых в этом разделе, оба операнда оцениваются полностью, но не в какой-либо опре- деленной последовательности. Ссылки: последовательность оценки 7.12; приоритет 7.2.1. 7.6.1 Мультипликативные операции Существуют три мультипликативные операции: * (умножение), / (деление) и % (остаток). Все они имеют одинаковый приоритет и левую ассоциатив- ность: мультипликативное-выражение : выражение-приведения мулыпипликативное-выражение мульт.-зн. выражение-приведения мульт.-зн. : один из * / % Ссылки: приоритет 7.2.1. Умножение Знаком * обозначается бинарная операция умножения. Каждый из операн- дов может иметь любой арифметический тип. Операнды подвергаются обыч- ным бинарным преобразованиям, тип результата равен типу операндов после преобразования. Результат не является 1-значением. Если оба операнда целые, выполняется целочисленное умножение, если с плавающей точкой — умноже- ние в системе с плавающей точкой. Переполнение в операции умножения, операнды которой (после преобразо- вания) имеют тип целого со знаком или действительного с плавающей точкой, может приводить к непредсказуемым результатам. Если операнды — беззна- ковые целые, результат сравним по модулю 2п с истинным математическим результатом операции (где п — число бит в представлении беззнакового цело- го). Ссылки: арифметические типы гл. 5; типы с плавающей точкой 5.2; целые типы 5.1; 1-значение 7.1; последовательность оценки 7.12; переполнение 7.2.2; типы со знаком 5.1.1; беззнаковые типы 5.1.2; обычные преобразования 6.3.4. Деление Знаком / обозначается бинарная операция деления. Каждый из операндов может иметь любой арифметический тип. Операнды подвергаются обычным бинарным преобразованиям, тип результата равен типу операндов после пре- образования. Результат не является 1-значением. В случае операндов с плавающей точкой, выполняется деление в системе с плавающей точкой. При целых операндах, если математическое частное не равно в точности целому значению, дробная часть отбрасывается (усечение в сторону нуля). В реализациях, предшествовавших С99, отрицательные зна-
248 Глава 7 чения могут усекаться в сторону, противоположную нулю. Библиотечные функции div и Idiv всегда были формально правильно определены в отноше- нии отрицательных операндов. Переполнение в операции деления, операнды которой (после преобразова- ния) имеют тип целого со знаком или действительного с плавающей точкой, может приводить к непредсказуемым результатам. Обратите внимание, что переполнение может происходить в целых значениях со знаком, представлен- ных в форме дополнения до двух, если максимальное представимое отрица- тельное значение поделить на —1; в этом случае, математическое частное ока- зывается равным значению, которое непредставимо в данной форме. В опера- ции деления с беззнаковыми операндами переполнения не происходит. Результат деления целого значения либо значения с плавающей точкой на нуль не определен. Ссылки: орифметические типы гл. 5; div 17.1; типы с плавающей точкой 5.2; целые типы 5.1, Idiv 17 1; 1-значение 7 1; переполнение 7.2.2; типы со знаком 5.1.1; беззнаковые типы 5.1 2, обычные преобразования 6.3.4. Остаток Знаком % обозначается бинарная операция вычисления остатка деления первого операнда на второй. Каждый из операндов может иметь любой целый тип. Операнды подвергаются обычным бинарным преобразованиям, тип ре- зультата равен типу операндов после преобразования. Результат не является 1-значением. Библиотечные функции div, Idiv и fmod вычисляют также остат- ки деления целых значений и значений с плавающей точкой. Если частное а/b представимо, всегда выполняется условие (a/b)*b + а%Ь = а — следовательно, операция вычисления остатка связана с операцией целочис- ленного деления. Как было сказано в предыдущем разделе, результат опера- ции деления, если один из операндов отрицателен, до С99 зависел от реализа- ции. Как следствие, операция вычисления остатка также оказывалась завися- щей от реализации. Пример Функция gcd вычисляет наибольший общий делитель, используя алгоритм Эвкли- да. Результат равен наибольшему целому, на которое х и у делятся без остатка: unsigned gcd(unsigned х, unsigned у) ( while ( у '= 0 ) { unsigned temp = у; у = х % у; х = temp; 1 return х, 1 Результат операции вычисления остатка может быть непредсказуемым, если произойдет переполнение при делении операндов. Следует иметь в виду, что в случае деления целых со знаком, представленных в форме дополнения до двух, переполнение может возникнуть при делении максимального представимого от- рицательного числа на -1; в этом случае, математическое частное оказывается равным значению, которое непредставимо в данной форме, а потому результат оказывается непредсказуемым даже в случае представимого остатка (нуль). В операции деления с беззнаковыми операндами переполнения не происходит.
Выражения 249 Если второй операнд равен нулю, результат вычисления остатка оказывает- ся неопределенным. Ссылки: div, Idiv 17.1; fmod 17.3; целые типы 5.1; 1-значение 7.1; переполнение 7.2.2; типы со знаками 5.1.1; беззнаковые типы 5.1.2; обычные бинарные преобразования 6.3.4. 7.6.2 Аддитивные операции Две аддитивные операции-----F (сложение) и — (вычитание), — обладают одинаковым приоритетом и левой ассоциативностью: аддитивное-выражение : мультипликативное-выражение аддитивное-выражение адд.-зн. мультипликативное-выражение адд.-зн. : один из + - Сложение Знаком + обозначается бинарная операция сложения. Операнды подверга- ются обычным бинарным преобразованиям. Оба операнда могут иметь ариф- метические типы, или же один из них может быть указателем на объект, дру- гой — целым. Никакие другие типы операндов не допускаются. Результат не является 1-значением. Если оба операнда имеют арифметические типы, тип результата совпадает с типом операндов после преобразования. В случае целых операндов, выполня- ется целочисленное сложение, в случае операндов с плавающей точкой — сло- жение в системе с плавающей точкой. При сложении указателя р с целым значением k, предполагается, что объ- ект, на который ссылается указатель р, располагается внутри массива таких объектов либо является отдельным объектом, располагающимся непосредст- венно за последним элементом этого массива, и результатом операции являет- ся указатель на объект, располагающийся на расстоянии k объектов от того, на который ссылается указатель р. Например, р+1 — это ссылка на объект, располагающийся непосредственно за объектом, на который ссылается указа- тель р, р+(~1) — на объект, располагающийся перед этим объектом. Если ука- затель р или p+k располагается не внутри массива (и не непосредственно за ним), результат оказывается непредсказуемым, р не может быть указателем на функцию или иметь тип void *. Пример Предположим, мы работаем на компьютере с побайтовой адресацией, в котором тип int занимает 4 байта. Пусть а — массив из десяти целых, начало которого располага- ется по адресу 0x100000, ip — указатель на целое, которому мы присвоили ссылку на первый элемент массива a, i — целая переменная со значением 6. То есть мы имеем: int *ip, i, а[10]; ip = Sa[0] ; i = 6; Каким будет значение ip+i? Поскольку целые имеют длину 4 байта, ip+i равно 0x100000+4*6 — то есть 0x100018 (24ю = 18ц;).
250 Глава 7 Пример Указатели на многомерные массивы и массивы переменной длины (С99) действуют аналогично. int п = 3; int m = 5; double rect[n][m] ; double (*p) [tn] ; p = rect; /* то же, что и p = SrectfO]; */ p++; /* теперь p == Srect [1] */ Указатель p ссылается на объект типа double[m] — массив из пяти значений с пла- вающей точкой двойной точности, аналогичный строке матрицы rect. Выражение р++ перемещает указатель р на следующую строку матрицы, увеличивая его значе- ние на 5*sizeof(double) единиц памяти. Результат операции сложения может быть непредсказуемым, если происхо- дит переполнение при операндах, являющихся целыми со знаком или дейст- вительными с плавающей точкой (после преобразования), или когда один из операндов является указателем. Если оба операнда — беззнаковые целые, ре- зультат сравним по модулю 2п с истинным математическим результатом опе- рации (здесь п — число битов в представлении результата). Ссылки: регулярные типы 5.4; типы с плавающей точкой 5.2; целые типы 5.1; 1-значение 7.1; многомерные массивы 5.4.2; последовательность оценок 7.12; переполнение 7.2.2; представ- ление указателей 5.3.2; ссылочные типы 5.3; скалярные типы гп. 5; типы со знаком 5.1.1; без- знаковые типы 5.1.2; обычные бинарные преобразования 6.3.4; массивы переменной дли- ны 5.4.5. Вычитание Бинарная операция вычитания. Операнды подвергаются обычным бинар- ным преобразованиям. Оба операнда могут иметь арифметические типы либо быть указателями на объекты совместимых типов (без учета квалификаторов типов); иначе, возможно сочетание указателя (левый операнд) с целым. Ре- зультат не является 1-значением. Если оба операнда имеют арифметические типы, тип результата совпадает с типом операндов после преобразования. Если оба операнда — целые, выпол- няется целочисленное вычитание, если с плавающей точкой — выполняется вычитание в системе с плавающей точкой. Притер Результат вычитания одного беззнакового целого из другого также является беззна- ковым целым, а потому не может быть отрицательным. Тем не менее, в отношении беззнаковых целых всегда выполняются тождества наподобие следующих: (а+(b-а)) == b (а-(а-Ь)) == Ь Вычитание целого из указателя аналогично уже рассмотренной нами опера- ции сложения целого с указателем. При вычитании целого значения к из ука- зателя р предполагается, что объект, на который ссылается этот указатель, располагается в массиве таких объектов или непосредственно после массива и что результат представляет указатель на объект внутри этого же массива (или непосредственно за ним), располагающийся на расстоянии —к объектов от того объекта, на который ссылается указатель р. Например, указатель р—1
Выражения 251 ссылается на объект, предшествующий тому, на который ссылается указатель р, р—(—1) — на следующий. Если указатель р или р—к не располагается в ука- занном массиве (или непосредственно за ним), результат оказывается неопре- деленным. р не может быть указателем на функцию или иметь тип void *. Если даны два указателя р и q одного типа, разность р—q равна целому зна- чению к, сложение которого с указателем q дает указатель р. Эта разность име- ет тип целого со знаком ptrdiff_t, определенный в stddef.h (в реализациях, предшествовавших Standard С, это может быть тип int или long). Результат яв- ляется вполне определенным и мобильным только в том случае, если оба указа- теля ссылаются на элементы одного и того же массива либо на объект, распола- гающийся сразу за этим массивом. Разность к равна разности индексов элемен- тов массива, на которые ссылаются указатели. Если р или р—q ссылается на область, располагающуюся вне массива, результат оказывается неопределен- ным. р и q не могут быть указателями на функции или иметь тип void *. Результат операции сложения может быть непредсказуемым, если происхо- дит переполнение при операндах, являющихся целыми со знаком или дейст- вительными с плавающей точкой (после преобразования), или когда один из операндов является указателем. Если оба операнда — беззнаковые целые, ре- зультат сравним по модулю 2п с истинным математическим результатом опе- рации (здесь п — число бит в представлении результата). Ссылки: регулярные типы 5.4; типы с плавающей точкой 5.2; целые типы 5.1; 1-значение 7.1; переполнение 7.2.2; представление указателей 5.3.2; ссылочные типы 5.3; ptrdiff_t 11.1; ска- лярные типы гл. 5; типы со знаком 5.1.1; совместимость типов 5.11; квалификаторы типов 4.4.3; беззнаковые типы 5.1.2; обычные бинарные преобразования 6.3.4. 7.6.3 Операции сдвига Знаком « обозначается бинарная операция сдвига влево, знаком » — би- нарная операция сдвига вправо. Обе операции имеют одинаковый приоритет и левую ассоциативность: выражение-сдвига: аддитивное-выражение выражение-сдвига знак-сдв. аддитивное-выражение знак-сдв. : один из Каждый операнд должен иметь целый тип. Каждый операнд в отдельности подвергается обычным унарным преобразованиям, тип результата совпадает с типом левого операнда после преобразования. В реализациях, предшество- вавших Standard С, оба операнда подвергаются обычным бинарным преобразо- ваниям. Результат не является 1-значением. Первый операнд — это сдвигаемое значение, второй указывает число битов, на которое это значение сдвигается. Направление сдвига указывается знаком операции. Знак « указывает сдвиг значения левого операнда влево. При этом, лишние биты слева отбрасываются, освободившиеся биты справа сбра- сываются в нуль. Знак » указывает сдвиг значения левого операнда вправо. Лишние биты справа отбрасываются, значения освободившихся битов слева зависит от типа левого операнда после преобразования: если он имеет беззна- ковый тип либо неотрицателен, эти биты равны нулю, если тип со знаком и от-
252 Глава 7 рицательное значение — в зависимости от реализации, сбрасываются в нуль либо заполняются значениями отбрасываемых битов слева. Следовательно, если левый операнд операции >> имеет тип со знаком и отрицателен, а правый операнд не равен нулю, эта операция оказывается не мобильной. Если значение правого операнда отрицательно, результат операции сдвига оказывается неопределенным, поэтому нельзя рассчитывать на то, что при от- рицательном правом операнде в операции « произойдет сдвиг вправо, а в опе- рации >> — влево. Кроме того, результат любой из операций оказывается не- определенным, если значение правого операнда оказывается больше числа би- тов в левом (после преобразования). Правый операнд может быть равен нулю; в этом случае, никакого сдвига не происходит, и результат равен значению ле- вого операнда после преобразования. Пример Полагаясь на приоритеты и ассоциативность операций, можно составлять выражения, которые будут визуально привлекательны, но семантически не вполне отчетливы: Ь « 4 » 8 Если Ь — 16-битовое беззнаковое значение, результатом будут 8 средних бит этой переменной. Как всегда, чтобы избежать путаницы, следует использовать скобки: (Ь « 4) » 8 Пример Ниже приведен пример применения операции сдвига беззнакового значения для вы- числения наибольшего общего делителя двух целых. Этот метод сложнее алгоритма Эвклида, но он может оказаться быстрее, поскольку в некоторых реализациях С операция вычисления остатка — особенно, при беззнаковых операндах, несколько медленна. unsigned binary_gcd(unsigned х, unsigned у) { unsigned temp; unsigned common_power_of_two = 0; if (x == 0) return у; /* Особые случаи */ if (у == 0) return x, /* Определение наибольшей степени двух, делящей х и у. */ while ( ((х | у) S 1) == 0) { х = х >> 1; /* или: "х »= 1;" */ у = у » 1; ++common_power_of_two; ) while ((х s 1) == 0) х = х >> 1, while (у) { /* x - нечетно, у - ненулевое. */ while ((у S 1) ==0) у = у >> 1; /* х и у нечетны. */ temp = у; if (х > у) у = х - у; else у = у - х; х = temp; /* Теперь х равно прежнему значению у, которое нечетно. Значение у четно, поскольку равно разности двух нечетных значений; значит, в следующем проходе цикла оно будет
Выражения 253 сдвинуто вправо минимум на один бит. */ 1 return (х << conunon_power_of_two) ; 1 Ссылки: целые типы 5.1; 1-значения 7.1; приоритет 7.2.1; типы со знаком 5.1.1; беззнаковые типы 5.1.2; обычные унарные преобразования 6.3.3. 7.6.4 Отношения В бинарных операциях <, <=, > и >= происходит сравнение операндов: выражение-отношения : выражение-сдвига выражение-отношения зн.-отношения выражение-сдвига зн,-отношения : один из Операнды подвергаются обычным бинарным преобразованиям. Операнды могут иметь действительные (не комплексные) арифметические типы либо быть указателями на значения совместимых неполных типов. Наличие квали- фикаторов в объявлениях типов указателей не влияет на сравнение. Результат всегда имеет тип int и значение 0 или 1. Результат не является 1-значением. Знаком < обозначается отношение «меньше чем», знаком <= — «меньше или равно», знаком > — «больше чем» и знаком >= — «больше или равно». Результат равен 1, если отношение выполняется, 0 — если не выполняется. Реализация арифметики с плавающей точкой в Standard С может включать неупорядочиваемые (unordered) значения наподобие NaN. Операции отноше- ний над такими значениями могут порождать исключения и возвращать не- верные значения. В разделе 17.16 рассматриваются функции, способные обра- батывать такие ситуации. В случае целых операндов выполняется целочисленное сравнение (со зна- ком либо беззнаковое), в случае операндов с плавающей точкой — сравнение в системе с плавающей точкой. Если операнды — указатели, результат зави- сит от относительного расположения в адресном пространстве двух объектов, на которые эти указатели ссылаются; результат оказывается определенным только в случае, если объекты, на которые ссылаются указатели, располагают- ся внутри одного массива или одной структуры, и в этом случае «больше» оз- начает «имеет больший порядковый номер (индекс)» или «объявлено позже в списке компонентов». Как частный случай в массивах указатель на объект, располагающийся за последним элементом массива, вполне определен и счита- ется больше всех указателей, ссылающихся на объекты, принадлежащие это- му массиву. Все указатели на члены одного объединения считаются равными. Пример Если написать выражение вида 3<х<7, его смысл будет отличаться от того, какой бы оно имело, будь выражением чисто математическим. Ввиду левой ассоциативности операций отношения, данное выражение интерпретируется как (3<х)<7. Поскольку же результат операции (3<х) — всегда 0 или 1, он всегда меньше 7, а потому резуль- тат выражения 3<х<7 всегда равен 1. Если же трактовать данное выражение как чис- то математическое, придется привлечь логическую операцию И: 3<х && х<7.
254 Глава 7 Пример Следует соблюдать осторожность в операциях отношений над операндами смешан- ных типов. Вот особенно яркий пример подобной операции: -1 < (unsigned) О На первый взгляд, в этом выражении нет ничего сложного и оно всегда возвращает 1 (true), поскольку -1 меньше, чем 0. Однако в процессе обычных бинарных преоб- разований, значение -1 меняет тип на unsigned и становится равным 1. Поэтому данное выражение всегда возвращает 0 (false). В некоторых ранних реализациях разрешается сравнение указателей с це- лыми, что фактически недопустимо. Значения, при этом, могут сравниваться как беззнаковые либо со знаком. Ссылки: арифметические типы гл. 5; регулярные типы 5.4; операция побитового И & 7.6.6; совместимые типы 5.1 1; типы с плавающей точкой 5.2, неполные типы 5.4, 5.6.1; целые типы 5.1; операция логического И && 7.7; 1-значение 7 1; NaN 5.2; ссылочные типы 5.3; приоритет 7.2.1; типы со знаком 5.1.1; квалификаторы типов 4.4.3; беззнаковые типы 5.1.2, обычные би- нарные преобразования 6.3.4. 7.6.5 Операции равенства В бинарных операциях == и != проверяется равенство двух операндов: выражение-равенство. : выражениеотношения выражение-равенство зн.-равенства выражениеотношения зн.-равенства : один из == != Возможны следующие сочетания типов операндов: 1. Оба операнда арифметических типов — возможно, комплексных. 2. Оба операнда — указатели на объекты совместимых типов либо имеют тип void *. 3. Один операнд — указатель на объект неполного типа, другой имеет тип void *. Первый операнд также будет преобразован в тип void *. 4. Один операнд — указатель, другой — нулевая ссылочная константа (целая константа 0). В случае операндов-указателей наличие или отсутствие квалификаторов типов в объявлениях объектов, на которые эти указатели ссылаются, не опре- деляет допустимость сравнения либо его результат. Арифметические операн- ды подвергаются обычным бинарным преобразованиям. Результат всегда име- ет тип int и значение 0 или 1. Результат не является 1-значением. В случае целых операндов, выполняется целочисленное сравнение, в случае операндов с плавающей точкой — сравнение в системе с плавающей точкой. Операнды-указатели считаются равными тогда и только тогда, когда выполня- ется одно из следующих условий. 1. Оба указателя ссылаются на один и тот же объект или функцию. 2. Оба указателя — нулевые. 3. Оба указателя ссылаются на объект, располагающийся непосредствен- но за последним элементом массива таких объектов.
Выражения 255 В операции == проверяется отношение «равно», в операции != отношение «не равно». Результат равен 1, если отношение выполняется, 0 — если не вы- полняется. В случае комплексных операндов (С99), условие равенства означает равен- ство действительных и мнимых частей операндов. Если один из операндов — действительный, другой — комплексный, сравнение выполняется так, как если бы действительный операнд был преобразован в комплексный. Оба опе- ранда подвергаются обычным бинарным преобразованиям для приведения к одинаковой точности. Сравнение структур и объединений не допускается, несмотря на допусти- мость выполнения операций присваивания над переменными и значениями этих типов. Пробелы в структурах и объединениях, образуемые вследствие ус- ловий выравнивания, могут быть заполнены любыми значениями, и их учет в операциях сравнения требовал бы недопустимо большого объема непроизво- дительных действий. Бинарные операции равенства имеют одинаковый приоритет (ниже приори- тета операций <, <=, > и >=) и левую ассоциативность. Пример Если написать выражение вида х=у==7, его смысл будет отличаться от того, какой бы оно имело, будь выражением чисто математическим. Ввиду левой ассоциативно- сти операций отношения, данное выражение интерпретируется как (х==у)==7. По- скольку же результат операции (х==у) — всегда 0 или 1, он никогда не равен 7, а по- тому результат выражения х==у==7 всегда равен 0. Если же трактовать данное вы- ражение как чисто математическое, придется привлечь логическую операцию И: х=у && у=7 Пример Побитовые операции включают операцию исключающего ИЛИ, не имеющую логи- ческого аналога. Логическую операцию исключающего ИЛИ можно реализовать при помощи операции !=. Выражение a<b != c<d возвращает 1, если только одно из выражений а<Ь и c<d равно 1, в остальных случая — 0. Если каждый из операндов может возвращать не только 0 или 1, к каждому из них можно применить унарную операцию: !х != !у возвращает 1, если только один из операндов х и у не равен нулю, О — в остальных случаях. Подобным образом операция == служит логическим ана- логом операции эквивалентности (EQV). Пример Очень распространенная ошибка при программировании в С — указание одного зна- ка = (присваивание) вместо двух == (сравнение). В некоторых языках программиро- вания сравнение обозначается именно одним знаком =. Хороший стиль программи- рования предполагает использование, в сочетаниях с операторами присваивания, сравнений в форме !=0 — это не позволяет путать одно с другим. К примеру, трудно определить с первого взгляда, не содержит ли опечатку следующий цикл: while (х = next_item() ) ( /* Может быть, правильно будет "x==next_item()" ?? */
256 Глава 7 Если приведенный вариант правилен, намерения программиста были бы более оче- видны, когда оператор имел бы вид: while ( (х = next_itemO) != 0) { 1 Ссылки: условия выравнивания 5.6.4, 6.1.3; побитовые операции 7.6.6; совместимые типы 5.1 1; логические операции 7.5.4, 7.7; 1-значение 7.1; нулевой указатель 5.3.2; ссылочные типы 5.3; приоритет 7.2.1; оператор присваивания = 7.9.1; квалификаторы типов 4.4.3; обычные би- нарные преобразования 6.3.4; void * 5.3.1. 7.6.6 Побитовые операции Знаками &, ', и | обозначаются, соответственно, бинарные побитовые опера- ции И, исключающее ИЛИ и ИЛИ. Каждая из них имеет левую ассоциатив- ность, определяющую, наряду с приоритетами, последовательность оценок. Операнды должны быть целыми и подвергаются обычным бинарным преобра- зованиям. Тип результата совпадает с типом операндов после преобразования. Результат не является 1-значением: выражение-побитового-ИЛИ : выражение-побитового-исключающего-ИЛИ выражение-побитового-ИЛИ | выражение-побитового исключающего-ИЛИ выражение-побитового-исключающего-ИЛИ : выражение-побитового-И выражение-поб итового-исключающего-ИЛИ выражение-побитового-И выражение-побитового-И : выражение-равенства выражение-побитового-И & выражение-равенства Каждый бит результата в этих операциях равен некоторой булевой функции над соответствующими битами операндов (после преобразования): • Функция & (И) возвращает бит 1, если биты обоих аргументов равны 1, иначе возвращает бит 0. • Функция ~ (исключающее ИЛИ) возвращает бит 1, если бит одного аргу- мента равен 1, бит другого — 0; если биты обоих аргументов равны 1 или 0, функция возвращает бит 0. е Функция | (ИЛИ) возвращает бит 1, если бит хотя бы одного из аргумен- тов равен 1, иначе возвращает бит 0. Схематически, это выглядит следующим образом: О ь а&Ь а~Ь а | Ь 0 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 1 0 1 1
Выражения 257 Все побитовые операции коммутативны и ассоциативны, и компилятор мо- жет выполнять перестановку компонентов выражения, содержащего данные операторы, соответственно условиям, изложенным в разделе 7.12. Чтобы код был мобильным, следует задавать побитовые операции только над беззнаковыми операндами. Использование целых операндов со знаком не порождает проблем на большинстве компьютеров, на которых эти типы пред- ставлены в форме дополнения до двух, но на других компьютерах возможны ошибки. При программировании необходимо внимательно следить за тем, чтобы не путать знаки побитовых операций & и | с их логическими эквивалентами && и 11. Побитовые операции возвращают тот же результат, что и соответствующие логические, только в случае булевых аргументов (со значениями 0 или 1) без по- бочных эффектов. Кроме того, в побитовых операциях всегда происходит оцен- ка обоих операндов, тогда как в логических, если оценки левого операнда доста- точно для определения результата, правый операнд не оценивается. Пример Если а равно 2, b равно 4, тогда а&Ь равно 0 (false), но а&&Ь равно 1 (true). 7.6.7 Примеры с целыми На следующих страницах демонстрируются использование, объявление и определение пакета «множества целых». Здесь побитовые операции исполь- зованы для реализации множеств как битовых векторов. Среди примеров — демонстрационная программа (testset.с), тестовый вывод этой программы, за- головочный файл пакета (set.h) и реализация функций в пакете (set.с). Ссылки: целые типы 5.1; логические операции && и | | 7.7; 1-значение 7.1; последователь- ность оценки 7.12; отношения 7.6.4; типы со знаками 5.1.1; беззнаковые типы 5.1.2; обычные бинарные преобразования 6.3.4. Примеры использования пакета SET: файл testset.c #include "set.h" int main(void) { print_k_of_n(0, 4) print_k_of_n(1, 4) print_k_of_n(2, 4) print_k_of_n(3, 4) print_k_of_n(4, 4) print_k_of_n(3, 5) print_k_of_n(3, 6) return 0; ) Пакет SET: вывод файла testset.c Все подмножества размера 0 множества (0, 1, 2, 3}: {) Полное число таких подмножеств равно 1. Все подмножества размера 1 множества (0, 1, 2, 3}:
258 Глава 7 {0} {1} {2} {3} Полное число таких подмножеств равно 4. Все подмножества разнера 2 множества (0, 1, 2, 3}: {0, 1} {0, 2} {1, 2} {0, 3} {1, 3} (2, 3} Полное число таких подмножеств равно 6. Все подмножества размера 3 множества (0, 1, 2, 3}: {0, 1, 2} {0, 1, 3} {0, 2, 3} {1, 2, 3} Полное число таких подмножеств равно 4. Все подмножества размера 4 множества (0, 1, 2, 3}: {0, 1, 2, 3} Полное число таких подмножеств равно 1. Все подмножества размера 3 множества (0, 1, 2, 3, 4}: {0, 1, 2} {0, 1, 3} {0, 2, 3} {1, 2, 3} {0, 1, 4} {0, 2, 4} {1, 2, 4} {0, 3, 4} {1, 3, 4} (2, 3, 4} Полное число таких подмножеств равно 10. Все подмножества размера 3 множества (0, 1, 2, 3, 4, 5} (0, 1, 2} (0, 1, 3) (0, 2, 3) (1, 2, 3) (0, 1, 4} (0, 2, 4} {1, 2, 4} (0, 3, 4} (1, 3, 4} (2, 3, 4} (0, 1, 5} (0, 2, 5} (1, 2, 5} (0, 3, 5} (1, 3, 5} (2, 3, 5} (0, 4, 5} (1, 4, 5) (2, 4, 5} (3, 4, 5} Полное число таких подмножеств равно 20. Пакет SET: файл set.h. /* set.h Пакет set предназначен для обработки множеств небольших целых, в интервале от 0 до N-1, где N — число битов в типе unsigned int. Каждое целое представлено битовым разрядом; бит i равен 1 тогда и только тогда, когда значение i принадлежит множеству. Наименее значимый бит соответствует 0. */ #include <limits.h> /* определение CHAR_BIT */ /* Для представления множеств используется тип SET. */ typedef unsigned int SET; /* SET_BITS: максимальное число битов в множестве. */ #define SET_BITS (sizeof(SET)*CHAR_BIT) /* check (i): True, если i ножет быть эленентон множества. */ #define check (i) (((unsigned) (i)) < SET_BITS) /* emptyset: множество, в котором нет ни одного элемента. */ #define emptyset ((SET) 0) /* add(s,i): добавление одного целого в множество. */ #define add(set,i) ((set) | singleset (i)) /* singleset(i): возвращает множество с одним элементом. */ #define singleset(i)(((SET) 1) « (i))
Выражения 259 /* intersect: возвращает пересечение двух множеств. */ #define intersect(setl,set2)((setl) & (set2)) /* union: возвращает объединение двух множеств. */ #define union(setl,set2)((setl) | (set2)) /* setdiff: возвращает множество элементов, принадлежащих setl или set2, но не обоим. */ #define setdiff(setl,set2) ( (setl) A (set2)) /* element: True, если i принадлежит множеству. */ #define element(i,set)(singleset((i)) & (set)) /* forallelements: Выполняет следующий оператор по однону разу для каждого элемента множества s, присваивая переменной j значение этого элемента. Для вывода всех элементов s, достаточно написать int j ; forallelements(j, s) printf("%d ", j); */ #define forallelements(j,s) \ for ((j)=0; (j)<SET_BITS; ++(j)) if (element((j),(s))) /* first_set_of_n_elements(n): Создает множество n целых co значениями от 0 до п-1. Использует свойства операции вычитания беззнаковых целых. */ ftdefine first_set_of_n_elements (n) (SET) ( (l«(n))-l) /* next_set_of_n_elements(s): На основе данного множества n эленентов, создает новое множество п элементов. Если начать с результата first_set_of_n_elements(к) и, применяя на каждом шаге next_set_of_n_elements к предыдущему результату, продолжать до тех пор, пока не получим множество, содержащее эленент m, ножно получить множество, представляющее все возможные способы выбора к из m элементов. */ extern SET next_set_of_n_elements PARMS((SET x) ) ; /* printset(s): Вывод множества в формате "(1, 2, 3, 4)". */ extern void printset(SET z); /* cardinality(s): Возвращает число элементов в s. */ extern int cardinality(SET x) ; /* print_k_of_n(k,n): Вывод всех множеств размером к, содержащих менее п элементов. Вывод наксимально возможного количества символов в каждой строке. Выводится также полное число таких множеств, которое должно быть равно n’/(k>(n-k)’), где п! = 1*2*...*п. */ extern void print_k_of_n(int k, int n); Пакет SET: файл set.c #include <stdio.h> #include "set.h"
260 Глава 7 int cardinality(SET x) ( /* Следующий цикл выполняется для каждого бита в множестве х. За каждый проход цикла, удаляется и прибавляется к значению счетчика наименьший из остающихся эленентов. Выражение (х & -х) - это множество, содержащее только наименьший элемент в х, при представлении целых в форме дополнения до двух. */ int count = 0; while (х != emptyset) ( х А= (х & -х); ++count; } return count; } SET next_set_of_n_elements(SET x) { /* В следующем блоке использованы многие необычные свойства беззнаковой арифнетики. Например: если х == 001011001111000, тогда smallest == 000000000001000 ripple == 001011010000000 new_smallest == 000000010000000 ones == 000000000000111 возвращаемое значение — 001011010000111 Процедура следующая. Определяется крайняя правая сплошная последовательность битов. Крайний левый бит этой последовательности сдвигается на одну позицию влево, остальные - в крайнюю позицию вправо. (Исходный вариант этой процедуры см. в НАКМЕМ.) */ SET smallest, ripple, new_smallest, ones; if (x — emptyset) return x; smallest = (x & -x); ripple = x + smallest; new_smallest = (ripple & -ripple); ones = ((new_smallest / smallest) » 1) - 1; return (ripple | ones); } void printset(SET z) { int first = 1; int e ; forallelements (e, z) ( if (first) printf("("); else printf (", ") ; printf ("%d", e) ; first = 0; } if (first) printf("{"); /* На случай emptyset */ printf ("}"}; /* Закрывающая скобка */ } ttdefine LINE_WIDTH 54 void print_k_of_n(int k, int n) {
Выражения 261 int count = 0; int printed_set_width = к * ((n > 10) ? 4 : 3) + 3; int sets_per_line = LINE_WIDTH / printed_set_width; SET z = first_set_of_n_elements(k); printf("\nBce подмножества размера %d множества ", к); printset (first_set_of_n_elements (n)); printf (":\n") ; do { /* Нумерация множеств. */ printset(z); if ((++count) % sets_per_line) printf (" "); else printf("\n"}; z = next_set_of_n_elements(z); } while ((z != emptyset) && !element(n, z)) ; if ((count) % sets_per_line) printf ("\n"); printf("Полное число таких подмножеств равно %d.\n", count); } 7.7 Логические операторные выражения Логическое операторное выражение состоит из двух выражений, разделен- ных одним из знаков логических операций && или 11. В других языках эти операции иногда именуются «условными» И и ИЛИ, поскольку в них второй операнд не оценивается, если значение первого дает достаточно информации для определения значения выражения. логическое-выражение-ИЛИ : логическое-выражение-И логическое-выражение-ИЛИ 11 логическое-выражение-И логическое-выражение-И : побитовое-выражение-ИЛИ логическое-выражение-И && побитовое-выражение-ИЛИ Логические операции выполняются над операндами любых скалярных ти- пов. Между типами двух операндов нет никакой связи — каждый из них, не- зависимо от другого, подвергается обычным унарным преобразованиям. Ре- зультат имеет тип int, значение 0 или 1 и не является 1-значением. И Первым (полностью) оценивается левый операнд операции &&. Если он ра- вен нулю (в том смысле, в каком это проверяется в операции ==), второй опе- ранд не оценивается и операция возвращает 0. Если левый операнд не равен нулю, происходит оценка правого операнда. Если правый операнд оказывает- ся равным 0, операция возвращает 0, иначе — 1. ИЛИ Первым (полностью) оценивается левый операнд операции ||. Если он не ра- вен нулю (в том смысле, в каком это проверяется в операции !=), второй опе- ранд не оценивается и операция возвращает 1. Если левый операнд равен нулю, происходит оценка правого операнда. Если правый операнд оказывает- ся не равным 0, операция возвращает 1, иначе — 0.
262 Глава 7 Пример Оператор присваивания г = а && b эквивалентен следующей конструкции: if (а = 0) г = 0; else { if (Ь == 0) г = 0; else г = 1: 1 Оператор присваивания г = а 11 Ь: if (а *= 0) г = 1; else ( if (Ь *= 0) г = 1; else г = 0; 1 Пример Примеры логических операций: а Ь а&&Ь Ь оценивается а||Ь Ь оценивается 1 0 0 до 1 нет 0 34.5 0 нет 1 да 1 Hello\n” 1 ДО 1 нет "\0" 0 0 нет 0 ДО &х у=2 1 да 1 нет Обе логические операции описываются как лево-ассоциативные, хотя это не имеет особого значения, так как они полностью ассоциативны семантиче- ски и их приоритет не совпадает с приоритетом любой другой операции. При- оритет операции && выше приоритета | |, однако этот приоритет нелишне под- тверждать скобками — это делает исходный текст более наглядным. Пример Выражение а<Ь || Ь<с && c<d || d<e идентично более наглядному: а<Ь || (Ь<с && c<d) || d<e Ссылки: побитовые операции & и | 7.6.6; операции равенства == и != 7.6.5; 1-значение 7 1; ссылочные типы 5 3; приоритет 7.2.1; скалярные типы гл. 5; обычные унарные преобразова- ния 6.3.3. 7.8 Условные выражения Знаками ? и : обозначаются условные выражения, имеющие более низкий приоритет по сравнению с бинарными выражениями и отличающиеся от них правой ассоциативностью:
Выражения 263 условное-выражение : выражение-логического-ИЛ И выражение-логического-ИЛИ 2 выражение : условное-выражение Условное выражение содержит три операнда. Первый операнд должен иметь скалярный тип, второй и третий могут иметь различные типы и подвергаются обычным унарным преобразованиям (если оцениваются). Тип результата зави- сит от типов второго и третьего операндов. В таблице 7.5 перечислены допус- тимые типы операндов в традиционном С, в таблице 7.6 — типы операндов, допустимые в Standard С. Условные выражения право-ассоциативны в отно- шении первого и третьего операндов. Результат не является 1-значением в большинстве компиляторов, за исключением нескольких, предшествовав- ших Standard С. Таблица 7.5. Второй и третий операнды условного выражения (традиционный С) Тип одного операнда Тип другого операнда Тип результата арифметический арифметический тип операндов после обычных бинарных преобразований структура или объединение1 те же структура или объединение структура или объединение указатель указатель того же типа или 0 ссылочный тип (указатель) 1 Некоторые из указанных типов недопустимы в отдепьных компиляторах, предшествовавших Standard С, Таблица 7.6. Второй и третий операнды условного выражения (Standard С) Тип одного операнда Тип другого операнда Тип результата арифметический арифметический тип операндов после обычных бинорных преобразований структура или объединение совместимые структура или объединение структура или объединение void void void укозотепь но тип Т,, с квалификаторами или без указатель но тип Tj, с квалификаторами или без, если типы Т, и Tj совместимы составной ссылочный тип1 указатель на тип Т2 void * с квалификотороми или без void *’ любой ссылочный ТИП нулевая ссылочная константа ссылочный тип1 1 Тип, на который указывает резупьтат, имеет все квалификаторы типов, но которые указывают оба операнда, 2 Т должен быть типом объекта пибо неполным типом. Условное выражение выполняется следующим образом: 1. Первый операнд полностью оценивается и проверяется на равенство нулю. 2. Если первый операнд не равен нулю, оценивается второй операнд, и его значение, преобразованное в тип результата, становится значением ус- ловного выражения. Третий операнд не оценивается.
264 Глава 7 3. Если первый операнд равен нулю, оценивается третий операнд, и его значение, преобразованное в тип результата, становится значением ус- ловного выражения. Второй операнд не оценивается. Пример Выражение г=а?Ь:с эквивалентно следующей конструкции: if (а ’= 0) г = Ь; else г = с; Выражение a?b:c?d:e?f:g интерпретируется как а ? Ь : (с ? d : (е ? f : g)) Пример В данном примере вложенность условных выражений, похоже, оправданна: реали- зуется функция signum, возвращающая 1, -1 или 0, в зависимости от того, вызвана ли она с положительным, отрицательным или нулевым аргументом: int signum(int х) { return (х > 0) ? 1 : (х < 0) ? -1 : 0; } Все, что сложнее этого, следует составлять из операторов if. Признаком хорошего стиля программирования следует считать заключение в скобки первого операнда ус- ловного выражения, что, впрочем, необязательно. Ссылки: арифметические типы гл, 5; регулярные типы 5.4; типы с плавающей тачкой 5.2; це- лые типы 5.1; 1-значение 7.1; ссылочные типы 5.3; приоритет 7.2.1; скалярные типы гл. 5; типы со знакам 5.1.1; структурные типы 5.6; объединения 5.7; беззнаковые типы 5.1.2; обычные би- нарные преобразования 6.3.4; обычные унарные преобразования 6.3.3; тип void 5.9. 7.9 Выражения присваивания Выражение присваивания состоит из двух выражений, разделенных зна- ком присваивания. Эти выражения право-ассоциативны. Знак = называется знаком простой операции присваивания; все остальные операции присваива- ния — составные: выражение-присваивания : условное-выражение унарное-выражение зн.-присваивания выражение-присваивания зн.-присваивания : один из = += -= *= /= % = «= »= &= -= |= Все операции присваивания имеют одинаковый приоритет и правую ассоциа- тивность (все остальные операции над двумя операндами в С лево-ассоциативны). Пример Выражение x*=y=z трактуется как x*=(y=z), а не (x*=y)=z; аналогично, выражение x=y*=z трактуется как x=(y*=z), а не (x=y)*=z.
Выражения 265 Правая ассоциативность операций присваивания позволяет составлять многосту- пенчатые операции с «очевидной» интерпретацией. Таким образом, выражение a=b=d+7 интерпретируется как a=(b=(d+7)) — то есть значение d+7 присваивается сначала переменной Ь, затем — переменной а. Левый операнд любой операции присваивания должен быть модифицируе- мым 1-значением, и модификация состоит в записи в него нового значения. Операции присваивания различаются, по способу вычисления нового значения левого операнда. Результат операции присваивания не является 1-значением. Ссылки: модифицируемое 1-значение 7.1; приоритет 7.2.1. 7.9.1 Простая операция присваивания Простая операция присваивания обозначается одним знаком равенства =. Значение правого операнда преобразуется в тип левого операнда и записывает- ся в него. Допустимые типы операндов перечислены в таблице 7.7. Таблица 7.7. Операнды операций присваивания Тип левого операнда Тип правого операнда арифметический арифметический структура или объединение совместимые структура или объединение указатель на тип Т укозотель на тип Т', совместимый с Т void * указатель на тип Т1 указатель на тип Т1 void * любой указатель нулевая ссылочная константа 1 В Standard С Т должен быть типом объекта либо неполным типом. В первоначальном определении С присваивание структур и объединений не допускалось, и это ограничение могло сохраниться в компиляторах некоторых старых версий. В Standard С установлены дополнительные ограничения на операнды, ка- сающиеся квалификаторов их типов. Прежде всего, левый операнд не может быть объявлен с квалификатором типа const. Кроме этого: 1. Операнды арифметических типов могут быть объявлены с квалифика- торами либо без. 2. Если операнды являются структурами или объединениями, они долж- ны иметь совместимые типы, объявленные с квалификаторами либо без. Это, в частности, означает, что члены структуры или объединения должны иметь типы, объявленные с одинаковыми квалификаторами. 3. Если оба операнда — указатели на объекты или функции, это должны быть указатели на совместимые типы, и типы этих указателей могут быть объявлены с квалификаторами либо без. Тип, на который ссыла- ется левый операнд, должен быть объявлен со всеми квалификаторами, с которым был объявлен тип, на который ссылается правый операнд — тем самым, предотвращается возможность присваивания указателю
266 Глава 7 int * значения указателя const int *, что позволило бы модифицировать целую константу. 4. Если один из операндов имеет тип void * с квалификаторами либо без, другой должен быть указателем на объект или неполный тип. Левый операнд должен указывать на объект, тип которого объявлен со всеми квалификаторами типа, на который ссылается правый операнд. Причи- на — та же, что в предыдущем пункте. Тип результата операции присваивания равен типу левого операнда (не пре- образованного и без квалификаторов). Результат — значение, записанное в ле- вый операнд. Результат не является 1-значением. Если оба операнда имеют арифметические типы, правый операнд подвергается обычным преобразовани- ям присваивания для преобразования в тип левого операнда (до присваивания). Простую операцию присваивания нельзя применять для копирования всего содержимого одного массива в другой. Имя массива не является модифицируе- мым 1-значением, а потому его нельзя указывать слева от знака присваивания. Кроме того, имя массива, указанное справа от знака присваивания, преобразу- ется (в процессе обычных преобразований) в указатель на его первый элемент, поэтому в левый операнд будет копирован именно этот указатель, а не содер- жимое массива. Пример Операцию = можно использовать для копирования адреса массива в ссылочную пе- ременную: int а[20], *р; р = а; Здесь а — массив целых, р — «указатель на целое». После присваивания, р ссылает- ся на первый элемент массива а. Можно добиться копирования в левый операнд самого массива, разместив его внут- ри структуры или объединения, поскольку последние полностью копируются в опе- рации присваивания: struct matrix {double contents[10] [10]; ]; struct matrix a, b; { /* Обнуление диагональных элементов. */ for (j = 0; j < 10; j++) b.contents [j] [j] = 0; f* Копирование всего массива 10x10 из b в a. */ а = b; ] Реализация простой операции присваивания предполагает, что значение в правой части и объект в левой не перекрывают друг друга (либо перекрывают полностью, как в операторе х=х) — иначе результат непредсказуем. Ссылки: арифметические типы 5.1 _2; регулярные типы 5.4; обычные преобразования при- сваивания 6.3.2; 1-значение 7.1; нулевой указатель 5.3.2; ссылочные типы 5.3; структурные типы 5.6; совместимость типов 5.1 1; объединения 5.7.
Выражения 267 7.9.2 Составная операция присваивания Составные операции присваивания можно неформально представить как выражения вида а ор= Ь, что эквивалентно a = a op b с условием, что выраже- ние а оценивается только один раз. Допустимые типы операндов зависят от операции ор. Возможные варианты перечислены в таблице 7.8. Строго говоря, оцениваются левый и правый операнды операции ор=, а ле- вый операнд должен быть модифицируемым 1-значением. Далее, над обоими операндами выполняется операция ор, что включает все необходимые «обыч- ные преобразования». После выполнения обычных преобразований присваи- вания, результат записывается в объект, обозначенный левым операндом. В составной операции присваивания, как и в простой, тип результата сов- падает с типом (не преобразованного) левого операнда. Результат равен значе- нию, записанному в левый операнд, и не является 1-значением. Таблица 7.8. Типы операндов составных операций присваивания Опероция присваивания Левый операнд Правый операнд *= /= арифметический арифметический %= целый целый += -= арифметический арифметический += -= указатель целый «= »= целый целый &= целый целый целый целый |= целый целый В самых ранних версиях С в обозначениях составных операций знак = за- писывался первым, что приводило к неопределенности — выражение х=—1 можно было интерпретировать как х=(-1) либо х=-(1). Современная форма свободна от этого недостатка. В некоторых компиляторах, не относящихся к Standard С, поддержка старых форм оставлена для совместимости с прежни- ми версиями, и там оператор х=—1 не интерпретируется как х=-(1), если зна- ки = и — разделены пробелом. Ссылки: арифметические типы гп. 5; преобразования присваивания 6.3.2; типы с плавающей точкой 5.2; целые типы 5,1; ссылочные типы 5.3; типы со знаком 5.1.1; беззнаковые типы 5.1.2; обычные бинарные преобразования 6.3.4; обычные унарные преобразования 6.3.3. 7.10 Последовательные выражения Комма-выражение (comma-expression) состоит из двух выражений, разде- ленных запятой. Здесь дается описание комма-операции как синтаксически лево-ассоциативной, однако это не имеет особого значения, так как она полно- стью ассоциативна семантически. Заметим, что комма-выражение располага- ется на вершине дерева выражений языка С:
268 Глава 7 комма-выражение : выражение-присваивания комма-выражение , выражение-присваивания выражение : комма-выражение Первым полностью оценивается левый операнд комма-операции. От него не требуется возврат какого-либо значения. Если операнд все же возвращает зна- чение, оно отбрасывается. Затем оценивается правый операнд. Тип и значение результата комма-выражения равны типу и значению правого операнда после обычных унарных преобразований. Результат не является 1-значением. Таким образом, оператор г=(а,Ь,...,с); (обратите внимание на обязательность скобок) эквивалентен оператору а;Ь;...г=с;. Особенность комма-операции состоит в том, что она может использоваться в качестве выражения — например, управляющего выражения в цикле. Пример Применение комма-операции в операторе for позволяет объединять несколько опе- раций присваивания в одном выражении — для инициализации или приращения нескольких переменных в одном цикле: for( х=0, y=N; x<N && у>0; х++, у—) ... Комма-операция ассоциативна и позволяет объединить в одном выражении любое число операций. Подвыражения оцениваются последовательно, значение последне- го становится значением всего выражения. Пример Слишком активное использование комма-операций делает исходный текст програм- мы запутанным; более того, иногда эти операции входят в противоречие с другими, в которых также используются запятые. Например, выражение f(a, Ь=5,2*Ь, с) всегда трактуется как вызов функции f с четырьмя аргументами. Комма-выраже- ние, в списке аргументов, должно быть заключено в скобки: f(а, (Ь=5,2*Ь), с) В числе других контекстов, требующих заключения комма-операций в скобки: выражения размеров полей в списках описателей объявлений струк- тур и объединений, перечислимых значений в списках описателей перечисли- мых типов, а также инициализационные выражения в объявлениях и инициа- лизаторах. Запятая используется также в качестве разделителя в препроцес- сорных вызовах макросов. Оценка операндов комма-операции происходит в слева направо, однако та- кая последовательность не гарантирована в других конструкциях с использо- ванием запятых. Например, выражения аргументов в вызове функции могут оцениваться в ином порядке. Ссылки: выражения с отбрасываемыми значениями 7.13; перечислимые типы 5.5; оператор for 8.6.3; вызовы функций 7.4.3; инициализаторы 4.6; 1-значение 7.1; вызовы макросов 3.3; структурные типы 5.6; объединения 5.7.
Выражения 269 7.11 Константные выражения В некоторых контекстах, в языке С допускается написание выражений, оцениваемых в процессе компиляции как константы. В каждом из таких кон- текстов эти выражения могут иметь незначительные отличия. Существуют три класса константных выражений: 1 . Пре процессорные константные выражения, используемые как тести- руемые значения в препроцессорных управляющих операторах #if и #elif. 2 . Целые константные выражения, используемые для определения гра- ниц массивов и размеров битовых полей в структурах, для явного ука- зания значений перечислимых типов или меток case в операторах switch. 3 . Инициализационные константные выражения, используемые для ини- циализации статических и внешних переменных, а также переменных составных типов (в версиях, предшествовавших С99). Константные выражения не могут содержать выражений присваивания, положительного или отрицательного приращения, вызова функций и ком- ма-выражений, за исключением ситуации, в которой они оказываются заклю- ченными внутри операнда операции sizeof. Все остальные литералы или опе- рации могут присутствовать в константных выражениях при соблюдении до- полнительных условий. Мы обсудим эти условия для каждого класса выражений в последующих разделах. Указанные условия устанавливаются в Standard С; что касается традиционных реализаций, там они также сущест- вуют, но в менее жестких формах. 7 .11.1 Препроцессорные константные выражения Препроцессорные константные выражения оцениваются во время компиля- ции и подчинены некоторым достаточно жестким ограничениям. Эти выраже- ния должны иметь целый тип и могут включать только целые и символьные константы, а также специальную операцию defined. В С99 все арифметиче- ские операции выполняются при исходных типах (определяемых на компью- тере, на котором происходит компиляция), эквивалентных конечным типам intmax_t и uintmax_t — соответственно «знаковости» операндов. Указанные типы определены в заголовочном файле stdint.h и имеют длину не менее 64 битов. В версиях, предшествовавших С99, требования Standard С касались только исходных типов, которыми должны были быть типы long или unsigned long, что не всегда удавалось обеспечить, если исходный и конечный компью- теры существенно отличались друг от друга. Препроцессорные выражения не должны выполнять каких бы то ни было оценок среды, кроме тех, которые происходят посредством обращения к мак- росам, определенным в заголовочных файлах float.h, limits.h, stdint.h и т.д. Приведение типов и операция sizeof не разрешены. Переменные программы невидимы для препроцессора, даже если объявлены с квалификатором const.
270 Глава 7 Пример В следующем примере предпринимается попытка (неправильная) проверить, со- ставляет ли размер типа int — на компьютере, на котором предполагается запуск программы, — более 16 битов: #if 1 « 16 /* Конечный целый тип состоит ив более чем 16 битов (НЕПРАВИЛЬНО!)*/ ffendif По существу, происходит проверка представления типа long на исходном компьюте- ре (в С89) или типа intmax_t — на конечном (в С99). Ниже приведен пример пра- вильной проверки размеров конечных типов. #include <limits.h> #if UINT_MAX > 65535 /* Конечный целый тип представлен более чем 16 битами */ #endif Препроцессор должен распознавать escape-последовательности в символь- ных константах, но может использовать символьный набор исходного либо ко- нечного компьютера при преобразовании символьных констант в целые значе- ния. Это означает, что выражения ’\п’ или ’z'—'а' могут иметь в препроцессор- ном выражении значения, отличные от значений, которые они имели бы, к примеру, в операторе if. Все это необходимо учитывать при программирова- нии для компьютера с символьным набором, отличающимся от символьного набора исходного компьютера. После расширения макроса, если в препроцессорном константном выраже- нии остаются какие-то идентификаторы, они заменяются нулевыми констан- тами. Это, пожалуй, не самое лучшее правило, поскольку наличие таких иден- тификаторов, чаще всего — результат ошибок программирования. Более пред- почтительный способ проверки определения имени в препроцессоре — при помощи операции defined или команд #ifdef и #ifndef. Компиляторы могут принимать дополнительные формы препроцессорных константных выражений, но программы, компилированные с ними, лишают- ся мобильности. Ссылки: выражения приведения 7.5.1; символьные константы 2.7.3; символьные наборы 2.1; определенная операция 3.5.5; константа перечислимого типа 5.5; escape-символы 2.7.5; float.h 5.2; #ifdef и Sifndef 3.5.3; intmax_t 21.5; limits.h 5.1; операция sizeof 7 5 2, stdint.h гл. 21. 7 .11.2 Целые константные выражения Целые константные выражения применяются для обозначения границ мас- сивов, размеров битовых полей в структурах, явного указания значений пере- числимых типов или меток case в операторах switch. Целое константное выра- жение должно иметь целый тип и может содержать целые либо символьные константы, а также константы перечислимых типов. В них допускается ис- пользование операции sizeof с любым операндом. Возможно также использо- вание приведения типов — но только для преобразования арифметических ти- пов в целые (за исключением операнда операции sizeof). Константы с плаваю-
Выражения 271 щей точкой допускаются только в качестве операндов операций приведения типа или sizeof. Константные выражения, не входящие в команды препроцессора, должны оцениваться по их представлению на конечном компьютере, вместе со значе- ниями символьных констант. Компиляторы могут принимать дополнительные формы целых константных выражений, в том числе выражения с плавающей точкой более общего вида, но программы, компилированные с ними, лишают- ся мобильности. В некоторых компиляторах, предшествовавших Standard С, какие бы то ни было приведения типов в константных выражениях не допус- каются. Если программу предполагается сделать мобильной, следует избегать использования приведения типов в константных выражениях. Ссылки: битовые попя 5.6.5; выражения приведения 7.5.1; перечислимые типы 5.5; константы с плавающей точкой 2.7.2; операция sizeof 7.5.2; оператор switch 8.7. 7 .11.3 Инициализирующие константные выражения Константное выражение в инициализаторе может содержать арифметиче- ские и адресные константные выражения. Арифметические константные выражения включают целые константные выражения, но могут включать также и константы с плавающей точкой (не только приводимые к целым типам или указываемые в операции sizeof) и при- ведения к любым арифметическим типам (в том числе, с плавающей точкой). Если некоторое выражение с плавающей точкой оценивается во время компи- ляции внутри константного выражения, компилятор может использовать представление, обеспечивающее большую точность или более широкий интер- вал значений, чем требуется в среде, для которой программа предназначается. Следовательно, значение выражения с плавающей точкой во время компиля- ции может незначительно отличаться от значения этого же выражения во вре- мя выполнения программы. Это правило отражает сложность моделирования операций над значениями с плавающей точкой в «чужой» среде. В остальных случаях, выражения должны оцениваться так же, как они будут оцениваться в рабочей среде. Адресное константное выражение может быть нулевой ссылочной констан- той — например, (void *)0, — а также адресом статического или внешнего объ- екта либо функции (возможно, с выражением, через знак + или —, задающим сдвиг от этого адреса). При формировании адресов могут использоваться опе- рации адресации (&), опосредования (*), индексации ([]) и выбора компонен- тов (. и ->), однако нельзя предпринимать никаких попыток обращения к зна- чениям объектов. Допускается также приведение ссылочных типов. Компиляторы могут принимать дополнительные формы инициализацион- ных константных выражений — например, адресные выражения с нескольки- ми адресами, — но программы, компилированные с ними, лишаются мобиль- ности. В Standard С указано, что инициализация, в зависимости от реализации, может происходить во время выполнения программы, что избавляет от необ- ходимости выполнять вычисления с плавающей точкой в процессе компиля- ции. Однако в этом случае, возникает опасность обращения к инициализируе- мым переменным до их инициализации.
272 Глава 7 Пример Ниже приведены примеры адресных константных выражений в инициализаторах переменных р и pf: static int а[10] ; static struct { int fl, f2; ) s; extern int f(); int i = 3; int *p[] = { Si, a, Sa[0], (int *)((char *)Sa[0]+sizeof(a)), Ss.f2 } ; int (*pf) () = Sf; Ссылки: адресная операция & 7.5,6; ссылочные типы 5,4; инициализаторы 4,6; операция sizeof 7,5,2; структурные типы 5.6, 7.12 Последовательность оценки Обычно компилятор может менять последовательность оценки выражений. Это изменение обычной последовательности (слева направо) может касаться аргументов вызываемой функции или операндов бинарной операции. Предпо- лагается, что бинарные операции и | полностью ассоциативны и ком- мутативны, и компилятору разрешается это использовать. Компилятор мо- жет, к примеру, оценивать выражение (a+b)+(c+d) в такой последовательно- сти, как если бы оно имело вид (a+d)+(b+c) (предполагается, что все переменные имеют один и тот же арифметический тип). Предположение коммутативности и ассоциативности всегда действительно для операций &, “и| над беззнаковыми операндами, но его действительность для этих же операций над операндами со знаком зависит от представления по- следних. Предположение коммутативности может не выполняться для опера- ций * и +, поскольку при какой-то последовательности может происходить пе- реполнение, не происходящее в другой. Тем не менее, компилятору разрешено принимать это предположение. Любая перестановка выражений в этих опера- циях не должна влиять на неявные преобразования типов операндов. Пример Можно определять последовательность оценок при помощи присваивания значений временным переменным. Тем не менее, хороший оптимизационный компилятор мо- жет изменить последовательность оценок и в этом случае: int tempi, temp2; /* Вычисление q= (а+Ь) + (c+d) - именно в таком порядке. */ tempi = а+Ь; temp2 = c+d; q = tempi + temp2; Пример Два выражения в следующем примере не эквивалентны, и компилятор не может за- менить одно другим, несмотря на то, что они отличаются только изменением после- довательности выполнения операций сложения:
Выражения 273 (1.0 + -3) + (unsigned) 1; /* Результат равен -1.0 */ 1.0 + (-3 + (unsigned) 1); /* Результат - большое значение */ Первое выражение очевидно и дает ожидаемый результат. Что касается второго, здесь результатом будет большое значение, поскольку значение со знаком -3 преоб- разуется в процессе обычных бинарных преобразований в значение 2га-3, где п — число битов в представлении беззнакового целого. Затем это складывается с беззна- ковым значением 1, результат преобразуется в представление с плавающей точкой, прибавляется к 1.0, и это дает 2л-1 в представлении с плавающей точкой. Програм- мист мог предвидеть либо не предвидеть такой результат; в любом случае, компиля- тор не должен усугублять ситуацию переопределением последовательности выпол- нения операций. В соответствии с определением языка компилятор может, в равной степени, переопределять последовательность операций с плавающей точкой. Однако из- менение последовательности выполнения этих операций может существенно влиять (в зависимости от значений операндов) на точность результата. Компи- лятор не может предвидеть эти значения, поэтому специалисты по численному анализу ожидают оценки выражений с плавающей точкой в той последова- тельности, в которой они написаны. Программист сам может устанавливать последовательность оценки. Последовательность оценки фактических аргу- ментов вызываемой функции не установлена, но все аргументы оцениваются поочередно, и оценка следующего аргумента начинается после полного завер- шения оценки предыдущего. Аналогичные правила действуют в отношении оценки операндов бинарной операции, а также для а и i в выражении a[i]. Пример Переменная х, в следующем примере — это массив указателей на символы, который необходимо рассматривать как массив строк, переменная р — указатель на указа- тель на символ, который необходимо рассматривать как указатель на строку. На- значение оператора if — проверка равенства строки, на которую ссылается указа- тель р (пусть это будет строка si) и следующей за ней строки (s2) (после сравнения двух строк массива, указатель р перемещается на следующую пару). char *х[10], **р=х; if ( strcmp(*р++, *р++) == 0 ) printf("Одинаковы.”); Конечно, использование двух побочных эффектов в одной переменной и в одном вы- ражении никак не может служить признаком хорошего стиля программирования, поскольку последовательность этих побочных эффектов не определена. Однако дан- ный фрагмент, написан, по-видимому, очень изощренным программистом, понимаю- щим, что последовательность побочных эффектов, в данном случае, не имеет никако- го значения, поскольку сравниваемые строки могут быть указаны в любом порядке. 7.12.1 Специальные маркеры Если отдельный объект в Standard С модифицируется более одного раза ме- жду двумя последовательными специальными маркерами, результат оказыва- ется неопределенным. Специальный маркер {sequence point) — это точка в по- следовательности выполнения, по достижении которой все побочные эффекты завершаются и новые побочные эффекты возникнуть не могут. Специальные маркеры устанавливаются:
274 Глава 7 • в конце полного выражения — то есть инициализатора, операторного вы- ражения, выражения в операторе return и управляющего выражения в условном операторе, цикле или операторе switch (в том числе, каждого выражения оператора for); • после первого операнда операций &&, 11, ?: и комма-операции; • после оценки аргументов вызываемой функции. Согласно этому правилу, значение выражения ++i*++i неопределенно, как и в предыдущем примере с функцией strcmp. Ссылки: операция сложения + 7.6.2; бинарные операции 7.6; побитовая операция И & 7.6.6; побитовая операция ИЛИ | 7.6.8; побитовая операция исключающего ИЛИ ~ 7.6.7; ком- ма-операция 7.10; условное выражение ?: 7.8; условный оператор 8.5; операторное выраже- ние 8.2; вызов функции 7.4.3; инициализаторы 4.6; операторы циклов 8.6; логические опера- ции И && и ИЛИ | | 7.7; операция умножения * 7.6.1; оператор возврата 8.9; функция strcmp 13.2; обычные бинарные преобразования 6.3.4. 7.13 Отбрасывание значений Возможны три обстоятельства, в которых значения, возвращаемые выра- жениями, не используются: 1. в операторном выражении; 2. в первом операнде комма-выражения; 3. в инициализационном выражении либо выражении приращения пере- менной оператора for. В этих случаях мы говорим, что значение выражения отбрасывается. Если значение выражения, не порождающего побочных эффектов, отбрасы- вается, компилятор может предположить ошибку программирования и вывес- ти предупреждение. К операциям, порождающим побочные эффекты, относят- ся присваивания и вызовы функций. Компилятор может выводить предупреж- дения также и в тех случаях, когда основная операция выражения с отбрасываемым значением не порождает побочных эффектов. Пример extern void f () ; f (х) ; /* Эти выражения не оправдывают */ i++; /* вывод предупреждения об */ а = Ь; /* отбрасывании значений. */ Следующие операторы правильны, но могут вызвать вывод предупреждения: extern int g(); g(x); /* Результат функции g отбрасывается. */ х + 7; /* Сложение без определенных побочных эффектов. */ х + (а *= 2) /* Результат операции, выполняемой последней (сложение), отбрасывается. */ Чтобы избежать вывода предупреждений, следует явно указывать отбрасывание значений, приводя выражения к типу void: extern int g(); (void) g(x); /* Возвращаемое значение отбрасывается намеренно */ (void)(х + 7); /* Довольно бессмысленно, но, как видно, зачем-то понадобилось. */
Выражения 275 Как правило, компиляторы С не выводят предупреждений при отбрасыва- нии значений функций, поскольку в ранних компиляторах функции, не воз- вращающие значений, объявлялись как возвращающие тип int. В Standard С компиляторам предоставляется более полная информация, но поставщики компиляторов стараются поддерживать совместимость с прежними версиями. Если компилятор обнаруживает, что основная операция выражения с от- брасываемым значением не порождает побочных эффектов, он может не гене- рировать ее код (в результате чего операнды этой операции становятся отбра- сываемыми значениями и могут, рекурсивно, подвергнуться такой же обра- ботке). Ссылки: присваивания 7.9; приведение типов 7.5.1; комма-аперация 7.10; оператор for 8.6.3; вызовы функций 7.4.3; операторные выражения 8.2; тип void 5.9. 7.14 Оптимизация обращений к памяти Как правило, компилятор может генерировать любой код, эквивалентный исходному тексту программы. Ему явным образом разрешается изменение по- следовательности операций, описание которого дано в разделе 7.12. Кроме того, компилятор может не генерировать код выражений, если эти выражения не по- рождают побочных эффектов, а их значения отбрасываются (см. раздел 7.13). Пример Некоторые компиляторы преобразуют исходный текст таким образом, что обраще- ния к памяти происходят не в том количестве и не таким образом, как указано в программе. Например, если обращение к какому-то элементу массива происходит более одного раза, компилятор может принять разумное решение извлечь значение этого элемента только один раз, чтобы повысить скорость выполнения. По сущест- ву, он как бы переписывает исходный текст наподобие следующего: int х,а[10]; х = а[j] *a[j] *a[j]; /* Возведение в куб элемента массива. */ после чего он принимает вид: int х,а[10] ; register int temp; temp = a[j] ; x = temp * temp * temp; /* Возведение в куб элемента массива. */ Для большинства приложений — в том числе, почти всех мобильных, — эти методы оптимизации очень полезны, поскольку позволяют повысить ско- рость вычислений в два раза и более без изменения вычислительного алгорит- ма. В то же время, такое поведение компилятора может порождать проблемы при программировании обработчиков прерываний и других машинозависи- мых программ. В этом случае, для управлением доступом к памяти следует ис- пользовать квалификатор типа volatile Standard С. Ссылки: volatile 4 4 5
276 Глава 7 7.15 Совместимость с C++ 7.15.1 Изменения в выражениях sizeof В C++ не допускается объявление типов в выражениях — например, в при- ведениях типов или операциях sizeof. Кроме того, некоторые операции sizeof возвращают разные значения в С и C++ ввиду различий в областях видимости и типах символьных литералов. Пример i = sizeof(struct S { ... )); /* Правильно в С, но не в C++ */ Пример Значение sizeof (Т) может меняться в случае переопределения Т. Значение sizeof('a') равно sizeof(int) в С, но sizeof(char) в C++. Значение sizeof(e), где е — константа перечислимого типа, равно sizeof(int) в С, но может быть другим в C++. Ссылки: символьные литералы 2.8.5; перечислимые типы 5.13.1; отличия в области видимо- сти 4.9.2; sizeof 7,5.2. 7.16 Упражнения 1. Какие из следующих выражений правильны в традиционном С? Како- вы типы правильных выражений? Предполагается, что f имеет тип float, i — тип int, ср — тип char * и ip — тип int *. (а) ср+0х23 (b) i+f (с) ++f (d) ip[i] (е) cp?i:f (f) f==0 (g) lip (h) cp && cp (i) f%2 (j) f+=i 2. Предположим, pl и p2 имеют тип char *. Перепишите следующие два оператора без использования операций положительного и отрицатель- ного приращений. (а) *++р1=*++р2; (b) *р1—=*р2—; 3. Битовая маска — это целое значение, состоящее из заданной последова- тельности двоичных нулей и единиц. Напишите макросы составления следующих битовых масок. Если аргументы макроса являются кон- стантами, результат также должен быть константой. Можно предполо-
Выражения 277 жить представление целых в форме дополнения до двух, но результат не должен зависеть от того, каким числом битов представлено целое и имеет ли компьютер тупоконечную или остроконечную архитектуру. (a) low_zeroes(n): слово, в котором младшие п битов равны нулю, ос- тальные — единице. (b) low_ones(n): слово, в котором младшие п битов равны единице, ос- тальные — нулю. (с) mid_zeroes(width, offset): слово, в котором младшие offset битов равны единице, следующие width битов — нулю, остальные — едини- це. (d) mid_ones(width, offset): слово, в котором младшие offset битов рав- ны нулю, следующие width битов — единице, остальные — нулю. 4. Правильны ли выражения j++==++j и j++&&++j? Если исходное зна- чение j равно 0, каков результат каждого выражения? 5. В следующей таблице перечислены пары типов левой и правой частей оператора присваивания. Какие из сочетаний допустимы в Standard С? Тип левой части Тип правой части (а) short signed short (Ь) char * const char * (с) int (*) [5] int (*) П (d) short const short (е) int (*) 0 signed (*)(int x, float d) (0 int * t * (где: typedef int t) 6. Если переменная х имеет тип struct{int f;}, переменная у — отдельно определенный тип struct {int f;}, допустимо ли в Standard С выражение х=у?

Глава 8 Операторы Язык С обладает обычным набором операторов, характерным для любого алгебраического языка программирования. Этот набор включает условные операторы, циклы и, конечно же, операторы перехода. После некоторых об- щих замечаний о синтаксисе мы рассмотрим операторы каждого вида в от- дельности. оператор: операторное-выражение оператор-с-~меткой составной-оператор условный-оператор оператор-цикла оператор-выбора оператор-выхода-из-цикла оператор-возврата-к-началу-цикла оператор-возврата оператор-перехода пустой-оператор условный-оператор: onepamop-if onepamop-if-else оператор-цикла: onepamop-do оператор-while onepamop-for 8.1 Общий синтаксис операторов Синтаксис операторов С подобен синтаксису языка ALGOL и производных от него. Тем не менее, есть ряд отличий, которые могут приводить к путанице и ошибкам. В С, как и в языках Ada или Pascal, операторы отделяются друг от друга точкой с запятой. Однако в С точка с запятой — это не просто раздели- тель, а скорее элемент синтаксиса большинства операторов. Единственный оператор языка С, который не должен обязательно оканчиваться точкой с за- пятой — это составной (или блочный) оператор. В качестве операторных ско-
280 Глава 8 бок, определяющих составной оператор, используются фигурные скобки ({}), а не ключевые слова begin и end: а=Ь; { Ь = с; d = е; } х = у; Еще одна особенность операторов языка С: «управляющие» выражения в условных операторах и циклах заключаются в скобки. Управляющее выра- жение не отделяется от остальной (выполняемой) части оператора никаким ключевым словом (наподобие then, loop или do): if (а<Ь) х=у; while (n<10) n++; Наконец, то, что в других языках программирования принято называть оператором присваивания (assignment statement), в С именуется выражением присваивания (assignment expression). Выражение присваивания может вхо- дить составной частью в более сложные выражения либо оставаться самим по себе, отделенным точкой с запятой: if ((х=у)>3) а=Ь; Ссылки: оператор присваивания 7.9; составной оператор 8.4; условные операторы 8.5; опе- раторы циклов 8.6. 8.2 Операторное выражение Любое выражение может рассматриваться как оператор, если оно оканчи- вается точкой с запятой: операторное-выражение: выражение ; При выполнении этого оператора, выражение вычисляется, полученный ре- зультат — если таковой имеется, — удаляется. Операторное выражение может быть полезно лишь в ситуациях, когда вы- числение выражения может иметь побочный эффект — например, присвоение значения переменной либо ввод/вывод данных. Как правило, выражение вхо- дит составной частью в операцию присваивания, положительного или отрица- тельного приращения или вызова функции. Пример speed = distance / time; ++event_count; printf("Again?"); pattern S= mask; (x<y) ? ++x : ++y; /* Присвоение переменной значения отношения */ /* Приращение event_count на 1 */ /* Вызов функции printf */ /* Удаление битов из переменной pattern */ /* Приращение на 1 меньшего из значений х и у */ Возможна более наглядная форма последнего оператора — с условным оператором if: if (х<у) ++х; else ++у; Выражение или часть его может не вычисляться, если это не влечет ника- ких побочных эффектов, а результат просто удаляется (см. раздел 7.13). Ссылки: выражения присваивания 7.9; исключаемые выражения 7.13; выражения гл. 7; вызо- вы функций 7.4.3; выражения приращения значений 7.4.4, 7.5.8.
Операторы 281 8.3 Операторы с метками Любой оператор может быть помечен меткой, что позволит передавать это- му оператору управление при помощи операторов goto или switch. Существу- ют метки трех видов. Именованные метки могут устанавливаться на любые операторы и используются в сочетании с оператором goto. Метки case или default могут устанавливаться только на операторы, располагающиеся внутри оператора switch: оператор-с-меткой: метка : оператор метка: именованная-метка метка-case метка-default Метка не может присутствовать в программе сама по себе; она всегда должна помечать какой-либо оператор. Если необходимо исключение из этого правила (например, чтобы пометить конец составного оператора), можно установить метку на пустой оператор. В С99, где допускается задание операторов и объяв- лений вперемежку, установка метки на объявлении не допускается; однако ее можно установить на пустой оператор, расположенный перед объявлением. Мы обсудим именованные метки несколько ниже, при рассмотрении опера- тора goto, метки case и default — при рассмотрении оператора switch. Ссылки: оператор goto 8,10; пустой оператор 8,11; оператор switch 8.7. 8.4 Составные операторы Составной оператор — это список некоторого числа (включая нуль) объяв- лений и операторов. В С99 объявления и операторы могут перемежаться друг с другом, тогда как в более ранних версиях языка объявления должны были располагаться перед операторами. составной-оператор: { список-объявлений-и-операторовопц } список-объявлений-и-операторов: объявление-или-оператор список-объявлений-и-операторов объявление-или-оператор объявление-или-оператор: объявление оператор Составной оператор может располагаться в любом месте, где может распола- гаться какой-либо иной оператор. Он порождает новую область видимости (блок) для всех объявлений и литералов, располагающихся внутри. В процессе выполнения составного оператора, происходят поочередные обработка и выпол- нение составляющих его объявлений и операторов. Выполнение завершается после того, как будет обработано последнее объявление и выполнен последний
282 Глава 8 оператор. Возможен преждевременный выход из составного оператора при по- мощи операторов goto, return, continue или break. Возможен также вход в со- ставной оператор, минуя его начало — в результате перехода на метку по опера- тору goto или switch. Следует, однако, иметь в виду, что такие нестандартные способы входа в составной оператор и выхода из него могут повлиять на распо- ложенные в операторе объявления. Мы обсудим это в следующем разделе. Ссылки: класс памяти auto 4.3; операторы break и continue 8.8; объявления гл. 4; опера- тор goto 8.10; класс памяти register 4.3; оператор return 8.9; область видимости 4.2.1. 8.4.1 Объявления внутри составных операторов Идентификатор, объявленный внутри составного оператора или иного бло- ка, именуется идентификатором уровня блока; объявление этого идентифика- тора именуется объявлением уровня блока. Идентификатор уровня блока дей- ствителен от его объявления до конца блока. Именно в этой области идентифи- катор видим, за исключением ситуации, когда он переобъявлен во внутреннем блоке. Объявление идентификаторов в блоках считается признаком хорошего стиля программирования, поскольку ограничение области видимости пере- менных делает текст программы более наглядным. Идентификатор может быть объявлен в блоке без спецификатора класса па- мяти. В этом случае, если это идентификатор функции, ему, по умолчанию, присваивается класс памяти extern, иначе — класс памяти auto. Идентифика- тору функции, объявленному внутри блока, не может быть присвоен никакой иной класс памяти, кроме extern. Если переменная или функция объявлены внутри блока с классом памяти extern, память им не распределяется, а потому к ним неприменимо инициали- зационное выражение. Такое объявление соотносится с переменной или функ- цией, определенными вне блока, в этом же или другом исходном файле. Если переменная, не являющаяся массивом переменной длины, объявлена внутри блока с классом памяти auto или register, распределение памяти ей выполня- ется при каждом входе в блок; при выходе из блока память освобождается. Та- ким образом, время жизни переменной совпадает со временем выполнения бло- ка — от его начала, а не от точки объявления переменной. Если объявление со- держит инициализационное выражение, инициализация значения переменной происходит при каждом входе в блок. В С99 переменная инициализируется за- ново каждый раз, как управление переходит к оператору блока, расположен- ному перед объявлением этой переменной; в прочих версиях С этого, как пра- вило, не происходит. Если, в результате выполнения оператора goto или switch, происходит переход к оператору, расположенному внутри составного оператора после объявления переменной, вычисления инициализирующего выражения не произойдет и переменная может оказаться не инициализиро- ванной. Значение автоматической переменной уровня блока не сохраняется от одного выполнения блока до другого. В С99, если массив переменной длины объявлен внутри блока, память ему распределяется не при входе в блок, как другим автоматическим переменным, а после объявления и вычисления требуемого объема; освобождение памяти происходит при выходе из блока. Следовательно, условия определения време- ни жизни и области видимости массива переменной длины совпадают. Массив переменной длины нельзя инициализировать. Не разрешается переход к опе- ратору, расположенному после объявления массива переменной длины, из
Операторы 283 точки, расположенной вне области его видимости. Однако возможен переход из области видимости массива переменной длины к оператору, расположенно- му перед его объявлением. В этом случае, память массива освобождается, за- тем распределяется вновь — возможно, в новом объеме. Все массивы перемен- ной длины в блоке подчиняются правилу, согласно которому память им рас- пределяется в последнюю очередь, освобождается — в первую. Это позволяет распределять массивам переменной длины стековую память. Если переменная объявлена в блоке с классом памяти static, память ей рас- пределяется один раз — при запуске программы, как любым другим статиче- ским переменным. Если объявление содержит инициализационное выраже- ние, инициализатор, который должен быть константой, вычисляется один раз — при запуске программы, — и переменная сохраняет значение от одного выполнения составного оператора до следующего. В С99 инициализатор также должен быть константой. Пример Следующий фрагмент программы не будет работать, если переход к оператору с мет- кой L: будет выполнен извне составного оператора, поскольку переменная sum, в этом случае, окажется не инициализированной. К тому же, невозможно опреде- лить, происходит ли подобный переход, без проверки всего тела функции, содержа- щей этот блок. { extern int а[100]; int i, sum = 0; L: for (i = 0; i < 100; i++) sum += a [ i ] ; 1 Пример Составной оператор без метки, используемый как тело оператора switch, не может быть выполнен обычным образом, а только посредством передачи управления опе- ратору с меткой внутри его. Следовательно, инициализация переменных с классом памяти auto или register в начале такого оператора не произойдет, а потому их на- личие априорно является ошибкой. switch (i) { int sum = 0; /* ОШИБКА! sum не будет равна 0 */ case 1: return sum; default: return sum+1; ) Ссылки: класс памяти auto 4,3; класс памяти extern 4.3; оператор goto 8,10; начальные значения 4.2.8; инициализаторы 4,6; класс памяти register 4.3; область видимости 4.2.1; класс памяти static 4.3; оператор switch 8.7; массив переменной длины 5.4.5; види- мость 4.2.2.
284 Глава 8 8.5 Условные операторы Существуют условные операторы двух видов: с условием else и без. В язы- ке С ключевое слово then в конструкции оператора if отсутствует: условный-оператор: onepamop-if onepamop-if-else оператор-if if ( выражение ) оператор onepamop-ifelse if ( выражение ) оператор else оператор При выполнении оператора любого из двух видов, первым делом вычисляется значение в скобках. Если результат его оказывается ненулевым (см. раздел 8.1), выполняется оператор, следующий за скобками. Если значение управляющего выражения оказывается равным нулю и оператор if содержит условие else, вы- полняется оператор, следующий за ключевым словом else. Наконец, если значе- ние управляющего выражения равно нулю, а условие else отсутствует, выполня- ется оператор, следующий непосредственно за условным оператором. В С99 оператор if образует блок с собственной областью видимости, как и все его подоператоры. Таким образом ограничивается область видимости объектов и типов, которые могут создаваться неявно при использовании со- ставных литералов или имен типов. Ссылки: составные литералы 7.4.5; управляющее выражение 8.1; имена типов 5.12. 8.5.1 Многовариантные условные операторы Условие выбора одного из множества вариантов можно описать каскадной последовательностью операторов if-else, в которой каждый оператор if, кроме последнего, содержит в своей части else еще один оператор if. Эта конструк- ция имеет примерно следующий вид: if (выражение;) оператор; else if (выражение2) оператор2 else if (выражениеэ) оператор3 else операторп Пример Ниже приведен пример выбора одного варианта из трех: функция signum возвраща- ет -1, если аргумент меньше нуля, 1 — если больше и 0, если он равен нулю. int signum(int х) { if (х > 0) return 1; else if (x < 0) return -1;
Операторы 285 else return 0; 1 Сравните этот пример с примером функции signum, приведенным в разделе 7.8. Особый вид многовариантного условного оператора — оператор switch, в котором управляющее выражение сравнивается с фиксированным набором констант. Ссылки: оператор switch 8.7. Пример В следующем примере демонстрируется неопределенность, которая может приво- дить к ошибкам при составлении многовариантных условных операторов. if ((к >= 0) && (к < TABLE_SIZE)) if (table[к] >= 0) printf("Значение %d равно %d\n:, к, table[kl); else printf("Ошибка: Недопустимое значение индекса %d.\n",k); Не слишком вдумчивый читатель может соотнести часть else с внешним оператором if и ожидать сообщения об ошибке в ситуации, когда выражение ((к >= 0) && (к < TABLE_SIZE)) возвращает значение false. Однако же, если поменять текст последнего сообщения об ошибке на следующий: else printf("Ошибка: значение %d отрицательно.\п",к); логично будет предположить, что программист рассчитывал на выполнение части else в случае, если table[k] >= О оказывается равным false. Из двух толкований вер- ным оказывается именно второе, но не первое. Если же требуется, чтобы оператор действовал в соответствии с первым толкованием, необходимо указать внутренний оператор if как составной: if ((к >= 0) && (к < TABLE_SIZE)) { if (table[к] >= 0) printf("Значение %d равно %d\n:, к, table[к]); 1 else printf("Ошибка: Недопустимое значение индекса %d.\n",k); Для того же, чтобы избежать путаницы, разумно будет оформить в том же стиле и вариант, соответствующий второму толкованию: if ((к >= 0) && (к < TABLE_SIZE)) ( if (table[к] >= 0) printf("Значение %d равно %d\n:, к, table[к]); else printf("Ошибка: значение %d отрицательно.\n",к); 1 Как видим, выделение фигурными скобками операторов, управляемых опе- раторами if, помогает избегать путаницы. В то же время, это приводит к засо- рению исходного текста программ необязательными символами. Пожалуй, ра- зумным компромиссом в этой ситуации будет выделение фигурными скобками любой выполняемой части оператора if, за исключением выражений и пустых операторов. Ссылки: составной оператор 8.4; операторное выражение 8.2; пустой оператор 8.1 1.
286 Глава 8 8.6 Операторы циклов В языке С определены три вида циклов: оператор-цикла: onepamop-while onepamop-do onepamop-for В операторе while условие выхода из цикла проверяется перед каждым вы- полнением оператора, в операторе do — после выполнения. Что же касается оператора for, то здесь реализован особый синтаксис, удобный для инициали- зации и модификации одной или нескольких управляющих переменных, на- ряду с проверкой условия выхода из цикла. Выполняемый оператор, встраи- ваемый в цикл, именуется иногда телом (body) цикла. В С99 операторы циклов образуют блоки с собственной областью видимо- сти — как и подоператоры, даже не являющиеся составными. Таким образом ограничивается область видимости объектов и типов, которые могут созда- ваться неявно при использовании составных литералов или имен типов. Ссылки: составные литералы 7.4,5; управляющее выражение 8,1, имена типов 5.12. 8.6.1 Оператор while В языке С, оператор while не содержит ключевого слова do: onepamop-while: while ( выражение ) оператор При выполнении оператора while прежде всего происходит вычисление управляющего выражения. Если результат равен true (ненулевое значение), выполняется оператор и цикл повторяется; иначе происходит выход из цикла. В процессе прохождения цикла, значение управляющего выражения может меняться. Выполнение цикла завершается, когда результат вычисления управляюще- го выражения оказывается равным false (нулевым), либо когда происходит выход из тела цикла в результате выполнения оператора return, goto или break. Оператор continue также может оказывать воздействие на выполнение цикла while. Притер В следующей функции цикл while применен для возведения целой переменной base в степень, заданную неотрицательной целой переменной exponent (без проверки пе- реполнения). Результат достигается за счет многократного возведения base в квад- рат и представления exponent в двоичном формате для определения необходимости умножения результата на base. Чтобы понять, как это работает, следует обратить внимание на то, что значение result получается в результате многократного умножения на base, и что процесс прекраща- ется после того, как exponent становится равной 0. int pow(int base, int exponent) { int result = 1; while (exponent > 0) {
Операторы 287 if (exponent % 2) result *= base; base *= base; exponent /= 2; ) return result; 1 Пример Ниже приведен практический пример оператора while, тело которого представляет пустой оператор: while ( *char_pointer++ ); В этом примере символьный указатель перемещается по строке в результате выпол- нения операции ++ до тех пор, пока не доходит до нулевого символа. Таким обра- зом, происходит поиск конца строки. Обратите внимание, что тестовое выражение интерпретируется как *(char_poin.ter++), а не (*char_pointer)++; в последнем слу- чае происходило бы приращение не значения указателя, а кода символа, на кото- рый указывал бы char_pointer++. Пример Ниже приведен вариант применения двух указателей для копирования текстовой строки: while ( *dest_pointer++ = *source_pointer++ ); Копирование символов происходит до тех пор, пока указатель source_pointer не дойдет до нулевого символа (который также копируется). Конечно же, при написа- нии этого оператора, программист должен быть уверен в том, что под копию строки распределено достаточно памяти. Ссылки: операторы break и continue 8.8; управляющее выражение 8.1; оператор goto 8.10; пустой оператор 8.1 1; оператор return 8.9. 8.6.2 Оператор do Оператор do отличается от оператора while тем, что тело его выполняется ми- нимум один раз, тогда как в операторе while тело может не выполняться ни разу: onepamop-do: do оператор while ( выражение ); Выполнение оператора do начинается с выполнения содержащегося в нем оператора, затем вычисляется управляющее выражение. Если выражение воз- вращает значение true (ненулевое), цикл повторяется, иначе управление пере- дается следующему за ним оператору. Выполнение оператора do прекращается, если управляющее выражение возвращает 0 или происходит выход из цикла в результате выполнения опера- тора return, goto или break. Оператор continue также может оказывать воз- действие на выполнение цикла do. Оператор do в С подобен оператору «repeat-until» языка Pascal, но с одним отличием: в нем выход из цикла происходит, когда управляющее выражение возвращает значение false, тогда как в repeat-until это происходит после воз-
288 Глава 1 врата true. В этом отношении, вариант С более последователен, так как здесь выход из любого цикла (while, do или for) происходит, когда управляющее вы- ражение возвращает false. Пример Следующий фрагмент программы считывает и обрабатывает текстовые символы, прекращая работу после обработки символа смены строки: int ch; do process( ch = getchar()); while (ch != '\n'); Того же результата можно было бы добиться, переместив вычисление в управляю- щее выражение оператора while, но в этом случае назначение оператора будет менее очевидно: int ch; while ( ch = getchar (), process(ch), ch != '\n1) /* пусто */ ; Пример Можно написать оператор do, тело которого представляет пустой оператор: do ; while (выражение); Однако того же можно достичь при помощи более простого оператора while: while (выражение) ; Ссылки: операторы break и continue 8,8; управляющее выражение 8.1; оператор goto 8.10; пустой оператор 8.1 1; оператор return 8.9; оператор while 8.6.1. 8.6.3 Оператор for Оператор for в языке С намного более универсален, чем любые операторы циклов из других языков программирования, действующие по принципу по- шаговой проверки. Мы обсудим принцип действия этого оператора, затем рас- смотрим несколько примеров. onepamop-for: for for-выражения оператор for-выражения: ( начальное условие(!Пц ; выражение,)пц ; выражение,,,щ ) начальное-условие: выражение объявление (С99) Оператор for состоит из ключевого слова for, за которым следуют три выра- жения, разделенные точками с запятой и заключенные в скобки; далее следу- ет оператор. Каждое из трех выражений в скобках необязательно — то есть мо- жет быть опушено, — однако две разделительные точки с запятой, как и скоб- ки, должны быть указаны в любом случае.
Операторы 289 Первое выражение используется, как правило, для инициализации переме- ной цикла, второе — для проверки, следует ли завершить цикл, назначение третьего — изменение переменной цикла (чаще всего, приращение). В принци- пе, каждое из выражений может быть использовано для выполнения любых вычислений, которые могут понадобиться в процессе выполнения цикла. Вы- полнение оператора for происходит следующим образом. 1. Если началъное-условие представляет собой выражение, это выражение вычисляется, а результат удаляется. Если началъное-условие представляет собой объявление (С99), объяв- ленные переменные инициализируются. Если началъное-условие отсутствует, не происходит ничего. 2. При наличии второго выражения, оно вычисляется как управляющее выражение. Если результат оказывается нулевым, выполнение опера- тора for завершается. Иначе (при ненулевом результате или отсутствии второго выражения) выполнение продолжается, как указано в п. 3. 3. Выполняется тело оператора for. 4. При наличии третьего выражения, оно вычисляется, результат вычис- ления удаляется. 5. Возврат к пункту 2. Выполнение оператора for прекращается, когда второе (управляющее) вы- ражение возвращает нулевое значение или происходит выход из цикла в ре- зультате выполнения оператора return, goto или break. Выполнение в цикле оператора continue приводит к переходу к п. 4 приведенного алгоритма. В С99 операторы for образуют блок с собственной областью видимости — как и подоператоры, даже не являющиеся составными. Таким образом ограни- чивается область видимости объектов и типов, которые могут создаваться не- явно при использовании составных литералов или имен типов. Кроме этого, первое выражение в цикле for можно заменять объявлением и инициализаци- ей нескольких управляющих переменных. Область видимости этих перемен- ных распространяется до конца оператора for, включая второе и третье управ- ляющие выражения. Операторы for часто пишутся именно с такими управ- ляющими переменными, ограничение же их области видимости упрощает оптимизацию программ компилятором С. Ссылки: операторы break и continue 8.8; составные литералы 7.4.5; управляющее выраже- ние 8.1; удаляемые выражения 7.13; оператор goto 8.10; оператор return 8.9; имена ти- пов 5.12; оператор while 8.6.1. 8.6.4 Использование оператора for Пример Как правило, первое выражение в операторе for используется для инициализации управляющей переменной, второе — для тестирования этой переменной, третье — для модификации ее соответственно алгоритму, реализуемому циклом. Например, цикл распечатки целых значений от 0 до 9 и их квадратов может иметь следующий вид: int j ; for (j =0; j < 10; j++) printf("%d %d\n", j, j*j);
290 Глава 8 Здесь в первом выражении происходит инициализация переменной j, во втором — проверка, достигло ли ее значение 10 (если да, выполнение цикла заканчивается), в третьем выражении происходит приращение j. В С99 переменную j можно объявить внутри цикла, ограничив, тем самым, область ее видимости: for (int j = 0; j < 10; j++) printf (’’%d %d\n" , j, j*j); Пример В С возможны два способа написания «бесконечных» циклов: for (;;) оператор while (1) оператор Такой цикл прерывается задаваемым внутри оператором break, goto или return. Пример Функцию pow, которой мы воспользовались выше для иллюстрации оператора while, можно переделать под оператор for: int pow(int base, int exponent) { int result = 1; for (; exponent > 0; exponent /=2) ( if (exponent % 2) result *= base; base *= base; ) return result; 1 Здесь наглядно видно, что цикл управляется переменной exponent, достигающей нуля в результате многократного деления на 2. Обратите внимание, что эта перемен- ная по-прежнему должна быть объявлена вне оператора for — внутри его перемен- ные не объявляются. Пропуск объявления переменных для цикла — распространен- ная ошибка при программировании цикла for. В результате, цикл использует пере- менные (например, i и j), объявленные где-либо в ином месте; изменение этих переменных может приводить к непредсказуемым последствиям. Пример Ниже приведен простой пример процедуры сортировки методом вставок, void insertsort(int v[], int n) ( register int i, j, temp; for (i = 1, i < n, i++) ( temp = v[i]; for (j = i~l; j >= 0 && v[j] > temp; j--) v[j+l] = v[j]; v [ j +1 ] = temp ; } 1
Операторы 291 Переменная i во внешнем цикле for пробегает значения от 1 до n—1 включительно. На шаге i все элементы от v[0] до v[i—1] уже отсортированы, и остается сортировать только элементы от v[i] до v[n]. Переменная j во внутреннем цикле уменьшается от i— 1; при этом элементы массива перемещаются по одному вверх, пока не будет най- дено место для помещения v[i] — почему данный метод и называется методом вста- вок. Данный алгоритм не слишком хорош для сортировки больших массивов, по- скольку максимальное число выполняемых операций в нем пропорционально п*п — т.е. О(п2). Пример Метод вставок можно модернизировать, снизив число требуемых операций до (Цп1-25). Для этого достаточно поместить первые два цикла в третий и задать прира- щение управляющей переменной цикла равным переменной gap, значение которой может быть больше 1. Таким образом, мы получаем функцию, в которой использо- ван оболочечный (shell) алгоритм сортировки. Этот алгоритм подобен алгоритму shell, опубликованному, в качестве примера, Керниганом (Kernighan) и Ричи (Ritchie) в книге С Programming Language (Язык программирования С). Мы, одна- ко, внесли в него три усовершенствования, два из которых предложены Кнутом (Knuth) и Седжвиком (Sedgewick) — см. Предисловие, — что привело к некоторому повышению скорости. void shellsort(register int v[], int n) { register int gap, i, j, temp; gap = 1; do (gap = 3*gap +1); while (gap <= n); for (gap /= 3; gap > 0; gap /= 3) for (i = gap, i < n, i++) ( temp = V[i]; for (j = i-gap; (j>=0)&&(v[j]>terap); j-=gap) v[ j+gap] = v[j] ; v[j+gap] = temp; 1 1 Усовершенствования, внесенные в алгоритм shell: 1. В первоначальном варианте исходное значение gap задавалось равным п/2, затем делилось на 2 на каждом шаге цикла. В нашем варианте исходное значение gap рав- но наибольшему члену последовательности (1, 4, 13, 40, 121, ...), не превышающему п, затем, на каждом шаге вешнего цикла, значение gap делится на 3. Это приводит к ускорению сортировки на 2-30 % . Показано, что данный выбор исходного значе- ния gap эффективнее варианта gap = и.. 2. Число операторов присваивания во внутреннем цикле уменьшено с трех до одного. 3. Добавлены классы памяти register и void. В некоторых случаях, применение класса памяти register очень резко повышает скорость выполнения (до 40%). Пример Управляющая переменная цикла не обязана принадлежать к целому типу. Ниже приведен пример с управляющей переменной-указателем, используемой для про- смотра связанной цепочки структур. struct intlist ( struct intlist *link;
292 Глава 8 int data; } void print-duplicates(struct intlist *p) { for (; p; p = p->link) { struct intlist *q; for (q = p->link; q; q = q->link) if (q->data == p->data) ( printf("Duplicate data %d", p->data), break; i } } Структура intlist связывает в цепочку записи данных — связный список. Функция print_duplicates просматривает этот список и выводит содержащиеся в нем дубли- рованные Записи. В первом цикле в качестве управляющей переменной использует- ся формальный параметр р — указатель, ссылающийся поочередно на каждый из элементов списка. Цикл завершается на записи, содержащей нулевой указатель на следующую запись. Во внутреннем цикле указатель q проходит все записи, следую- щие за записью, обрабатываемой внешним циклом. Ссылки: ссылочные типы 5.3; класс памяти register 4.3; операция ссылки на элемент структу- ры 7.4.2; структурные типы 5.6; тип void 5.9. 8.6.5 Циклы с несколькими управляющими переменными Иногда оказывается удобным строить циклы с более чем одной управляю- щей переменной. В этом случае особенно удобно использовать список выраже- ний через запятую для объединения нескольких выражений в одно. Пример Следующая функция меняет порядок следования элементов связного списка на об- ратный. struct intlist { struct intlist *link; int data; }; struct intlist ‘reverse(struct intlist *p) { struct intlist ‘here, *previous, ‘next; for (here = p, previous = NULL; here != NULL ; next = here->link, here->link = previous, previous = here, here = next) /* пусто */ ; return previous; } Пример Приведенная ниже функция string_equal принимает два строковых аргумента и возвращает 1, если они равны между собой, 0 — если не равны. int string_equal(const char *sl, const char *s2) { char *plt *p2; for (pl=sl/ p2=s2; *pl && *p2; pl++. p2++)
if (*pl != *p2) return 0; return *pl — *p2; ) В цикле for две ссылочные переменные перемещаются, параллельно, по двум стро- кам. Выражение р1++, р2++ перемещает оба указателя на следующий символ каж- дой строки. Если символы, на которые ссылаются указатели, оказываются не рав- ными между собой, выполнение функции прерывается оператором возврата, возвра- щающим значение 0. Если один из указателей переходит на нулевой символ (это описывается выражением *р1 && *р2), цикл завершается обычным образом, второй же оператор return определяет, располагаются ли нулевые символы каждой из строк в одной и той же позиции. Следует заметить, что функция могла бы работать правильно даже в случае, если заменить выражение *р1 && *р2 на *р1. Более того, в этом варианте функция выполнялась бы быстрее, но текст ее был бы менее нагля- ден. Ссылки: операторы break и continue 8.8; список выражений 7.10; ссылочные типы 5.3; опе- рация ссылки на элемент структуры —> 7.4.2; структурные типы 5.6. 8.7 Операторы switch Оператор switch — это конструкция, обеспечивающая множество вариан- тов перехода в зависимости от значения управляющего выражения. Этот опе- ратор действует подобно оператору «case» из языков Pascal или Ada, но реали- зация его ближе к оператору «вычисляемого goto» из языка FORTRAN. оператор-switch: switch (выражение) оператор метка-case: case конспгантное-выражение метка-default: default Управляющее выражение, следующее за ключевым словом switch, должно возвращать значение целого типа и может подвергаться преобразованиям в обычных операциях с одним аргументом (унарных). Выражение, следующее за ключевым словом case, должно быть целым константным выражением (см. раздел 7.11.2). Оператор, входящий в состав конструкции switch, иногда име- нуется телом (body) оператора switch. Как правило (хотя и не всегда), это со- ставной оператор. Метка case или default считается принадлежащей самому внутреннему из вложенных операторов switch, в котором она расположена. Любой оператор, располагающийся в теле оператора switch, в том числе и само тело, может быть помечен меткой case или default. Более того, один и тот же оператор мо- жет быть помечен несколькими метками case и, вдобавок, меткой default. Метки case и default не могут располагаться вне тела оператора switch. Не мо- жет существовать двух меток case, принадлежащих одному оператору switch и имеющих константные выражения с одним и тем же значением. Любой опе- ратор switch может содержать не более одной метки default. Оператор switch выполняется в следующей последовательности.
294 Глава 8 1. Вычисляется управляющее выражение. 2. Если значение управляющего выражения совпадает со значением кон- стантного выражения одной из меток case, принадлежащих оператору switch, управление передается оператору, помеченному этой меткой, подобно тому, как это делается при выполнении оператора goto. 3. Если значение управляющего выражения не совпадает со значением константного выражения ни одной из меток case, но в операторе switch есть оператор, помеченный меткой default, управление передается это- му оператору. 4. Если значение управляющего выражения не совпадает со значением константного выражения ни одной из меток case и в операторе switch нет метки default, ни один из операторов тела оператора switch не вы- полняется, а управление передается оператору, следующему непосред- ственно за оператором switch. Если при сравнении значений управляющего выражения и константного значения метки case их типы не совпадают, происходит преобразование типа значения метки в тип управляющего выражения. Порядок, в котором происходит сравнение значений управляющего выра- жения и меток case, не определен и может зависеть от числа и значений по- следних. Программисты часто полагают, что оператор switch выполняется по- добно последовательности операторов if и в том порядке, в каком расположе- ны метки case, но этот порядок может не соблюдаться. Когда управление передается оператору, помеченному меткой case или default, после его выполнения происходит выполнение всех последующих опе- раторов, вне зависимости от меток case или default, которыми они помечены, до конца оператора switch либо до оператора перехода (goto, return, break или continue), который передаст управление в точку за его пределами. Несмотря на то, что стандарт (Standard С) разрешает управляющему выра- жению иметь любой целый тип, в некоторых ранних компиляторах оно не мо- жет иметь тип long или unsigned long. Стандарт, кроме этого, разрешает огра- ничение числа меток case в операторе switch. В С89 это число не может превы- шать 257, в С99 — 1023. Конечно же, этого более чем достаточно — особенно, если ориентироваться на 8-битовый тип char. Если какой-либо объект динамического типа в С99 оказывается видимым на любой метке case или default, соответствующий оператор switch должен полностью входить в область видимости этого объекта. Иными словами, ни один объект динамического типа не может включать в свою область видимости только часть оператора switch, за исключением ситуации, когда вся его об- ласть видимости заключена в одной ветви case или default. Попробуем пере- формулировать еще раз: невозможно «укрыть» метку case или default в блоке, содержащем объект динамического типа. Ссылки: операторы break и continue 8 8, константные выражения 7.11; оператор goto 8.10; целые типы 5.1; оператор с меткой 8.3, оператор return 8.9, динамические типы 5 4.5 8. 7.1 Использование операторов switch Как правило, тело оператора switch — это составной оператор, внутренние операторы которого (операторы более высокого уровня) помечены метками case и (или) default. Следует заметить, что метки case или default никак не
Операторы 295 влияют на порядок выполнения операторов программы — операторы с метка- ми выполняются точно так же, как и непомеченные. Для прекращения выпол- нения оператора switch в любой точке его тела следует воспользоваться опера- тором break. Пример switch (х) { case 1: printf ("*"); case 2: printf("**"); case 3: printf("***") ; case 4: printf("****"); } Указанный оператор при x, равном 2, выведет девять звездочек. Дело в том, что по- сле передачи управления на оператор с меткой 2, будет выполнен не только этот опе- ратор, но также и два, следующие за ним. Чтобы в данной конструкции выполнялся только тот оператор, управление на который было передано по значению управляю- щей переменной, необходимо разделить операторы с метками case оператором break, как в следующем примере: switch (х) { case 1: printf("*"); break; case 2: printf("**"); break; case 3: printf("***"); break; case 4: printf("****"); break; } Совершенно очевидно, что в последнем операторе break нет никакой необходимо- сти, однако задание его считается признаком хорошего стиля программирования, поскольку предотвращает, в какой-то мере, ошибки при добавлении в эту конструк- цию новых операторов. Мы советуем придерживаться при составлении операторов switch следую- щего простого правила. Тело оператора switch должно всегда быть составным оператором, и метки должны устанавливаться на внутренних операторах этого составного оператора. Требования к стилю этого оператора совпадают со сти- листическими требованиями к оператору goto. Кроме этого, перед каждой меткой case (или default), кроме первой, следует задавать оператор break, пре- рывающий выполнение оператора switch, либо комментарий, уведомляющий о намеренном пропуске этого оператора. Требование применения в качестве тела оператора switch составного опера- тора, компонентами которого являются операторы с метками case и default — чисто стилистическое и никак не связано с синтаксисом языка, не устанавли- вающего также какого-либо порядка следования этих меток. Пример Комментарий в следующем фрагменте программы сообщает о том, что оператор break после метки case fatal пропущен намеренно. case fatal: printf("Fatal ");
296 Глава 8 /* Продолжить выполнение */ case error: printf("Ошибка"); ++error_count; break; Пример Ниже приведен пример того, куда могут завести благие намерения — в данном слу- чае, намерение максимально эффективно реализовать несложный алгоритм. if (prime(х) ) process_prime(х); else process_composite(х); Функция prime возвращает 1, если ее аргумент — простое число, и 0 — если состав- ное. Тестирование программы показало, что в большинстве обращений к функции prime значение аргумента было небольшим. Чтобы уменьшить число обращений к функции prime, обработка значений была перенесена в оператор switch — притом, таким образом, чтобы большие значения обрабатывались оператором с меткой default. Далее, программа последовательно уплотнялась, пока не была приведена к следующему виду: switch(х) default; if (prime(х)) case 2: case 3: case 5: case 7: process_prime(x); else case 4: case 6: case 8: case 9: case 10: else process_composite(x); Пожалуй, это самый причудливый из когда-либо составленных операторов switch, который, тем не менее, вполне работоспособен. 8.9 Операторы break и continue Операторы break и continue применяются для изменения порядка выпол- нения операторов внутри цикла, a break — также и в операторе switch. Эти операторы стилистически предпочтительнее оператора goto, который может применяться с этой же целью. onepamop-break: break; onepamop-continue: continue; Выполнение оператора break приводит к выходу из наименьшего (самого вложенного) цикла while, do, for или оператора switch, которым этот оператор принадлежит. Управление передается оператору, следующему непосредствен- но за циклом (оператором switch). Размещение оператора break вне цикла или оператора switch является ошибкой. Выполнение оператора continue приводит к переходу в конец наименьшего (самого вложенного) цикла while, do или for, которым этот оператор принад- лежит, после чего выполнение цикла продолжается с этой точки с новым вы-
Операторы 297 числением управляющего выражения (или приращением управляющей пере- менной — в случае цикла for). Размещение оператора continue вне цикла или оператора switch является ошибкой. Оператор continue не применяется внутри оператора switch, но может при- меняться внутри оператора цикла, располагающегося внутри оператора switch. Пример Операторы break и continue можно рассматривать как варианты оператора goto. Рассмотрим конструкции, в которых могут быть использованы операторы break и continue: while ( выражение ) оператор do оператор while ( выражение ); for (выражение!; выражениег; выражениез) оператор switch ( выражение ) оператор Попробуем переписать эти операторы в следующем виде: { while ( выражение ) {оператор С:;} В:;} { do {операторе:;} while ( выражение ); В:;} { for (выражение! • выражениег; выражениез) {оператор С:;) В:;} { switch ( выражение ) оператор В:;} Здесь В и С — метки, расставленные таким образом, что оператор break, в любой из указанных конструкций, оказывается равнозначным оператору goto В;, оператор continue (за исключением оператора switch, в котором он не применяется) — опера- тору goto С;. Конечно же, все это — в предположении, что тела циклов не содержат других циклов с операторами break и continue. Пример Существуют два важных и, одновременно, распространенных варианта применения оператора break: для выхода из оператора case, располагающегося внутри оператора switch, и для преждевременного прерывания цикла. Первое из этих применений про- иллюстрировано примером оператора switch в разделе 8.7, второе — следующим при- мером заполнения массива текстовыми символами. В последнем случае, процесс за- вершается при достижении конца массива или после исчерпания набора символов. ifinclude <stdio.h> static char array[100]; int i, c; for (i = 0; i < 100; i++) { c = getchar {); if (c = EOF) break; /‘Закончить, если указатель в конце файла*/ array[i] = с; } /* Теперь i равно фактическому числу прочитанных символов */ Обратите внимание, что оператор break в данном случае использован для обработки аварийного состояния, чего допускать не следует. Пример Ниже приведен пример применения оператора break внутри «бесконечного» цикла. Задача — максимально быстрое определение наименьшего элемента массива а (дли- ной N). Допускается временное изменение массива.
298 Глава 8 int temp = a[0]; register int smallest = a[O]; register int ptr = Sa[N]; /* Позиция, следующая за последним элементом а */ for (;,) { while (*--ptr > smallest) ; if (ptr == sa[OJ) break; a[0] = smallest = *ptr; 1 a [ 0] = temp; Важной особенностью этого примера является то, что вся работа выполняется в ком- пактном цикле while, перемещающем указатель по массиву от конца к началу. При этом элементы массива, значения которых больше обнаруженного на данный момент наименьшего значения, пропускаются. (Если элементы массива располагаются в про- извольном порядке, обнаружение достаточно малого элемента массива позволит про- пускать большую часть элементов.) Цикл не может выйти за начало массива, по- скольку текущее наименьшее значение записывается в первый его элемент. После за- вершения цикла while, если оказывается, что указатель достиг начала цикла, происходит выход из внешнего цикла за счет выполнения оператора break; иначе за- даются новые значения а[0] и smallest, затем повторяется выполнение цикла while. Пример Сравним предыдущий пример с более простым и логичным вариантом: register int smallest = а[0]; register int j; for (j = 1; j < N; ++j) if (3[j) < smallest) smallest = a[j]; Конечно же, этот вариант более нагляден. Однако здесь на каждом шаге приходится выполнять проверку условия j < N, чтобы не выйти за пределы массива. В предыду- щем, более изящном, варианте эта проверка выполняется неявно. В определенных обстоятельствах, когда скорость оказывается решающим фактором, сложный вари- ант может оказаться предпочтительным; в остальных случаях лучше выбрать про- стую и наглядную схему. Ссылки: оператор do 8 6.2; оператор for 8.6.3; оператор goto 8 10; оператор switch 8.7, оператор while 8.6.1. 8.9 Операторы return Оператор return применяется для завершения выполнения функции, ино- гда — с возвратом значения: оператор-return: return выражениеогщ ; Выполнение оператора return приводит к завершению выполнения функ- ции, в которой он располагается; управление передается оператору вызываю- щей функции, следующему непосредственно за оператором, вызвавшим дан- ную функцию. В С99 оператор return может быть указан без какого-либо выражения толь- ко в функции типа void. В С89 это допускается также в функциях других ти-
Операторы 299 нов, однако следует иметь в виду, что последствия попыток использования значения такой функции непредсказуемы. Если оператор return указан с возвращаемым выражением, функция долж- на иметь тип, отличный от void. Выражение преобразуется к типу функции, как это делается в операторе присваивания. Если преобразование типа оказы- вается невозможным, оператор return оказывается ошибочным. Результат завершения выполнения функции без оператора return оказыва- ется таким же, как и выход из нее по return без выражения. Если при этом функция имеет тип, отличный от типа void, возвращаемое ею значение оказы- вается неопределенным. Пример Многие программисты заключают выражение оператора return, в скобки, хотя в этом и нет необходимости. Вероятно, эта привычка порождена синтаксисом таких операторов, как switch, if, while и т.п.: int twice(int x) { return (2*x); } Ссылки: удаляемые значения 7.13; вызов функции 7.4.3; определение функции 9.1; соглаше- ние о типах возвращаемых функциями значений 9.8. 8.10 Операторы goto Оператор goto применяется для передачи управления любому оператору внутри функции: оператор-goto: goto именованная-метка ; именованная-метка: идентификатор Идентификатор, следующий за ключевым словом goto, должен совпадать с меткой одного из операторов функции. Выполнение оператора goto приводит к передаче управления на оператор, помеченный указанной меткой, и выпол- нению этого оператора. Ссылки: оператор с меткой 8.3. 8.40.1 Применение оператора goto Оператор goto в языке С применяется для передачи управления любому оператору внутри данной функции, однако некоторые из таких переходов мо- гут приводить к запутыванию программы и препятствовать оптимизации при компилировании. По этой причине, не рекомендуется делать переходы внутрь частей «then» или «else» оператора if извне его, из части «then» в часть «else» и обратно, в тело оператора switch или цикла, а также внутрь составного опе- ратора — опять же, извне. Впрочем, подобных переходов следует избегать не только в отношении оператора goto, но также при расстановке меток операто- ра switch. При переходе внутрь составного оператора не выполняется инициа- лизация переменных в первых его строках. Хороший стиль программирова-
300 Глава 8 ния предполагает использование, по возможности, вместо оператора goto опе- раторов break, continue и return. Пример Несмотря на все предостережения, использование оператора goto иногда может быть вполне оправданно. В следующем примере происходит поиск значения v в дву- мерном массиве а. Когда значение обнаруживается, происходит выход при помощи оператора goto из двух вложенных друг в друга циклов с сохранением значений пе- ременных этих циклов i и j. ttinclude <stdio.h> int i, j, v, a[N] [M] ; for (i=0, i++; i < N) for (j=0, j++; j < M) if (a[i][j] == v) goto found; printf("а не содержит %d\n", v); found: printf("a[%d][%d]==%d\n", i, j, v), Ссылки: операторы break и continue 8.8; управляющее выражение 8.1; оператор if 8.5; оператор с меткой 8.3; оперотор return 8.9, оператор switch 8.7. 8.11 Пустые операторы Пустой оператор обозначается просто точкой с запятой: пустой-оператор: ♦ Пустой оператор применяется, главным образом, в двух случаях: во-пер- вых, как пустое тело оператора цикла (while, do или for); во-вторых, когда требуется установить метку прямо перед закрывающей фигурной скобкой, обозначающей составной оператор. Метку нельзя устанавливать прямо перед скобкой, поскольку она должна помечать оператор. Пример Следующий цикл обходится без тела, поскольку всю работу выполняет управляю- щее выражение: char *р; while ( р++ ); Поиск конца строки */ Пример Метка L устанавливается на пустой оператор: if (е) { goto L; /* Завершение ветви if */ L: ;} else ...
Операторы 301 Ссылки: оператор do 8.6.2; оператор for 8.6.2; оператор с меткой 8.3; оператор while 8.6.1. 8.12 Совместимость с C++ 8.12.1 Составные операторы В C++ не разрешается переход внутрь составного оператора с обходом объ- явлений, содержащих инициализаторы. Пример goto L; /* Разрешено, но не рекомендуется в С; не разрешено в C++ */ { int i = 10; L: ) Ссылки: переход внутрь составного оператора 8.4.2. 8.12.2 Объявления в циклах В С99 разрешено объявление переменных в начальном условии цикла for; область видимости этих объявлений простирается до конца тела цикла. Это полностью соответствует правилам синтаксиса Standard C++. В некоторых ранних версиях C++ область видимости этих объявлений выходит за пределы цикла в содержащие его составной оператор или функцию. 8.13 Упражнения 1. Перепишите следующие конструкции без использования операторов for, while и do: (a) for (n=A;n<B;n++) suin+=n; (b) while (a<b) a++; (c) do sum+=*p; while (++p < q); 2. Каково значение j в конце следующего фрагмента программы? { int j=l; goto L; { static int i = 3; L: j = i; } } 3. Каково значение sum после выполнения следующего фрагмента про- граммы?
302 Глава 8 int i,sum = 0; for(i=0;i<10;i++) { switch{i) { case 0: case 1: case 3: case 5: sum++; default: continue; case 4: break; } break; }
Глава 9 Функции В этой главе мы будем рассматривать функции — особенности их объявле- ния и определения, задание формальных параметров и типов возвращаемых данных, вызов. Функции уже упоминались несколько раз по ходу изложения. Описатели функций рассматривались в разделе 4.5.4, типы и объявления — в разделе 5.8. Со времени появления первых версий языка С описание функций несколько усложнилось. В Standard С был предложен новый, более совершенный, способ объявления функций с использованием прототипов, содержащих подробные описания параметров. С появлением прототипов функций изменился способ их вызова. Объявления функций — с прототипами или без, — не содержат в себе ничего сложного, однако при смешении обеих форм в одной функции приходит- ся руководствоваться сложной системой правил, чтобы определить способ их взаимодействия. В C++ функции объявляются только с прототипами. Наличие прототипа функции определяется синтаксисом ее описателя (раз- дел 4.5.4). В традиционном С, если функция объявлена без прототипа: 1. Перед вызовом функции ее аргументы подвергаются обычному преоб- разованию. 2. Проверка типов или числа аргументов не проводится. 3. Функция может вызываться с разным числом аргументов. Если же функция объявлена с прототипом: 1. Происходит преобразование типов аргументов функции к объявленным типам формальных параметров. 2. Число и типы аргументов должны соответствовать указанным в объяв- лении функции; несоблюдение этого условия приводит к ошибке. 3. Функции с переменным числом аргументов должны объявляться явно, неуказанные аргументы подвергаются стандартному преобразованию. Вопрос использования (или неиспользования) прототипов в объявлениях функций непрост и связан с мобильностью программы. Чтобы сохранить со- вместимость с реализациями, не относящимися к Standard С, от прототипов следует отказаться. Напротив, для совместимости с C++ необходимо объяв- лять функции с прототипами. Можно совмещать обе формы, используя опера- торы условной компиляции, но это тоже не слишком удобно. В следующем разделе мы рассмотрим объявление функций с прототипами и без, а также не- которые вопросы мобильности (переносимости на другие платформы).
304 Глава 9 9.1 Определения функций Определение — это представление новой функции, содержащее следующую информацию: 1. Тип возвращаемого значения (для функций, возвращающих значения). 2. Типы и число формальных параметров. 3. Видимость функции вне файла, в котором она определена. 4. Код, выполняемый при вызове функции. Описание синтаксиса определения функции дается ниже. Объявления функций могут располагаться только на верхнем уровне исходного файла или единицы трансляции. единица-трансляции: объявление-верхнего-у ровня единица-трансляции объявление-верхнего-у ровня объявление-верхнего-уровня: объявление определение-функции определение-функции: спе.цификатор-определения-фу акции составной-оператор спецификатор-определения-функции: спецификаторы-объявлениЯспц описатель список-объявленийи1Щ список-объявлений: объявление список-объявлений объявление Синтаксис других объявлений верхнего уровня рассмотрен нами в главе 4. В версиях, предшествовавших С99, если в части спецификатор-объявленияО1Щ отсутствовал спецификатор типа, предполагался тип int. В С99 указание спе- цификатора типа обязательно. Описатель внутри части спецификатор-определения-функции должен со- держать элемент описатель-функции с ее именем непосредственно перед левой скобкой. Синтаксис описателя функции рассматривался в разделе 4.5.4 и при- водится еще раз ниже: описатель-функции: прямой-описателъ ( список типов параметров ) (С89) прямой-описателъ ( список-идентификаторов0ПЦ ) список-типов-параметров: список-параметров список-параметров, ... список-параметров: объявление-параметра список-параметров, объявление-параметра
Функции 305 объявление-параметра: спецификаторы-объявления описатель спецификаторы-объявления абстрактный-описатель0ГЩ список-идентификаторов: идентификатор список-идентификаторов, идентификатор Если описатель функции, указывающий ее имя, включает элемент список- типов-параметров, этот описатель считается прототипом, иначе — описате- лем традиционного вида. Прототип содержит в описателе объявления имен и типов всех параметров; список-объявлений, следующий за описателем, остается пустым. Хороший стиль программирования предполагает определение всех функций языка С в форме прототипов. В традиционных реализациях, предшествовавших Standard С, имена пара- метров перечисляются в описателе, типы (в любом порядке) — в списке-объявле- ний.оПц, следующем за описателем. Все параметры объявляются в списке-объяв- лении, однако в версиях, предшествовавших С99, тип необъявленного парамет- ра принимался, по умолчанию, равным int. Традиционные формы объявления функций отнесены в С99 к устаревшим, что может означать прекращение их поддержки в будущем. Пример int f(int i, long j) { ... } /* прототип */ int f(i, j) int i; long j { ... } /* традиционная форма */ На форму спецификатора-определения-функции налагаются определенные ограничения. Идентификатору, объявленному в определении функции, дол- жен быть присвоен тип, указываемый в описателе определения. Это означает, что описатель должен содержать, непосредственно перед левой скобкой, описа- тель-функции, указывающий ее идентификатор. Идентификатору нельзя при- своить «функциональный» тип через имя типа. Функция не может возвращать массив или другую функцию. В описателе должны быть указаны имена параметров функции. Если описа- тель имеет форму прототипа, объявление-параметров должно включать описа- тель, но не абстрактный-описатель. Если описатель составлен не в форме прототипа, он, в случае наличия у функции аргументов, должен включать спи- сок-идентификаторов. Чтобы не возникало неопределенности, запрещено сов- падение имен параметров с видимыми именами typedef (в первых компилято- рах это ограничение отсутствовало). В объявлениях параметров разрешен только один класс памяти — register. Элемент список-объявленийопц не разрешен в прототипах (только в традици- онных объявлениях) и может содержать только объявления и идентификаторы параметров. В некоторых традиционных компиляторах С допускается дополни- тельные объявления (например, структур или типов), однако смысл таких объ- явлений не вполне ясен, потому их лучше размещать в теле функции. Пример Для иллюстрации рассмотренных правил, ниже приведены примеры правильно со- ставленных объявлений функций.
306 Глава 9 Определение Описание void f() Функция f не имеет параметров и не возвращоет значений {...} (традиционная форма). int g(x, у) int x, у; {...} Функция g вызывается с двумя целыми параметрами и возвращоет целое значение (традиционная форма). int h(int x, int y) {...} Функция h вызывается с двумя целыми параметрами и возвращоет целое значение (прототип). int (*f(int x))Q {•••} Функция f вызывается с одним целым параметром и возвращоет указатель на массив целых значений (прототип). Ниже перечислены неправильно составленные объявления с описанием ошибок. Предполагается объявление имени типа typedef int Т(). Определение Описание int (*q) {...} Tr{...} q — указатель, о не функция. Идентификатор г нельзя объявить функцией, присвоив ему тип, определенный в typedef. Ts(){...} Функция не может возврощоть функцию. void t(int, double) {...} void u(int x, y) int y; Не указаны имена параметров функции t. Только часть объявления составлено в форме прототипа. {•••} В определении функции не могут быть указаны иные спецификаторы клас- са памяти, кроме extern и static. Класс памяти extern означает возможность обращения к функции из другого файла — то есть имя функции экспортирует- ся компоновщику. Спецификатор static означает невозможность обращения к функции из другого файла (имя функции не экспортируется компоновщи- ку). Если класс памяти, в определении функции, не указан, по умолчанию принимается extern. В любом случае, функция видима от точки ее определе- ния до конца файла, в частности, она видима в собственном теле. Ссылки: описатели 4,5; класс памяти extern 4.3; объявления функций 5 8; объявления с ини- циализацией 4.1, класс памяти static 4 3, спецификаторы типов 4.4 9.2 Прототипы функций Прототип функции — это ее объявление (еписок типов параметров) или определение в форме прототипа. Как и в традиционном объявлении функции, в прототипе указывается тип возвращаемого значения. Однако в отличие от традиционного объявления, в прототипе указываются также число и типы формальных параметров. Современное программирование на языке С подразу- мевает объявление функций в форме прототипов. В С99 традиционная форма объявления функций рассматривается как устаревшая. Существуют три основные вида прототипов: для функций без параметров, с фиксированным числом параметров и с переменным числом параметров:
Функции 307 1. Если функция не имеет параметров, список типов параметров состоит из одного спецификатора типа void. Пустой список параметров в опре- делении функции означает то же, что и ключевое слово void, однако этот вариант обозначения отсутствия параметров устарел, а потому его не следует применять. Пример extern int random_generator(void); static void do nothing(void) {) /* ключевое слово void необязательно */ 2. В функции с фиксированным числом параметров, типы параметров указываются в списке типов параметров. Если в объявлении функции присутствует прототип, при необходимости, в него могут быть включе- ны имена параметров (это удобно для документирования функции). Имена параметров указываются в определении функции. Пример double square(double х) { return х*х } extern char *strncpy(char *, const char ★, size t); 3. В функции с переменным числом параметров или с параметрами раз- ных типов, некоторое число фиксированных параметров указывается, как описано выше, затем следуют запятая и многоточие (...). Список па- раметров должен содержать минимум один фиксированный пара- метр — иначе к функции невозможно будет обратиться при помощи стандартных средств из stdarg.h. Пример Ниже приведен пример объявления функции с переменным числом параметров. Имена параметров соответствуют требованиям, установленным для указанной стан- дартной библиотеки. extern int printf( FILE *_file, const char *_format, ...); Пример Прототипы можно применять в любом описателе функции, даже если в нем объяв- ляются довольно сложные типы. Объявление функции signal (раздел 19.6) в Stan- dard С имеет вид: void (*signal(int sig, void (*func)(int siga)))(int siga), Объявленная функция signal вызывается с двумя аргументами: sig (целым) и func — указателем на функцию типа void с одним целым аргументом. Значение, возвращаемой функцией signal, имеет тот же тип, что и ее второй аргумент — то есть указатель на функцию типа void с одним целым аргументом. Приведенное объ- явление можно переписать в более наглядном виде: typedef void sig_handler(int siga); sig_handler *signal(int sig, sig_handler *func); Однако указывать имя sig_handler в typedef нельзя ввиду правил, регулирующих определение функций, поэтому определение типа придется повторить: void new_sig_handler(int siga) (...)
308 Глава 9 Прототипы можно смешивать с традиционной формой в одном объявлении. В сле- дующем объявлении: typedef void sig_handler2(); /* не прототип */ sig_handler2 *signal2(int sig, sig_handler2 *func); второй аргумент функции signa!2 объявлен в традиционной форме, хотя сама функ- ция объявлена в форме прототипа. Ссылки: описотель функции 4.5 4; объявления функций 5.8; определения функций 9 1; тип void 5.9. 9.2.1 Действие прототипа Чтобы предвидеть, каким образом будет происходить вызов функции, про- граммисту необходимо знать, задано ли поведение функции прототипом. Вы- зов функции определяется прототипом в следующих случаях. 1. Если объявление функции или типа видимо и выполнено в форме про- тотипа. 2. Если определение функции видимо и выполнено в форме прототипа. Обратите внимание, что требуется лишь видимость любого прототипа функ- ции — могут существовать другие непрототипные видимые объявления или определения. При наличии нескольких прототипов одной функции (или типа функции), либо объявления и определения прототипа, эти объявления и определения должны быть совместимы соответственно правилам раздела 5.11.4. Ссылки: совместимые и составные типы 5.1 1. 9.2.2 Сочетание прототипов и традиционных объявлений Несмотря на то, что смешение прототипов с традиционными объявлениями для описания одной и той же функции не рекомендуется, в Standard С опреде- лены условия, допускающие такое смешение (см. раздел 5.11.4). Если функция вызывается с аргументами, не соответствующими ее опреде- лению, поведение вызова оказывается непредсказуемым. Традиционный язык С предполагает полную ответственность программиста за соответствие аргу- ментов, указываемых в вызове, объявленным. При этом, однако, аргументы и параметры преобразуются к типам меньших размеров — возможно, более удобным. В Standard С, благодаря объявлениям в форме прототипов, компиля- тор может проверить соответствие аргументов прототипу при вызове. В зависимости от того, где располагаются объявления функций, способы вызова некоторых из этих функций могут определяться прототипами, дру- гих — традиционными объявлениями, третьих — фактическими определения- ми функций. Вызов функции и ее определение могут располагаться в одном исходном файле либо в разных. В тех случаях, когда вызов функции не опре- деляется прототипом, полная ответственность за правильное формирование вызова ложится на программиста.
Функции 309 Пример В целом возможно существование множества прототипов, которые по отдельности совместимы с некоторыми традиционными объявлениями. Предположим, к приме- ру, где-то в программе С задано одно из таких традиционных объявлений: extern int f () ; Рассмотрим несколько прототипов, совместимых либо несовместимых с указанным объявлением: Прототип Совместимость c int f() Причина extern double f(void); нет Слисок параметров правилен, но не совпадают типы возвращаемых значений. extern int f(int, float); нет Обычно floot преобразуется в double; данные два типа несовместимы. extern int f(double x); да Тип параметро не меняется при преобразовании. extern int f(int i, ...); нет Прототип не мажет содержать многоточий. extern int f(float *); да Аргумент ссылочного типо не преобразуется. Пожалуй, существует всего один прототип, совместимый с традиционным объявлением функции. Он известен как прототип Миранды, поскольку «при- сваивается» объявлению функции, которое иначе не имело бы прототипа (по- хоже, название происходит от «правила Миранды», разрешающего не давать в суде показания против себя — прим, перев.) Standard С предписывает объявление функций с переменным числом пара- метров в форме прототипов. Это означает, что при переносе в Standard С про- грамм предыдущих версий, придется переписать объявления всех функций с переменным числом параметров (например, printf) в форме прототипов. Пример Предположим, в некоторой программе С установлено следующее (традиционное) оп- ределение: int f(x, у) float х; int у; (...) Ниже перечислены несколько прототипов, совместимых либо несовместимых с этим определением. Прототип Совместимость c int f() Причина extern double f(double х, int у); нет Список параметров правилен, но не совпадают типы возвращаемых значений. extern int f(float, int); нет Первый параметр должен иметь тип double. extern int f(float, int, ...); нет Прототип не может содержать многоточий. extern int f(double a, int b); да Единственный совместимый прототип; имена параметров несущественны Ссылки: совместимые типы 5.1 1; printf 15.1 1
310 Глава 9 9.2.3 Разумное применение прототипов Сверка аргументов с прототипами не гарантирует от ошибок. Программа, написанная на С, состоит из множества исходных файлов, и компилятор не в состоянии проверить, все ли обращения к той или иной функции соответст- вуют прототипу, совместимы ли все прототипы некоторой функции и соответ- ствуют ли они ее определению. Тем не менее, можно, в значительной мере, обезопасить себя от ошибок по- добного рода, если следовать нескольким простым правилам: 1. Каждая внешняя функция должна иметь единственный прототип в за- головочном файле. Наличие единственного прототипа устраняет саму проблему несовместимости прототипов. 2. К каждому исходному файлу, содержащему вызов внешней функции, должен подключаться заголовочный файл с ее прототипом. Этим гаран- тируется выполнение всех вызовов этой функции по одному прототипу и обеспечивается возможность проверки аргументов компилятором. 3. Тот же заголовочный файл должен быть подключен и к исходному фай- лу, в котором функция определена. Это дает возможность компилятору сверить прототип с объявлением и, следовательно, гарантировать соот- ветствие всех вызовов функции ее определению. Определение функции может быть не в форме прототипа. Эти же правила применимы к программированию статических функций. Необходимо следить за тем, чтобы прототипы статических функций распола- гались до их вызовов и определений. 9.2.4 Прототипы и соглашения о вызовах функций Эта глава интересна, в первую очередь, тем, кто пишет компиляторы. Тем не менее, углубленное понимание правил, реализуемых прототипами, будет небесполезно всем. Одно из достоинств прототипов состоит в том, что с их по- мощью компилятор может генерировать более эффективные вызовы некото- рых функций. Пример В традиционном С, если функции был определен параметр типа float, компилятор обязан был задать преобразование его в тип double, чтобы затем, после вызова функ- ции, но до записи в параметр, он был преобразован обратно в тип float. В Standard С, если компилятор встречает вызов функции, объявленной по прототипу extern int f (float); он может не менять тип аргумента на double. При этом, безусловно, он должен опус- тить обратное преобразование типов внутри функции, при реализации ее определения: int f(float х) { , . . } Здесь, однако, есть одна тонкость, состоящая в том, что в данном случае компилятор не обязан обеспечивать совместимость с вызовами, выполняемы- ми не в соответствии с прототипом, поскольку может оказаться, что ни одно традиционное объявление (или определение) несовместимо с указанным про- тотипом. Отсюда следует, что в Standard С не определено, что произойдет в ре- зультате вызова функции, выполненного из точки, в которой ее прототип не
Функции 311 виден. Компилятор может передать аргумент в регистре, несмотря на требова- ние передавать аргументы функций, объявленных в традиционной форме, че- рез стек. Однако же, если функции, объявленной или определенной традиционным образом, сопоставить прототип Миранды, компилятор обязан будет реализовать вызов этой функции соответственно совместимому соглашению о вызовах. Пример Вызов функции g, варианты объявления которой приведены ниже, должен выпол- няться по совместимому соглашению о вызовах. extern short g(); extern short g(int, double); /* Возможно, прототип Миранды для g */ Это же условие можно сформулировать следующим образом. Если компилятор Standard С встречает вне области видимости прототипа вызов функции process(a, b, с, d) ; с аргументами следующих типов: short а; struct {int а, Ь;} Ь; float *с; float d; тогда вызов функции должен соответствовать следующему прототипу: int process(int, struct (int a, b;), float *, double); Данное правило нельзя рассматривать как установку прототипа, который может оп- ределять способ выполнения последующих вызовов. Если дальше в программе (либо в другом исходном файле) появится еще один вызов функции process с тремя значе- ниями типа double в качестве аргументов, он будет реализован по следующему про- тотипу: int process(double, double, double); Как видим, два вызова одной и той же функции оказываются несовместимыми друг с другом. Таким образом, компилятор может реализовать каждый вызов функции, определяемый прототипом, в зависимости от его формы только в том случае, если он видит этот вызов и если в прототипе 1. указан тип аргумента, несовместимый с обычными преобразованиями аргументов (char, short, их беззнаковые варианты, float), или 2. указано многоточие, обозначающее список аргументов переменной дли- ны. Поскольку преобразование типов char и short в тип int требует на большин- стве компьютеров затраты минимума ресурсов, первое правило относится, главным образом, к типу float. Второе правило указывает, что в поддержке стандартным соглашением о вызовах переменного числа аргументов (как в традиционных компиляторах С) нет необходимости. Например, компилятор Standard С может выбрать пере- дачу первых четырех слов (фиксированных) аргументов любой функции в ре- гистрах, остальное — в стеке. Такое преобразование, скорее всего, не подойдет для традиционного С, где для некоторых функций требуется последовательное размещение всех аргументов в стеке. Любая функция с переменным числом
312 Глава 9 аргументов традиционного языка С (например, printf) должна быть снабжена прототипом, прежде чем может быть компилирована в Standard С. Класс памяти register игнорируется в прототипе, а это означает, что его нельзя использовать для изменения соглашения о вызовах, примененного в функции. Его можно использовать в теле функции только как подсказку. 9.2.5 Совместимость со стандартным и традиционным С Standard С в настоящее время достаточно распространен, поэтому прототи- пы рекомендуется использовать во всех программах. В тех редких случаях, когда требуется совместимость с компилятором, не поддерживающим прото- типы, можно отказаться от них, сохранив совместимость одновременно со Standard С и традиционным компилятором. При этом, однако, придется ли- шиться дополнительной проверки типов в Standard С. Проблему можно ре- шить при помощи макроса PARMS: #ifdef _STDC__ #define PARMS(х) x #else #define PARMS(x) () #endif Теперь вместо прототипа extern int f(int a, double b, char c); необходимо задать следующее объявление (обратите внимание на двойные скобки): extern int f PARMS((int a, double b, char c)}; Традиционный компилятор компилирует эту строку как extern int f (); Standard С — как extern int f(int a, double b, char c); Макрос PARMS не срабатывает правильно в определениях функций, поэтому определение указанной функции необходимо написать в традиционном син- таксисе, который также воспринимается компилятором Standard С: int f(а, b, с) int a; double b; char с; { } Традиционное определение в Standard С не породит никаких проблем, если выше, в исходном файле, указан прототип этой функции. Ссылки: макрос_STDC_3.3.4.
Функции 313 9.3 Объявление формальных параметров Формальные параметры в определениях функций могут объявляться в фор- ме прототипа либо в традиционном синтаксисе. Единственный спецификатор класса памяти, применимый к объявлению па- раметра — register; он служит напоминанием компилятору, что параметр пред- полагается использовать интенсивно, а потому, при выполнении функции, его следует оставить в регистре. На объявление параметрам класса памяти register распространяются обычные ограничения, изложенные в разделе 4.3. В Standard С область видимости формальных параметров совпадает с обла- стью видимости идентификаторов, объявленных на верхнем уровне тела функ- ции, поэтому их нельзя скрыть или переобъявить в теле. В некоторых совре- менных компиляторах С такое переобъявление разрешается, однако оно почти неизбежно приводит к ошибке. Пример Объявление double х; в следующем определении функции приведет к ошибке ком- пиляции в Standard С. Тем не менее, в некоторых других компиляторах такая фор- ма объявления проходит, делая параметр х недоступным в теле функции. int f (х) int х; ( double х; /* Скрывает параметр! */ } В Standard С можно объявить параметр любого типа, кроме void. Однако если объявить тип параметра наподобие «функция типа Т», он будет неявным образом переобъявлен в тип «указатель на функцию типа Т». Аналогично, тип параметра «массив типа Т» будет переобъявлен в «указатель на тип Т». Тип массива в объявлении параметра может быть неполным. Указанные измене- ния происходят вне зависимости от того, объявлена ли функция через прото- тип или традиционным образом, и дополняют преобразования параметров функции, выполняемые по умолчанию при ее вызове (раздел 6.3.5). В боль- шинстве случаев программисту незачем знать об этих преобразованиях типов, поскольку внутри функции параметры можно использовать так, как если бы они имели объявленные типы. Пример Предположим, функция FUNC определена следующим образом: void FUNC (int f (void) , int (*g) (void), int h[J, int *j) ( int i; i = f(); /* Правильно */ i = g(); /* Правильно */ i = h[3]; /* Правильно */ i = j[3]; /* Правильно */ ) Предположим также наличие следующего вызова функции FUNC:
314 Глава 9 extern int a(void), b[20]; FUNC(a, a, b, b) ; В этом случае выражение f внутри FUNC будет эквивалентно g, выражение h — эк- вивалентно j. В некоторых версиях С, предшествовавших Standard, нельзя было объяв- лять тип «функция типа Т» — только «указатель на функцию типа Т». В С99 несколько расширен синтаксис объявления формальных параметров массивов. В квадратных скобках верхнего уровня ([]) может быть указан спи- сок квалификаторов массива. Соответствие типов массивов ссылочным типам поддерживается квалификаторами массивов (типов) const, volatile и restrict. Это означает, что объявление параметров вида Т А[список-квалификаторов е] будет эквивалентно следующему: Т * список-квалификаторов А Пример Если задать следующие объявления С99: extern int f(int x[const 10]); extern int gfconst y[10]); то параметр x функции f будет обрабатываться как имеющий тип int * const (кон- станта-указатель на int), параметр у функции g — как const int * (указатель на кон- станту int). В квадратных скобках массива в С99 можно указать также квалификатор static. Это служит оптимизационной подсказкой для С, обеспечивая непустое значение параметра-массива с объявленным размером. Без этого квалификато- ра в качестве фактического аргумента может быть передан нулевой указатель на массив. Компилятору трудно определить, насколько безопасно будет, к примеру, заранее заполнить значениями входные параметры при вызове функции. Наконец, при объявлении в С99 формального параметра-массива в прототи- пе (не в определении функции) значение размера можно заменить звездочкой, указывающей, что фактический аргумент будет массивом переменной длины. Неконстантное выражение в прототипе, указывающее размер массива, тракту- ется так же, как звездочка. В определении функции размер должен указы- ваться неконстантным выражением. Формальный параметр рассматривается как локальная переменная указан- ного (или перезаписанного) типа, в которую копируется значение соответст- вующего аргумента, указанного при вызове функции. Параметру может быть присвоено значение, но это повлияет только на значение локального аргумен- та; аргумент, переданный вызываемой функции функцией вызывающей, оста- ется неизменным. Параметр, тип которого объявлен как функция или массив, является, ввиду правил перезаписи, 1-значением, несмотря на то, что иденти- фикаторы с этими типами к 1-значениям, как правило, не относятся. В традиционных компиляторах С разрешается указывать в разделах объяв- ления параметров typedef, структурные типы, объединения или перечисле-
Функции 315 ния. В Standard С, в разделах объявления параметров могут присутствовать только имена формальных параметров, но не типов — причем, должны быть указаны имена всех параметров. В версиях, предшествовавших С99, указание типа int в списке параметров было необязательным. Если параметры объявля- ются в форме прототипа, раздел объявления параметров должен быть пуст. Пример int process_record(r) struct { int a; int b; } r; /* He Standard C */ { } Наличие подобных конструкций в Standard С — признак плохого стиля программи- рования. Если объявление содержит параметры, оно должно быть выведено за пре- делы функции — тогда им сможет воспользоваться вызывающая функция. Если объявление не содержит параметров, его следует переместить в тело функции. Ссылки: список квалификаторов массива 4.5,3; перечислимые типы 5.5; описатепь функции 4.5.4; прототип функции 9.2; неполные типы 5.4; регистровый класс памяти 4.3; спецификато- ры класса памяти 4.3; структурные типы 5.6; typedef 5.10; типы-объединения 5.7; массив пе- ременной длины 5.4.5; тип void 5.9. 9.4 Настройка типов параметров Материал данного раздела применим к ситуациям, когда прототипы функ- ций не используются ни в традиционном, ни в Standard С. В отсутствие прото- типа аргументы функций приходится подвергать определенным преобразова- ниям. Эти преобразования, предназначенные для упрощения аргументов и приведения их в надлежащее состояние, принято называть обычными преоб- разованиями аргументов; перечень их приведен в разделе 6.3.5. После вызова функции перед ее выполнением аргументы вновь преобразуются — на этот раз обратно к объявленным типам. Например, если объявлена функция F с пара- метром х типа short, при ее вызове происходит следующее: 1. Вызывающая функция увеличивает размер аргумента х, преобразуя его из типа short в тип int. 2. Полученное значение int передается функции F. 3. F сужает аргумент х, преобразуя его из типа int в тип short. 4. F записывает значение типа short в параметр х. К счастью, эти преобразования не приводят к существенному избыточному расходованию ресурсов — по крайней мере, для целых типов. В число преобра- зуемых входят типы char, short, unsigned char, unsigned short и float. Пример Следует иметь в виду, что некоторые компиляторы, предшествовавшие Standard С, не выполняют обратного преобразования аргументов внутри функции. Рассмотрим следующую функцию с параметром типа char: int pass through(с) char с;
316 Глава 9 ( return с; } Некоторые компиляторы реализуют эту функцию так, как если бы ее параметр имел тип int: int pass_through(с) int с ; { return с; } В результате, обратного преобразования аргумента в тип char не происходит, а пото- му, к примеру, выражение pass_through(0xl001) возвращает не 1, а 0x1001. Пра- вильная реализация этой функции должна иметь примерно следующий вид: int pass_through(anonymous) int anonymous; { char c = anonymous; return c; ) Ссылки: регулярные типы (моссивы) 5.4; действительные типы с плавающей точкой 5.2; преоб- разование аргументов функций 6.3.5; определение функции 5.8; прототипы функций 9.2; ти- пы-функции 5.8; целые типы 5.1; 1-значение 7.1; ссылочные типы (указатели) 5.3. 9.5 Соглашения о преобразовании параметров В С поддерживается передача параметров только по значению. Это означа- ет, что значение фактического параметра копируется в область памяти, при- надлежащую вызываемой функции. Если при выполнении функции произой- дет изменение этого параметра, это никак не отразится на фактическом пара- метре, указанном вызывающий функцией. Чтобы произошло изменение параметра в вызывающей функции, вызываемой функции необходимо пере- дать не значение параметра, а его адрес. Пример Из-за того, что параметры х и у передаются по значению, функция swap работает неправильно. void swap(x, у) /* обмен значениями между х и у */ /* Вариант с ошибкой’ */ int х, у, ( int temp; temp = х; х = у; у = temp; } swap(a, b); /* Обмен значениями между а и b не выполняется. */ Функция срабатывает, если ей передаются не значения, а адреса аргументов: void swap(x, у) /* обмен значениями между *х и *у */
Функции 317 /* Правильный вариант */ int *х, *у; { int temp; temp » *х; *х = *у; *у = temp; swap(&a, &b); /* Выполняется обмен значениями между а н Ь. */ Локальные параметры функции располагаются, как правило, в стеке. Од- нако в языке не определен порядок помещения параметров в стек, как нет и запрета передавать их через регистры. Если формальный параметр не объяв- лен с классом памяти register, его можно указать с адресным оператором &, что гарантирует размещение этого параметра в адресуемой области памяти. Следует иметь в виду, что адрес формального параметра — это адрес копии фактического параметра, но не самого фактического параметра. При написании функций с переменным числом аргументов для обеспече- ния максимальной мобильности программы следует использовать средства за- головочных файлов varargs и stdarg. Ссылки: адресная операция & 7,5.6; прототип функции 9.2; класс памяти register 4.3; заго- ловочный файл stdarg 11.4; заголовочный файл varargs 1 1.4.1. 9.6 Соответствие параметров В большинстве современных языков программирования (таких, к примеру, как Pascal или Ada) проверяется соответствие числа и типов формальных и фактических параметров функций. Эта же проверка выполняется в Standard С, если функция объявлена прототипом. Пример В следующем примере, вызов функции sqrt не контролируется прототипом, поэтому компилятор С не обязан предупреждать программиста о том, что фактический пара- метр функции имеет тип long, тогда как формальный объявлен как double. По су- ществу, компилятор и не сумеет этого сделать, если определение и вызов функции будут располагаться в разных исходных файлах. Функция sqrt возвратит неверное значение: double sqrt( х ) /* не прототип */ double х; { } long hypotenuse(х,у) long х,у; { return sqrt(x*x + у*у); } Если вызов в Standard С контролируется прототипом, типы фактических аргументов преобразуются к типам формальных параметров. Компиляция прерывается только в случае, если преобразование невозможно или число ар- гументов не совпадает с числом формальных параметров.
318 Глава 9 Пример Снабдив функцию sqrt из предыдущего примера прототипом, мы обеспечим возврат правильного результата. Аргумент будет преобразован из типа long в тип double без вмешательства программиста: double sqrt{ double х ) /* * прототип */ { } long hypotenuse(х,у) long х,у; ( return sqrt(х*х + у*у); } Хороший стиль программирования предполагает явное преобразование типов аргу- ментов к типам формальных параметров, если только это преобразование не будет дублировать обычное преобразование типов аргументов. В этом случае оператор воз- врата значения в предыдущем примере будет иметь вид: return sqrt( (double) (х*х + у*у) ); Некоторые функции С (например, fprintf) могут вызываться с переменным числом аргументов разных типов. В традиционном С очень надежное написание таких функций обеспечивается библиотекой varargs. К сожалению, ввиду не- больших различий в реализации этой библиотеки, такие функции плохо пере- носятся на другие платформы. В Standard С аналогичный механизм (stdarg) обеспечивает не только надежность, но и мобильность. Функции, использую- щие эту библиотеку, должны объявляться в форме прототипов с многоточием (,...) в списке параметров до любого из вызовов, что позволяет компилятору со- ставить правильную схему вызова. Ссылки: преобразование фактических параметров 9.4; преобразование аргументов функ- ции 6.3.5; прототипы функций 9.2; fprintf I5.l I. 9.7 Типы возвращаемых значений Функции можно задать любой тип возвращаемого значения, кроме «масси- ва типа Т» или «функции, возвращающей значение типа Т». В этих случаях следует задавать возвращение указателя на значения соответствующих типов, имея в виду, что автоматического преобразования типов, как в случае фор- мальных параметров, здесь не происходит. Значение, возвращаемое функцией, указывается выражением оператора return, завершающего ее выполнение. Правила составления этого выражения обсуждаются в разделе 9.8. Возвращаемое функцией значение не относится к 1-значениям (возврат про- исходит «по значению»), поэтому функцию нельзя указывать в качестве левой части оператора присваивания. Пример f() = х; /* Ошибка */ *f() = х, /* Допускается, если f возвращает указатель на переменную соответствующего типа */ f().п = х; /* Ошибка - это не 1-значение (раздел 7.4.2) */
Функции 319 Ссылки: регулярные типы (массивы) 5.4; вызовы функций 7.4.3; параметры функций 9.4; типы функций 5.8; 1-значение 7.1; ссылочные типы 5.3; тип void 5.9. 9.8 Соглашение о возвращаемых значениях Если функции задан тип Т возвращаемого значения (не void), тип выраже- ния, указанного в операторе return, должен быть преобразуем в Т в операции присваивания, и это преобразование, по существу, происходит как в традици- онном, так и в Standard С. Пример Если функция объявлена с типом возвращаемого значения int, следующий оператор: return 23.1; будет эквивалентен оператору return (int) 23.1; что, опять же, равнозначно return 23; Если тип возвращаемого функцией значения указан как void, указание опе- ратора return с выражением приведет к ошибке. Ошибкой будет также указа- ние этой функции в контексте, требующем возврата значения. В ранних ком- пиляторах, в которых тип void не определен, функции, не возвращающие зна- чений, объявляются без указания типа: process_something() /* Очевидно, не возвращает значения */ { } Для большей наглядности исходных текстов, можно даже определить собст- венный тип void (раздел 4.4.1). В С89 разрешено указание операторов return без выражений даже в тех случаях, когда функция имеет тип, отличный от void; в С99 это запрещено, как и в C++. Назначение этого правила — обеспечить совместимость с компи- ляторами, в которых не реализован тип void. Если функция имеет тип, отлич- ный от void, и выполняется оператор return без аргументов, возвращаемое значение оказывается неопределенным. Следовательно, вызов такой функции для получения возвращаемого ею значения не имеет смысла. Ссылки: подгонка к формальным параметром 9 4, спецификаторы типа по умолчанию 4.4.1; 1-значение 7.1; оператор return 8.9, тип void 5.9. 9.9 Функция main Во всех программах С определяется единая внешняя функция с именем main, служащая точкой входа в программу — то есть первой функцией, вы- полняемой после запуска программы. Возврат из этой функции приводит к за- вершению выполнения программы, а возвращаемое значение служит призна- ком успешного либо аварийного завершения, как при вызове библиотечной
320 Глава 9 функции exit. Выход из функции main без оператора return (в результате вы- полнения последнего оператора), происходит так же, как в результате выпол- нения оператора return 0;. В Standard С функция main определяется без параметров либо с двумя па- раметрами. int main(void) {...} int main() (...) /* * допустимо, но не рекомендуется */ int main( int argc, char *argv[] )) (...) Если функция main объявлена без параметров, программа не получает инфор- мации о среде, в которой запускается. Впрочем, эту информацию можно полу- чить позже, при помощи библиотечных функций getenv и system. В версиях компилятора, предшествовавших С99, указание типа функции main можно было опускать; по умолчанию принимался тип int. Теперь это не разрешено. Значения аргументов, объявленных для функции main, устанавливаются средой, в которой запускается программа. У программиста нет возможности непосредственно влиять на это. Параметр argc — это число «аргументов» или «параметров», переданных программе при ее запуске пользователем или дру- гой программой. Параметр argv — массив строковых указателей на аргументы программы. Первая строка (argv[0]) — это имя программы. Если имя не ука- зано, значение argv[0][0] должно быть равно ’\0'. Строка argv[i], i = 1, ..., argc-1, представляет г-й аргумент программы. Элемент argvfargc] в Standard С должен быть нулевым указателем. В некоторых более ранних компиляторах это условие не удовлетворено. Массив argv, как и строки, на которые указыва- ют его элементы, должны быть модифицируемыми, но не изменяться в тече- ние выполнения программы. Если строки в смешанном регистре не поддержи- ваются, строки аргументов должны быть указаны в нижнем регистре. В неко- торых автономных средах С, а также системах программного обеспечения (например, Microsoft Windows MFC) могут действовать особые соглашения от- носительно способа запуска программы С. Пример Следующая небольшая программа выводит собственное имя и аргументы, ((include <stdio.h> int main(int argc, char *argv[j) { int i ; printf("MH«: %s\n", argv[0]); printf("Аргументы: "); for(i=l; i<argc; i++) printf("%s ", argv[i]); printf("\n"); return 0; ) В некоторых реализациях допускается указание третьего аргумента — char * envp[] — указателя на массив «значений среды», оканчивающийся нулевым символом. Каждый элемент этого массива является указателем на строку вида "имя=значение", оканчивающуюся нулевым символом. Если этот указатель среды не указан как параметр main, его можно найти в некоторой глобальной переменной. В некоторых реализациях для UNIX это глобальная переменная
Функции 321 environ. Удобнее, однако, находить эту переменную при помощи стандартной функции Standard С getenv. Пример Полагая, что envp содержит указатель на переменные среды, следующий оператор выводит их: for(i=0; envp[i] != NULL; i++) printf("%s\n", envp[i]); Ссылки: exit 16.5; getenv 16.6; system 16.7. 9.10 Встраиваемые функции Встраиваемые функции появились впервые в С99. Встраиваемая функция обозначается спецификатором inline в объявлении или определении. Ключе- вое слово inline служит лишь напоминанием компилятору о том, что обраще- ние к этой функции должно происходить как можно быстрее. Само ключевое слово inline происходит от оптимизационного метода inline expansion (внутри- строковое кодирование), когда вызов функции заменяется копией ее тела. Та- ким образом, устраняются непроизводительные операции, связанные с вызо- вом функции. Многие компиляторы С, предшествовавшие С99, содержали расширения для поддержки встраиваемых функций; в C++ они также поддер- живаются. Для внутристрокового кодирования установлены три основных принципа. 1. Видимое определение. Чтобы заменить вызов функции ее внутристро- ковым представлением, транслятор должен знать определение этой функции во время компиляции ее вызова. В С99, если функция объяв- лена как inline, объявление ее должно быть видимо в данном исходном файле. 2. Свобода выбора. Программист не указывает компилятору, необходимо ли выполнять внутристроковое кодирование. При наличии, к примеру, четырех обращений к встраиваемой функции, компилятор может два из них реализовать внутристроковым кодированием, еще два — обыч- ными вызовами; все это — без каких бы то ни было указаний в исход- ном тексте программы. 3. Неизменность смысла. Реализуя некоторые обращения к функциям в форме внутристрокового кодирования, компилятор должен позабо- титься о том, чтобы они ничем не отличались от обычных вызовов. Внутристроковое кодирование — не более чем метод оптимизации, ни- как не затрагивающий смысла программы. Любая статическая функция может быть помечена ключевым словом in- line, поскольку все вызовы ее происходят в том же исходном файле, в котором располагается определение. В отличие то этого, вызов внешней функции происходит, как правило, из файла, в котором ее определение отсутствует. Поскольку для реализации внут- ристрокового кодирования компилятору необходимо видеть как объявление, так и определение встраиваемой функции, сделать это вне файла, содержаще-
322 Глава 9 го определение, непросто. Следовательно, требуется средство, позволяющее «заглянуть» в нужный исходный файл, чтобы увидеть определение функции. Этот метод «заглядывания» называется встраиваемым определением (inli- ne definition). Если все объявления некоторой функции, на верхнем уровне не- которого исходного файла, содержат ключевое слово inline, но не содержат ключевого слова extern, определение этой функции (в этом исходном файле) и есть встраиваемое определение. (Естественно, указанное определение долж- но существовать и быть помеченным, опять же, как inline, но не extern.) Встраиваемое определение не создает внешнего определение функции — необ- ходимо создать еще одно внешнее определение в другом исходном файле. Внутреннее определение — это альтернатива программированию внешнего вы- зова, и компилятор может воспользоваться ею для выполнения внутристроко- вого кодирования. Если компилятор не прибегает к внутристроковому кодиро- ванию, он генерирует обычный вызов функции, трактуя встраиваемое опреде- ление как обычное определение extern. Если встраиваемые определения и единственное внешнее определение функции не соответствуют друг другу, поведение программы оказывается непредсказуемым. Один из возможных ва- риантов использования встраиваемых определений — замена объявления функции в заголовочном файле встраиваемым определением. Пример Функция square возвращает квадрат аргумента. Заголовочный файл square.h содер- жит встраиваемое определение для любого исходного файла, в который будет вклю- чен. Одновременно, встраиваемое определение служит объявлением внешней функ- ции на случай, если компилятор решит не реализовать вызов в виде внутристроко- вого кодирования или же ему потребуется адрес этой функции. Встраиваемое определение размещено в файле square.с, содержащем также объявление extern. Таким образом, определение, размещенное в файле square.h, становится внешним определением функции. // Файл: square.h // Встраиваемое определение: inline double square(double x) { return x*x } // Файл: square.c ((include "square.h1' // Форсирование внешнего определения при помощи внутристрокового кода extern inline square(double x); Определения inline стандартных функций, как правило, не могут использо- ваться в библиотечных заголовках, поскольку программы могут иногда пере- объявлять эти функции (макросы). Однако в любой реализации может исполь- зоваться собственный, не переносимый на другие платформы, механизм реали- зации внутристрокового кода или каких-либо иных стандартных библиотечных средств. Возможно возникновение проблем в ситуации, когда внешняя встраивае- мая функция содержит определение статического объекта. Связать статиче- ский объект, располагающийся во встраиваемом определении, со статическим объектом, располагающемся во внешнем определении в другом исходном фай- ле, непросто, поэтому в С99 любые (нестатические) встраиваемые функции не могут содержать определений модифицируемых статических объектов и ссы- лок на идентификаторы с внутренним связыванием. Разрешено определение
Функции 323 константных статических объектов, но любое встраиваемое определение мо- жет создать собственный объект. Ссылки: спецификатор функции inline 4.3.3. 9.11 Совместимость с C++ 9.11.1 Прототипы Для обеспечения совместимости с C++ объявления всех функций должны быть в форме прототипов. По существу, традиционное объявление имеет в C++ совершенно иной смысл. Пустой список параметров в C++ означает, что функ- ция запускается без параметров, в С — с неопределенным числом параметров. Пример int f(); /* Означает int f(void) в C++, int f(...) - в С */ int g (void); /* Одинаковый смысл в С и C++ */ х = f (2) ; /* Правильно в С, но не в C++ */ 9.11.2 Объявление типов в списках параметров и возвращаемых значений Объявления типов нельзя размещать в списках параметров или объявлени- ях типов возвращаемых значений — это не разрешено в C++. Пример struct s { ... } fl(int i); /* Правильно в С, но не в C++ */ void £2(enum е{...} х); /* Правильно в С, но не в C++ */ 9.11.3 Соглашение о типах возвращаемых значений В C++ и С99 функции всех типов, кроме void, должны возвращать значение соответствующего типа. В С89 функции, не возвращающие значений, разре- шены для обеспечения обратной совместимости. Пример int f(void) { return; /‘ Правильно, но непредсказуемо в С, неправильно в C++ */ ) Ссылки: соглашение о типах возвращаемых значений 9.8.
324 Глава 9 9.11.4 main В C++ функцию main нельзя вызывать рекурсивно, как нельзя и получить ее адрес. В C++ налагается больше ограничений на запуск программы, поэто- му обработка функции main происходит особым образом. Если манипуляция функцией main все же необходима, можно создать другую функцию, вызывае- мую из main и используемую вместо нее. 9.11.5 inline В С99 правила встраиваемых определений функций несколько менее жест- ки по сравнению с C++, где требуется точное соответствие всех встраиваемых определений внешнему определению. В С99 допускается размещение в отдель- ных исходных файлах специализированных встраиваемых определений, а программисту разрешается самому устанавливать степень соответствия. В C++, в отличие от С99, встраиваемая функция должна быть объявлена тако- вой во всех исходных файлах. Для обеспечения мобильности программа долж- на быть написана в соответствии с более жесткими правилами C++. 9.12 Упражнения 1. 2. 3. Какие из следующих объявлений являются прототипами в Standard С? (а) short f(void); (Ь) int f(); (с) double f(...); (d) int f(i,j); (е) int *f(float); (f) int f(i) int i; {...} Ниже приведены объявления и определения функций. Какие пары со- вместимы в Standard С? Объявление Определение (a) extern int f(short x); int f(x) short x; {...} (b) extern int f(); int f(short x) {...} (c) extern f(short x); int f(short int y) {...} (d) extern void f(int x); void f(int x, ...) {...} (e) extern f(); int f(x, y) short x, y; {...} (f) extern f(); f(void) {...} объявления и вызова функций. Укажите, Ниже приведены примеры в каждом случае, соответствует ли вызов требованиям Standard С; если да — укажите, какому преобразованию будет подвергнут каждый из фактических параметров. Предполагается, что s имеет тип short, Id — long double. Объявление Определение (a) extern int f(int *x); f(&s) (b) extern int f(); f(s, Id) (c) extern f(short x); f(ld) (d) extern void f(short,...); f(s, s, Id)
Функции 325 (е) int f(x) short x; {...} f(s) (f) int f(x) short x; {...}; f(ld) 4. Выполняется ли вызов функции Р в следующем фрагменте программы в соответствии с прототипом? Почему? extern void Р(void); int Q() { extern P () ; P() ; } 5. Если тип возвращаемого функцией значения объявлен как short, выра- жение какого из следующих типов в ее операторе return приведет к воз- врату осмысленного значения? (a) int (b) long int (с) void (например, вызов функции типа void) (d) char * 6. Объясните, чем данное макроопределение функции square отличается от встраиваемого варианта из раздела 9.10: #define square(х) ((х)*(х))

ЧАСТЬ II Библиотеки С

Глава 10 Введение в библиотеки Standard С — это стандарт языка плюс набор стандартных библиотек. Стан- дартные библиотеки обеспечивают поддержку текстовых символов и строк, ввода и вывода, математических функций, преобразований даты и времени, динамического распределения памяти и других возможностей. Необходимые средства (типы, макросы, функции) определяются в каждой из библиотек стандартным заголовочным файлом. Чтобы задействовать средства той или иной библиотеки, необходимо вставить в исходный текст программы команду препроцессора #include с именем соответствующего заголовочного файла. Пример В следующем фрагменте исходного текста, заголовочный файл math.h обеспечивает программе доступ к функции вычисления косинуса угла (cos). #include <math.h> double x, у; x = cos(у); Некоторые традиционные реализации С не обладают полными комплекта- ми заголовочных файлов, поэтому программистам иногда приходится писать объявления библиотечных функций. Standard С допускает реализации, в которых библиотечные средства, опре- деленные как функции, могут быть представлены также в форме макросов с теми же именами. Макросы позволяют реализовать более быстрые вызовы простых функций либо могут использоваться для вызова функций с другими именами. Как и функция, макрос вычисляет каждое из выражений, указан- ных в качестве аргументов, ровно один раз. Если, вне зависимости от наличия макроса, требуется обращение именно к функции, макрос обходится, как по- казано в следующем примере. Пример Предположим, у нас есть опасение, что существует макрос, дублирующий функцию cos в библиотеке math. Ниже показаны два способа обращения к этой функции, ми- нуя макрос. Суть первого из них — в отсутствии левой скобки непосредственно по- сле имени предполагаемого макроса, что предотвращает встраивание макрорасши- рения в точке вызова.
330 Глава 10 «include <math.h> double a, b, (*p)(double); p= Seos; a = (*p)(b); /* вызов только функции cos */ a = (cos) (b); /* вызов только функции cos */ Второй способ: дублирующий макрос можно просто убрать: «include <math.h> ttundef cos a = cos(b); /* вызов только функции cos */ Ссылки: #indude 3.4; макросы с параметрами 3.3.2; #undef 3.3.5. 10.1 Стандартные средства С В разделе 10.3 дана сводка стандартных библиотечных средств в форме пе- речисления имен, содержащихся в заголовочных файлах, с указанием глав и разделов, где дается их подробное описание. Описания всех средств соответствуют спецификации Standard С. В боль- шинстве случаев (кроме особо оговоренных) для традиционного С можно ис- пользовать объявления библиотечных функций Standard С, для чего в их опре- деления необходимо внести изменения, перечисленные ниже: 1. Удалить все функции, в которых используются типы Standard С напо- добие long long или -Complex, а также те, которые впервые определе- ны именно в Standard С (С89 или С99). 2. Удалить описатели const, restrict и volatile. Удалить ключевые слова static, располагающиеся в квадратных скобках описателей массивов. 3. Поменять тип void * на char *, тип size_t на int. На библиотечные средства и заголовочные файлы в Standard С наложен ряд условий, назначение которых — обеспечение целостности реализации. 1. Библиотечные имена зарезервированы. Нельзя определить внешний объект, имя которого совпадает с именем, объявленным в стандартной библиотеке. 2. Заголовочные файлы некоторых библиотек могут быть «встроены» в компилятор; тем не менее, их также необходимо указывать в команде препроцессора #include, чтобы сделать видимыми. Это означает, что, к примеру, выражение stdio.h может не иметь никакого отношения к соответствующему заголовочному файлу. 3. Заголовочные файлы можно включать в исходный текст в любых мес- тах и в любом количестве, что, однако, не всегда разрешено в традици- онных компиляторах. Пример Ниже приведен пример распространенного способа устранения дублированного включения в исходный текст заголовочных файлов. /* Заголовочный файл stddef.h */ #ifndef _STDDEF /* Нельзя переобъявлять */ «define STDDEF 1
Введение в библиотеки 331 typedef int ptrdiff_t; ... /* другие определения */ #endif 10.1.1 Зарезервированные библиотечные идентификаторы Кроме ключевых слов, перечисленных в разделе 2.6, Standard С резерви- рует для внутреннего использования идентификаторы, объявленные в стан- дартной библиотеке, а также ряд других. Чтобы не дублировать эти иденти- фикаторы, следует руководствоваться простым правилом: не объявлять иден- тификаторы, которые уже где-либо объявлены, а также идентификаторы, начинающихся с символа подчеркивания. Это поможет избежать конфликта имен при переходе к другим реализациям Standard С. Ниже перечислены бо- лее подробные правила. Вид идентификатора Правило Библиотечные идентификаторы с внешней связью — имена функций, еггпо (номер ошибки) и т.п. Нельзя объявлять с любой другой внешней связью. Библиотечные идентификаторы с областью видимости на уровне файла, а также библиотечные макросы. Нельзя объявлять с областью видимости но уровне файпо, если подключен заголовочный фойл соответствующей библиотеки. Идентификаторы, начинающиеся с символа подчеркивания и заглавной буквы либо еще одного символа подчеркивания. Нельзя объявлять в любой ситуации. Такие идентификаторы часто используются в расширениях розничных компиляторов С. Другие идентификаторы, начинающиеся с символа подчеркивания. Нельзя объявлять с областью видимости на уровне фойло. Компилятор не допускает замены стандартных библиотечных функций. Попытка написать, к примеру, собственную функцию sqrt приведет к ошибке компоновки ввиду конфликта имен. Это ограничение обеспечивает компиля- тору С больше свободы в пакетировании и использовании стандартных биб- лиотечных функций. 10.2 Совместимость с C++ Язык C++ включает библиотеку Standard С и, кроме этого, содержит ряд собственных библиотек. Имена этих библиотек не оканчиваются на .h, что уст- раняет возможность конфликтов с библиотеками С. В C++ реализовано другое соглашение о вызовах функций, так что функ- цию C++ нельзя вызвать из С. В то же время, существует способ вызова функ- ций С из C++. При этом объявление функций С должно подчиняться двум ус- ловиям. 1. Объявления должны быть выполнены в форме прототипов Standard С. C++ не допускает традиционных объявлений. 2. Объявления внешних функций С должны быть явно помечены как от- носящиеся именно к С, для чего вслед за классом памяти extern указы- вается строка "С" в C++ объявлении.
332 Глава 10 Пример Если функция С вызывается из другой функции С, ее можно объявить следующим образом: extern int f(void); Объявление для вызова этой же функции из функции C++ будет иметь вид: extern "С" int f(void); Спецификатор extern "С” можно указать сразу для нескольких функций: extern "С" { double sqrt(double х); int f(void); ) При написании заголовочного файла для библиотеки, которая может ис- пользоваться в С или C++, принадлежность вызываемых функций к С можно указывать в самом файле либо в программе C++, использующей этот файл. Пример Предположим, нам необходимо обращаться к библиотеке library.h из программ С или C++. Первый вариант организации доступа к этой библиотеке — размещение в заголовочном файле объявлений extern ’’С” в операторе компиляции по условию наличия макроса___cplusplus, что означает компиляцию в C++: /* Файл library.h */ #ifdef __cplusplus extern "C" ( #endif /* Объявления C */ #ifdef __cplusplus ) #endif Второй вариант — написание заголовочного файла с обычными объявлениями С и за- ключение команды #include в исходном файле C++ в спецификатор extern "С”: extern "С" ( #include "library.h" ) Второй вариант удобен в ситуациях, когда приходится использовать биб- лиотеки, написанные до принятия решения о переходе в C++. Допускается вложение объявлений extern "С" {} друг в друга. Ссылки: макрос__cplusplus 3.9.1.
Введение в библиотеки 333 10.3 Библиотечные идентификаторы и заголовочные файлы 10.3.1 assert.h См. главу 19. assert NDEBUG 10.3.2 complex.h См. главу 23. Файл введен в С99. cabs catan clog csinf cabsf catanf clogf csinh cabsl catanh clogl csinhf cacos catanhf complex csinhl cacosf catanhl _Complex_I csinl cacosh catanl conj csqrt cacoshf ccos conjf csqrtf cacoshl ccosf conjl csqrtl cacosl ccosh epow ctan carg ccoshf epowf ctanf cargf ccoshl cpowl ctanh cargl ccosl cpro j ctanhf easin cexp cprojf ctanhl casinf cexpf cprojl ctanl casinh cexpl creal CX_LIMITED_RANGE casinhf cimag crealf I casinhl cimagf creall imaginary casinl cimagi csin _Imaginary_I 10.3.3 ctype.h См. главу 12. isalnum isgraph isupper isalpha islower isxdigit isblank isprint tolower iscntrl ispunct toupper isdigit isspace
334 Глава 10 10.3.4 error.h См. главу 11. EDOM ERANGE EILSEQ errno 10.3.5 fenv.h См. главу 22. Файл введен в С99. FE_ALL_EXCEPT FE_TONEAREST fegetround fesetround FE_DFL_ENV FE_TOWARDZERO feholdexcept fetestexcept FEJDIVBYZERO FE_UNDERFLOW FENV_ACCESS feupdateenv FE_DOWNWARD FE_UPWARD fenv_t fexcept_t FE_INEXACT feclearexcept feraiseexcept FE_INVALID fegetenv feaetenv FE_OVERFLOW fegetexceptflag fesetexceptflag 10.3.0 float.h См. таблицу 5.3. DBL_ DIG DBL_MIN_EXP FLT_MAX_EXP LDBL_ _MANT_DIG DBL_ EPSILON DECIMAL_DIG FLT_MIN LDBL_ MAX DBL _MANT_DIG FLT_DIG FLT_MIN_10_EXP LDBL_ _MAX_ 10_EXP DBL MAX FLT_EPSILON FLT_MIN_EXP LDBL_ _MAX_ EXP DBL_ _MAX_10_EXP FLT_EVAL_METHOD FLT_RADIX LDBL_ MIN DBL _MAX_EXP FLT_MANT_DIG FLT_ROUNDS LDBL_ _MIN_ 10_EXP DBL MIN FLTJMAX LDBL_DIG LDBL_ _MIN_ EXP DBL _MIN_10_EXP FLT_MAX_10_EXP LDBL_EPSILON 10.3.7 inttypes.h См. главу 21. Файл введен в С99. CNiLEASTN PRIoMAX PRIiFASTW PRIXFASTW imaxabs PRIoW PRIiLEASTW PRIxLEASTW imaxdiv PRIoPTR PRIiMAX PRIXLEASTW imaxdiv_t PRIuFASTW PRIiW PRIxMAX PRIdFASTW PRIuLEASTW PRIiPTR PRIXMAX PRIdLEASTW PRIuMAX PRIoFASTW PRIxN PRIdMAX PRIuN PRIoLEASTW PRIXW PRIdW PRIuPTR PRIxPTR SCNuFASTN PRIdPTR PRIxFASTW PRIXPTR SCNuLEASTW
Введение в библиотеки 335 SCNdFASTW SCNuMAX SCNiW SCNxPTR SCNdLEAST.V SCNuW SCNiPTR strtoimax SCNdMAX SCNuPTR SCNoFASTW strtoumax SCNdW SCNxFASTW SCNoLEASTW wcstoimax SCNdPTR SCNxLEASTW SCNoMAX wcstoumax SCNiFASTW SCNxMAX SCNoW SCNiMAX SCNxW SCNoPTR 10.3.8 iso646.h См. раздел 11.5. Файл введен в Дополнении 1 к С89. and bitor not_eq xor and_eq compl or xor_eq bitand not or_eq 10.3.9 limits.h См. таблицу 5.2. CHAR_BIT LLONG_MAX SCHAR_MAX UINT_MAX CHAR_MAX LLONG_MIN SCHAR_MIN ULLONG_MAX CHAR_MIN LONG_MAX SHRT_MAX ULONG_MAX INTJMAX LONG_MIN SHRT_MIN USHRTJMAX INT_MIN MB_LEN_MAX UCHAR_MAX 10.3.10 locale.h См. главу 20. LC_ALL LC_MONETARY Iconv setlocale LC_COLLATE LC_NUMERIC localeconv LC_CTYPE LC_TIME NULL 10.3.11 math.h См. главу 17. acos asinf atan2f cbrt acosf asinh atan21 cbrtf acosh asinhf atanf cbrtl acoshf asinhl atanh ceil acoshl asinl atanhf ceilf acosl a tan atanhl ceill asin atan2 atanl copys.
336 Глава 10 copysignf fmin Isunorderedldexp nanf copysignl fminf Idexpf nanl cos fminl Idexpl nearbyint cosf fmod Igamma nearbyintf cosh fmodf Igammaf nearbyintl coshf fmodl lgammal nextafter coshl FP_CONTRACT llrint nextafterl cosl FP_FAST_FMA llrintf nexttoward double_t FP_FAST_FMAF llrintl nexttowardf erf FP_FAST_FMAL llround nexttowardl erf c FP_ILOGBO llroundf pow erfcf FP_ILOGBNAN llroundllog powf erf cl FP_INFINITE loglO powl erff FP_NAN loglOf remainder erfl FP_NORMAL loglOl remainderf exp FP_SUBNORMAL loglp remainderl exp2 FP_ZERO loglpf remquo exp2f fpclassify loglpl remquof exp21 frexp log2 remquol expf frexpf log2f rint expl frexpl log21 rintf expml HUGE_VAL logb rintl expmlf HUGE_VALF logbf round expmil HUGE_VALL logbl roundf fabs hypot logf roundl fabsf hypotf logl scalbln fabsl hypotl Irint scalblnf fdim ilogb Irintf scalblnl fdimf ilogbf Irintl scalbn fdiml ilogbl Iround scalbnf float_t INFINITY Iroundf scalbnl floor isfinite Iroundl signbit floorf isgreater MATH_ERREXCEPT sin floorl isgreaterequal math_errhandling sinf fma isinf MATH_ERRNO sinh fmaf isless modf sinhf fmal islessequal modf f sinhl fmax islessgreater modf 1 sinl fmaxf isnan NAN sqrt fmaxl isnormal nan sqrtf
Введение в библиотеки 337 sqrtl tanhf tgamma f tan tanhl tgamma 1 tanf tanl trunc tanh tgamma truncf 10.3.12 setjmp.h См. раздел 19.4 jmp_buf longjmp setjmp 10.3.13 signal.h См. раздел 19.6 raise SIG_ERR SIGFPE sig_atomic_t SIG_IGN SIGILL SIG_DFL SIGABRT SIGINT 10.3.14 stdarg.h См. раздел 11.4 va_arg va_end va_copy va_list va_start 10.3.15 stdbool.h См. раздел 11.3 bool false bool_true_false_are_defined true 10.3.16 stddef.h См. раздел 11.1 NULL ptrdiff_t wchar_t offsetof size_t truncl signal SIGSEGV SIGTERM 10.3.17 stdint. h См. главу 21. Файл введен в С99. INT_ _FASTW_MAX INT_LEASTW_MAX INTMAX_ _C INT _FASTN_MIN INT_LEAST W_MIN INTMAX_ MAX int fastw_t int_leastW_t INTMAX_ _MIN intmax_t INTN_C INT.M_MAX
338 Глава 10 INTWJMIN SIG_ATOMIC_MAX UINTMAX_C uintptr_t intW_t SIG_ATOMIC_MIN OINTMAX_MAX WCHAR_MAX INTPTR_MAX SIZE—MAX uintmax_t WCHAR_MIN INTPTR_MIN UINT_FASTN_MAX UINTW_C WINT_MAX intptr_t uint_fastN_t UINTW_MAX WINT_MIN PTRDIFF_MAX UINT_LEASTW_MAX uintW_t PTRDIFF_MIN uint_leastw_t OINTPTR_MAX 10.3.18 stdio.h См. главу 15. BUFSIZ fputs printf stderr clearerr fread putc stdin EOF freopen putchar stdout fclose fscanf puts TEMP_MAX feof fseek remove tmpfile terror fsetpos rename tmpnam fflush ftell rewind ungetc fgetc fwrite scanf vfprintf fgetpos getc SEEK_CUR vfscanf fgets getchar SEEK_END vprintf FILE gets SEEK_SET vscanf FILENAME_MAX _IOFBF setbuf vsnprintf fopen _I0LBF se tvbuf vsprintf FOPEN_MAX _I0NBF size_t vsscanf fpos_t L_tmpnam snprintf frintf NOLL sprintf fputc perror sscanf 10.3.19 stdlib.h См. главу 16. abort div Idiv mbtowe abs div_t ldiv_t NULL atexit exit llabs qsort atof _Exit Ildiv rand atoi EXIT_FAILURE lldiy_t RAND_MAX atol EXIT_SOCCESS malloc realloc atoll free MB_CUR_MAX size_t bsearch getenv mblen srand calloc labs mbstowcs strtod
Введение в библиотеки 339 strtof strtol strtold strtoll strtoul strtoull system wchar_t wcstombs wctomb 10.3.20 string.h См. главу 13. memchr size_t strcspn strpbrk memcmp Strcat strerror strrchr memcpy strchr strlen strspn memmove strcmp strncat strstr memset strcoll strncmp strtok NULL strcpy strncpy strxfrm 10.3.21 tgmath.h См. раздел 17.12 . Файл введен в C99. acos cproj hypot nexttoward acosh creal ilogb pow asin erf Idexp remainder as inh erfc 1gamma remquo atan exp llrint rint atan2 exp2 llround round atanh expml log scalbln carg fabs loglO scalbn cbrt fdim loglp sin ceil floor log2 sinh cimag fma logp sqrt conj fmax Irint tan copysign fmin Iround tanh cos fmod nearbyint tgamma cosh frexp nextafter trunc 10.3.22 time.h См. главу 18. asctime ctime mktime struct tm clock difftime NULL time clock_t gmtime size_t time_t CLOCKS_PER_SEC localtime strftime
340 Глава 10 10.3.23 wchar.h См. главу 24. Файл введен в Дополнении 1 к C89. btowc putwchar wcschr wcstok fgetwc size_t wcscmp wcstol fgetws swprintf wcscoll wcstold fputwc swscanf wcscpy wcstoll fputws tm wcscspn wcstoul fwide ungetwc wcsftime wcstoull fwprintf vfwprintf wcslen wcsxfrm fwscanf vfwscanf wcsncat wctob getwc vswprintf wcsncmp WEOF getwchar vswscanf wcsncpy wint_t mbrlen vwprintf wcspbrk wmemchr mbrtowc vwscanf wcsrchr wmemcmp mbsinit WCHAR-MAX wcsrtombs wmemcpy mbsrtowcs WCHAR_MIN wcsspn wmemmove mbstate_t wchar_t wcsstr wmemset NULL wcrtomb wcstod wprintf putwc wcscat wcstof wscanf 10.3.24 wctype.h См. главу 24. Файл введен в Дополнении 1 к C89. iswalnum iswgraph iswxdigit wctype iswalpha iswlower towctrans wctype_t iswblank iswprint towlower WEOF iswcntrl iswpunct towupper wint_t iswctype iswspace wctrans iswdigit iswupper wctrans t
Глава 11 Стандартные дополнения языка Некоторые библиотеки Standard С можно рассматривать как составную часть языка. Они содержат стандартные определения и параметризацию, способствую- щие большей мобильности программ С, и должны поддерживаться автономными реализациями, даже если остальные библиотеки этому условию не удовлетворя- ют. Эти основные библиотеки состоят из заголовочных файлов float.h, iso646.h, limits.h, stdarg.h, stdbool.h, stddef.h и stdint.h. Описание средств, объявленных в файлах float.h и limits.h, дано в главе 5, библиотека stdint.h рассматривается в главе 21. Остальные библиотеки мы рассмотрим в этой главе. Кроме этого, мы рассмотрим здесь средства errno.h, хотя эта библиотека и не относится к дополнениям языка. Не относится к таковым, несмотря на свое имя, и библиотека stdlib.h, которую мы рассмотрим в главе 16. 11.1 NULL, ptrdiff.t, size_t, offsetof Краткий перечень #include <stddef.h> #define NULL ... typedef ... ptrdiff—t; typedef ... size_t; typedef ... wchar_t; #define offsetof( type, member-designator )... Перечисленные объявления принадлежат файлу stddef.h Значение макроса NULL — обычная константа нулевого указателя. Во мно- гих реализациях это может быть просто целая нулевая константа либо нуль, преобразованный в тип void *. В Standard С для удобства этот макрос объявлен во многих заголовочных файлах. Тип ptrdiff_t — это определяемое реализацией целое со знаком, получаемое в результате вычитания двух указателей. В большинстве случаев, это тип long. Тип size_t — целое без знака, результат операции sizeof. В большинстве случаев это, опять же, тип long. В некоторых реализациях, предшествовав- ших Standard С, тип size_t имеет знак. Максимальные и минимальные значе- ния типов ptrdiff_t и size_t определены в файле stdint.h С99.
342 Глава 11 С ростом мощности процессоров растет также объем оперативной памяти до размеров, превышающих возможности 32-битовых указателей. В некоторых реализациях тип ptrdiff_t может быть эквивалентен типу long long из С99, что иногда порождает проблемы в программах, написанных ранее, когда вы- полнялось условие sizeof(size_t) = sizeof(ptrdiff_t) = sizeof (long). Макрорасширение offsetof — это целое константное выражение (типа size_t), равное смещению в байтах члена member-designator структуры типа type. Если этот член является битовым полем, результат оказывается непред- сказуемым. Если макрос offsetof не определен (что характерно для реализа- ций, предшествовавших Standard С), его можно определить следующим обра- зом: #define offsetof(type, metnb) ((size_t)&((type *) 0) ->memb) Если данная реализация не допускает использования подобным образом констант нулевых указателей, иногда существует возможность вычисления смещения при помощи заранее определенного ненулевого указателя, как раз- ности адресов члена и самой структуры. Пример После выполнения следующего фрагмента программы значение diff будет равно 1, а значения size и offset будут равны. [На компьютерах с байтовой адресацией, где sizeof(int) возвращает 4, как size, так и offset равны 4.] {{include <stddef.h> struct s {int a; int b; } x; size_t size, offset; ptrdiff_t diff; diff = Sx.b - Sx.a; size = sizeof (x.a); offset = offsetof(struct s, b); Тип wchar_t определен в stddef.h, но мы отложим его рассмотрение до гла- вы 24, где будет дано описание заголовочного файла wchar.h. Ссылки: преобразование целых значений в укозатепи 6.2.7; нулевые указатели 5.3 2; ссы- лочные типы 5.3, операция sizeof 7 5 2; stdint.h гл 21; вычитание указателей 7.6.2; wchar_t 24 1 11.2 EDOM, ERANGE, EILSEQ, errno, strerror, perror Краткий перечень ({include <errno.h> extern int errno; или {{define errno ... {{define EDOM {{define ERANGE ... {{define EILSEQ ...
Стандартные дополнения языка 343 Краткий перечень #include <stdio.h> void perror(const char *s) #include <string.h> char ‘strerror(int errnum) Эти средства, определенные в errno.h и других заголовочных файлах, пред- назначены для вывода сообщений об ошибках в стандартных библиотеках. Во внешнюю переменную еггпо записываются коды ошибок библиотечных функций, определяемые реализацией. Традиционно, их имена в errno.h, по- добно макросам, начинаются на Е. Все коды ошибок — это положительные це- лые; библиотечные функции не должны обнулять еггпо. В Standard С еггпо может быть не переменной, а макросом, расширяющимся в любое модифици- руемое 1-значение типа int. Пример Вполне возможно определение еггпо следующим образом: extern int *_errno_func(); ^define errno (*_errno_func()) Пример Один из распространенных способов использования переменной еггпо — обнуление ее перед вызовом функции, затем проверка после вызова: еггпо = 0; х = sqrt (у); if (еггпо) ( printf("Аварийное завершение sqrt, код %d\n", еггпо); х = 0; ) В большинстве реализаций С существует список кодов ошибок, которые мо- гут записываться в переменную еггпо. В файле errno.h определены следующие коды: EDOM Значение аргумента не входит в область значений, определенных для данной математической функции (например, отрицательный аргумент функции log). ERANGE Значение, возвращаемое функцией, не входит в интервал допустимых значений; функция возвращает формально правильное значение, которое не может быть представлено ввиду ограничений формата с плавающей десятичной запятой (точкой). Примером может служить возведение большого число в большую степень (функция pow). EILSEQ Обнаружена ошибка кодирования при трансляции многобойтавой последовательности символов. Эта ошибка, в конце концов, обнаруживается функциями mbrtowc или wcrtomb, которые вызываются другими функциями обработки «широких» символов (см. Дополнение 1 к С89) Функция strerror возвращает указатель на строку сообщения об ошибке, текст которого зависит от реализации. Строка сообщения не модифицируема и может быть переписана только в результате следующего обращения к функ- ции strerror.
344 Глава 11 Функция perror выводит в выходной поток сообщений об ошибках следую- щую последовательность: строку аргумента s, двоеточие, пробел, краткое опи- сание ошибки, код которой указан в переменной errno, и символ смены стро- ки. В Standard С, если s является нулевым указателем или указывает на нуле- вой символ, выводится только текст сообщения — строка префикса, двоеточие и пробел опускаются. Пример Предыдущий пример с функцией sqrt можно переписать для функции perror: #include <math.h> #include <errno.h> errno = 0; x = sqrt(y); if (errno) { perror("Аварийное завершение sqrt"); x = 0, ) Теперь, в случае аварийного завершения sqrt, последует вывод примерно следующе- го сообщения: Аварийное Завершение sqrt: domain error В некоторых системах реализуется схема, не относящаяся к Standard С: со- общения об ошибках, соответствующие значениям errno, могут быть обозначе- ны массивом строковых указателей. Имя этого массива, чаще всего, sys_err- list, его индексы могут соответствовать значениям errno. Переменная sys_nerr содержит максимальное значение индекса массива sys_errlist; следует прове- рить это значение, чтобы убедиться, что errno не содержит нестандартного кода ошибки. Ссылки: ошибко кодирования 2,1,5; mbrtowc 1 1.7; wcrtomb 11 7. 11.3 bool, false, true Краткий перечень ((include <stdbool.h> #define bool _Bool #define false 0 ((define true 1 ((define bool true false are defined 1 Заголовочный файл stdbool.h введен впервые в С99 и содержит именно объ- явления, перечисленные выше. Имена и значения булевых типов соответству- ют C++. Как правило, отменять (#undefine) объявления макросов, располагающих- ся в стандартных заголовочных файлах, не разрешается. Тем не менее, в С99 возможны отмена объявлений и, при необходимости, переопределение макро- сов bool, false и true. Ссылки: тип _Воо1 5 1 5
Стандартные дополнения языка 345 11-4 va_list, va_start, va_arg, va_end Краткий перечень #include <stdarg.h> typedef ... va_list; #define va_start( a_list ap, type LastFixedParm) ... #define va_arg( va_list ap, type) ... void va_end(va_list ap); void va copy(va list dest, va list src) ; Библиотека stdarg.h содержит мобильные средства доступа к спискам аргу- ментов переменной длины, необходимые при использовании функций наподо- бие fprintf (неявно) и vfprintf (явно). Первоначально в С не было никаких ограничений в отношении способа пе- редачи аргументов функциям, так что программисты принимали решения на основе свойств конкретных систем, а потому эти решения оказывались не мо- бильными. В конце концов в традиционном С появилась библиотека var- args.h, обеспечившая мобильность; в Standard С аналогичная библиотека име- ет имя stdarg.h. Это разные библиотеки, поскольку Standard С допускает на- личие фиксированного числа параметров перед переменной частью списка, то- гда как в предыдущих реализациях весь список параметров должен иметь пе- ременную либо фиксированную длину. Описания определенных макросов, функций и типов приведены ниже. Биб- лиотека превосходно организована и практически независима от реализации: va_list Этот тип применяется для объявления локольной переменной состояния с общепринятым именем ар, используемой для передачи параметров. va_start Этот макрос инициализирует переменную состояния ар. Его необходимо вызывать до первого вызова va_arg или va_end. В традиционном С va_start устанавливает в ар внутренний указатель но первый из передаваемых функции аргументов; в Standard С va_start содержит дополнительный параметр — имя последнего фиксированного параметра, — и устанавливает внутренний указатель в ар на первый орп/мент списка переменной длины. va_arg Этот мокрое возвращает значение следующего параметра в списке и переводит внутренний указатель (в ор) на следующий параметр (при наличии). Должен быть укозон тип этого (следующего) параметра (после обычного преобразования аргументов), чтобы va_arg мог вычислить объем, занимаемый им в стеке. При первом вызове, после вызова vo_start, va arg возвращает значение первого порометро в списке переменной длины. va_end Эту функцию (или мокрое) необходимо вызывать после считывания макросом va_arg всех аргументов. Он выполняет все необходимые обнуления переменных ар и vaalist va_copy (С99) Этот макрос копирует текущее состояние из src в dest, создавая, тем самым, второй указатель но список аргументов. После этого va_arg можно применять, независимо, к src и dest. Мокрос va_end необходимо вызывать кок с src, ток и с dest. Имя типа type, указываемое при вызове макроса va_arg, должно быть на- писано таким образом, чтобы присоединение к нему суффикса давало ука- затель на этот тип.
346 Глава 11 Новый макрос С99 va_copy(saved_ap, ар) можно использовать для сохране- ния указателя на список аргументов, пока va_arg(ap, type) проходит по спис- ку. При необходимости можно использовать va_arg(ap, type) для просмотра предыдущей позиции. Пример Мы знаем, как писать функции с переменным числом аргументов в Standard С. В следующем разделе будет показано, как это делается в традиционном С. Функция printargs вызывается с произвольным числом аргументов различных типов и выво- дит их значения на стандартное устройство вывода. Первый аргумент функции printargs — это массив целых значений, указывающий число и типы следующих аргументов. Конец массива обозначается нулевым элементом. Ниже приведен при- мер использования функции printargs, совместимый как с традиционным, так и со Standard С: #include "printargs.h" int arg_types[] = { INTARG, DBLARG, INTARG, DBLARG, 0 ), int main() { printargs(arg_types[0], 1, 2.0, 3, 4.0 ); return 0; Объявление printargs и значения спецификаторов целого типа располагаются в файле printargs.h. /* файл printargs.h; Standard С */ ((include <stdarg.h> #define INTARG 1 /* коды, указанные в argtypepf] */ #define DBLARG 2 void printargs(int *argtypep, ...); Ниже приведено соответствующее определение printargs в Standard С. #include <stdio.h> ((include "printargs .h" void printargs( int *argtypep, ...) /* Standard C */ { va_list ap; int argtype; va_start(ap, argtypep); while ( (argtype = *argtypep++) '= 0 ) { switch (argtype) { case INTARG: printf ("int: %d\n", va_arg(ap, int) ); break; case DBLARG: printf("double: %f\n", va_arg(ap, double) ); break; /* ... */ ) ) /*while*/ va_end(ap) ; )
Стандартные дополнения языка 347 11.4.1 Традиционные возможности: varargs.h Краткий перечень традиционного С #include <varargs.h> #define va_alist ... #define va_dcl ... typedef ... va_list; void va_start( va_list ap ); type va_arg( va_list ap, type); void va end(va list ap); В традиционном С передача функциям произвольного числа аргументов реализуется при помощи заголовочного файла varargs.h. Он содержит два но- вых макроса и новое определение va_start: va_alist Этот макрос заменяет список параметров в определении функции с произвольным числом порометров. va del Этот мокрос заменяет список порометров в определении функции. Чтобы он мог быть пустым, его не надо оканчивать точкой с зопятой. va_start Этот макрос инициализирует переменную состояния ар. Его необходимо вызывать до первого вызова va_arg или va_end. В традиционном С va_start устанавливает в ар внутренний указатель на первый из передаваемых функции аргументов; он принимает на один аргумент меньше, чем версия дпя Standard С. Пример Ниже приведено объявление printargs в традиционном С. /* файл printargs.h; традиционный С */ #define INTARG 1 /* коды, указанные в argtypep[] */ #define DBLARG 2 #include <varargs.h> printargs( va_alist ); А теперь реализация printargs в традиционном С. Отличия только в списке аргу- ментов функции и вызове va_start. #include <stdio.h> #include "printargs.h" printargs( va_alist )/* Традиционный C */ va_dcl { va_list ap; int argtype, *argtypep; va_start (ap); argtypep = va_arg(ap, int *) ; while ( (argtype = *argtypep++) != 0 ) { switch (argtype) ( case INTARG: printf("int: %d\n", va_arg(ap, int) ); break; case DBLARG: printf("double: %f\n", va_arg(ap, double) ); break; /* ... */ ) )
348 Глава 11 va_end(ap); > 11.5 Макросы операций Standard С Краткий перечень #include <iso646.h> #define a n d&& #define and_eqs= Udefine bitandi #define bito r| #define comp 1- #define n о t! Sdefine not_eq!= #define о r | | #define or_e q|= #define x о rA #define xor eqA- Дополнение 1 к C89 включает заголовочный файл iso646.h, содержащий определения макросов, которые можно использовать вместо знаков некоторых операций. Эти операции могут оказаться неудобными для написания в ограни- ченном символьном наборе (ISO 646). В C++ эти операции обозначаются клю- чевыми словами. Пример Следующие три оператора if идентичны. #include <iso646.h> if (*Р 1 1 ч != 0) *Р л = q; /* обычная запись */ if (*р О о 1 9 9 ! q != 0) *Р ??’ = q; /* триграфы */ if (*Р or ч != 0) *Р xor_eq q; /* iso646.h */ Кроме этого, в Дополнении 1 предусмотрена замена знаков пунктуации на- подобие { и } для символьных наборов, в которых эти знаки не употребляются. Ссылки: ключевые слово 2.6; замена символов 2.4.1; триграфы 2 1.4
Глава 12 Обработка символов Существуют два способа обработки текстовых символов: классификация и преобразование. Имя любого классификационного средства начинается на is, оно возвращает значение типа int; если это значение не равно нулю (true), аргумент принадлежит указанному классу, если равно нулю (false) — не при- надлежит. Имя любого средства преобразования символов начинается на to, оно возвращает значение типа int, представляющее текстовый символ либо признак конца файла (EOF). В Standard С имена, начинающиеся на is и to, за- резервированы для функций классификации и преобразования, которые могут появиться в будущем. Функции обработки символов, рассматриваемые в этой главе, объявлены в библиотечном заголовочном файле ctype.h. В Дополнении 1 к С89 определен параллельный набор функций классифи- кации и преобразования, предназначенных для обработки «широких» симво- лов. Имена этих функций начинаются на isw и tow; остальная часть каждого имени — как у соответствующей обычной символьной функции. Функции классификации широких символов вызываются с аргументами типа wint_t и возвращают значение истинности типа int. Функции преобразования отобра- жают одни значения типа wint_t в другие. Существуют также обобщенные функции классификации (wctrans и iswctrans) и преобразования (wctrans и towctrans) для расширенных символьных наборов, которые могут классифи- цироваться особым образом. Эти функции определены в заголовочном файле wctype.h. Отрицательное целое EOF — это код, не представляющий реального симво- ла (соответствующий широкий символ — WEOF). Например, функция fgetc (раздел 15.6) возвращает символ EOF, когда файловый указатель позиции на- ходится в конце файла, не считывая, при этом, никакого реального символа. Следует, однако, помнить, что переменная типа char в некоторых реализаци- ях может иметь знак, а это означает возможность совпадения символа EOF с одним из реальных (нестандартных) символов (стандартные символы в слу- чае типа char со знаком кодируются неотрицательными значениями). Все функции, рассматриваемые в этой главе, правильно работают с любыми значе- ниями, которые могут быть отнесены к типу char или unsigned char, а также и со значением EOF, но дают непредсказуемый результат с любыми другими целыми значениями, если только в описании какой-либо из них не указано об- ратное. Значение WEOF имеет тот же смысл в типе wchar_t, что EOF в char, но не обязано быть отрицательным. Формулирование указанных возможностей в Standard С было сделано с уче- том необходимости поддержки различных географических (национальных) ус-
350 Глава 12 ловий; как правило, компилятор пытается, по возможности, принимать неко- торые допущения о кодировании символов и таких понятиях, как «буква». В этом отношении версии этих функций из традиционного С в общих чертах аналогичны версиям Standard С, за исключением того, что все ASCII-функции (например, isascii и toascii) здесь также удалены. Предупреждение; В некоторых реализациях, отличных от Standard С, пере- менные типа char могут иметь знак, беззнаковый же символьный тип называ- ется unsigned char — при этом, однако, некоторые функции обработки симво- лов могут работать с переменными только первого типа. Впрочем, иногда эти функции не могут работать даже с типом char — только со «стандартными» символами, включая EOF. Ссылки: EOF 111; WEOF I I. I; широкий символ 2.1,4; wchar_t I I. I; wint_t 111. 12.1 isalnum, isalpha, iscntrl, iswalnum, iswalpha, iswcntrl Краткий перечень ^include <ctype.h> int isalnum(int c); int isalpha(int c); int iscntrl(int c) ; int isascii(int с); /* Общее расширение */ #include <wctype.h> int iswalnum(wint_t c); int iswalpha(wint_t c); int iswcntrl(wint—t c); Функция isalnum проверяет, принадлежит ли с к буквенно-цифровым зна- кам — то есть является ли одним из следующих символов (при значении гео- графического параметра "С”): 0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnapqrstuvwxyz По определению эта функция эквивалентна следующему выражению: isalpha(с) || isdigit (с) Функция isalnum проверяет, является ли с буквой — то есть одним из сле- дующих символов (при значении географического параметра "С"): ABCDEFGHI JKLMNOPQRSTUVWXYZ abcde fghi j k Imnopqrs tuvwxy z При любой географической установке эта функция возвращает true, если islower(c) или isupper(c) возвращает true; она возвращает false, если хотя бы одна из функций iscntrl (с), isdigit(c), ispunct(c) или isspace(c) возвращает true. В остальных случаях, поведение функции зависит от реализации. Функция iscntrl проверяет, является ли с «управляющим символом». При использовании стандартного 128-символьного набора ASCII, к управляющим
Обработка символов 351 относятся символы, кодируемые значениями от 0 до 31 (37g или IFig), а также символ с кодом 127 (177g или 7F16). Функция isprint (раздел 12.4) является дополнительной (по крайней мере, в отношении стандартных реализаций ASCII). Функция isascii не входит в Standard С, но распространена как расширение библиотек С. Ее назначение — проверка принадлежности значения с интервалу от 0 до 127 (177g или 7F16), что соответствует принадлежности к стандартному 128-символьному набору ASCII. В отличие от других функций классификации символов традиционного С, isascii правильно действует с любым аргументом типа int (именно этот тип объявлен для ее аргумента даже в традиционном С). В традиционном С каждая из этих функций принимает аргумент типа char, но возвращает значение int. Пример Следующая функция is_id возвращает TRUE, если строковый аргумент s является правильным идентификатором С, иначе — FALSE. Функция работает правильно только при значении географического параметра, равном "С". # include <ctype.h> # define TRUE 1 # define FALSE 0 int is_id(const char *s) { char ch; if ((ch = *s++) = '\0') return FALSE; /* пустая строка */ if (* (isalpha(ch) || ch = return FALSE; while ((ch = *s++) != ' \0') { if (!(isalnum(ch) || ch = ) return FALSE; I return TRUE; I 12.1.1 Функции обработки широких символов Заголовочный файл wctype.h, определенный в Дополнении 1 к С89, содер- жит объявления трех дополнительных функций. Функция iswalnum действует как выражение iswalpha(c) || iswdigit(c). Функция iswalpha проверяет, принадлежит ли символ с символьному набо- ру, который соответствует установленному географическому параметру. При любом географическом параметре эта функция возвращает true, если isw- lower(c) или iswupper(c) возвращает true; она же возвращает false, если iswcntrl(c), iswdigit(c), iswpunct(c) или iswspace(c) возвращает true; в осталь- ных случаях, поведение этой функции зависит от реализации. Функция iswcntrl возвращает ненулевое значение, если аргумент с равен коду одного из управляющих широких символов набора, соответствующего ус- тановленному географическому параметру. Широкие символы управления пе- чатью, определяемые классификационной функцией iswprint (раздел 12.4), не относятся к управляющим.
352 Глава 12 12.2 iscsym, iscsymf Краткий перечень (не Standard С) #include <ctype.h> int iscsym(char c); int iscsymf(char c); В Standard С эти функции отсутствуют. Функция iscsym предназначена для проверки возможности включения символа в идентификатор С, iscsymf — для проверки возможности указания символа в первой позиции идентификатора. Функция iscsymf возвращает true, если в качестве аргумента указаны одна из минимум 52 букв верхнего и нижнего регистров или символ подчеркива- ния; iscsym, кроме этого, возвращает true, если аргумент — одна из минимум 10 десятичных цифр. В зависимости от реализации обе функции могут возвра- щать значение true и в других условиях. 12.3 isdigit, isodigit, isxdigit, iswdigit, iswxdigit Краткий перечень #include <ctype.h> int isdigit (int c); int isxdigit(int c) tfinclude <wctype.h> int iswdigit(wint_t c}; int iswxdigit(wint—t c); Функция isdigit проверяет принадлежность аргумента с множеству десятич- ных цифр, функция isxdigit — множеству шестнадцатеричных (22 символа): 0123456789ABCDEFabcdef В реализациях, предшествовавших Standard С, эти функции вызываются с аргументом типа char, возвращают значение типа int. Существует также функция isodigit, не принадлежащая Standard С и применяемая для проверки равенства аргумента одной из восьми восьмеричных цифр. 12.3.1 Функции обработки широких символов Функция iswdigit (Дополнение 1 к С89) проверяет принадлежность сим- вольного аргумента с группе десятичных цифр, функция iswxdigit — группе шестнадцатеричных.
Обработка символов 353 12.4 isgraph, isprint, ispunct, iswgraph, iswprint, iswpunct Краткий перечень #include <ctype.h> int isgraph(int c); int ispunct(int c) ; int isprint(char c); #include <wctype.h> int iswgraph(wint_t c) ; int iswpunct(wint_t c); int iswprint(wint t c); Функция isprint проверяет принадлежность с к печатным (не управляю- щим) символам. Пробел всегда относится именно к печатным. Функция isgraph определяет, является ли данный символ графическим — то есть лю- бым печатным, кроме пробела. Функции isprint и isgraph отличаются друг от друга трактовкой пробела; функция isprint в большинстве реализаций обратна функции iscntrl — впрочем, не при всех значениях географического парамет- ра в Standard С. В традиционном С указанные функции вызываются с аргу- ментом типа char, возвращают значения типа int. Пример В стандартном 128-символьном наборе ASCII печатными считаются символы с кода- ми от 040 до 0176 — то есть пробел плюс следующие символы: !"#$%&' ()* + ,- ./ 0123456789: ;< = >? 0ABCDEFGHIJKLMNOPQRSTUVWXYZ [ \ ] Л _ abcdefghijklmnopqrstuvwxyz ( I } ~ Эти же символы, кроме пробела, относятся к графическим. Функция ispunct проверяет принадлежность с к пунктуационным симво- лам (знакам препинания) — то есть к печатным символам, кроме пробела и всех, для которых функция isalnum возвращает true. Пример В стандартном 128-символьном наборе ASCII пунктуационными считаются пробел плюс следующие символы: !"#$%&' ()* + ,- ./:;< = > 12.4.1 Функции обработки широких символов Функция iswprint (Дополнение к С89) проверяет принадлежность аргумен- та с к печатным широким символам — то есть к набору широких символов, за-
354 Глава 12 висящему от географической установки, в котором каждый из символов зани- мает на устройстве вывода минимум одну позицию и не является управляю- щим символом. Функция iswgraph эквивалентна выражению iswprint(c) && (iswspace(c). Функция iswpunct проверяет, относится ли с к зависящей от географической установки группе широких символов, для которых выполняется условие: iswprint(c) && !(iswalnum(с) || iswspace(c)) 12.5 islower, isupper, iswlower, iswupper Кроткий перечень #include <ctype.h> int islower(int c); int isupper(int c) ; #include <wctype.h> int iswlower(wint_t c); int iswupper(wint t c); Если географический параметр равен "С", функция islower проверяет при- надлежность с к 26 буквам нижнего регистра (строчным), isupper — к 26 бук- вам верхнего регистра (прописным). Поведение этих функций может меняться в зависимости от значения географического параметра, но символы, для кото- рых они возвращают true, должны удовлетворять следующему условию: (iscntrl(c) && (isdigit(с) && (ispunct(c) && (isspace(с) В традиционном С указанные функции вызываются с аргументом типа char, возвращают значения типа int. 12.5.1 Функции обработки широких символов Функция iswlower (Дополнение 1 к С89) проверяет, принадлежит ли аргу- мент с к буквам нижнего регистра или к другому, зависящему от географиче- ского параметра, набору широких символов, удовлетворяющих условию: (iswcntrl(c) && (iswdigit(c) && (iswpunct(с) && (iswspace(c) Функция iswupper проверяет, принадлежит ли аргумент к буквам верхнего регистра или к другому, зависящему от географического параметра, набору широких символов, удовлетворяющих тому же условию, что и в случае функ- ции iswlower. 12.6 isblank, isspace, iswhite, iswspace Кроткий перечень tfinclude <ctype.h> int isblank(int c); int isspace(int c};
Обработка символов 355 Краткий перечень #include <wctylpe.h> int iswspace(wint t c); Функция isspace проверяет, является ли с кодом пробельного символа. Если значение географического параметра равно "С", isspace возвращает true только для символов табуляции ('\t'), возврата каретки ('\г'), смены строки ('\п' ), вертикальной табуляции ('\v' ), подачи страницы ('\f') и пробела (' '). Очень часто эта функция используется в качестве определения пробельного символа. Функция isblank проверяет, является ли с кодом символа, используемого в качестве разделителя слов в текстовой строке. Стандартные символы пробела (' ’) и табуляции С\С) всегда относятся к таким разделителям; к ним же могут от- носиться, в зависимости от значения географической установки, и другие симво- лы, для которых функция isspace также возвращает true. Если значение геогра- фического параметра равно "С”, такие дополнительные символы отсутствуют. В некоторых реализациях С определен вариант функции isspace с именем iswhite. В традиционном С указанные функции вызываются с аргументом типа char, возвращают значения типа int. 12.6.1 Функции обработки широких символов Функция iswspace (Дополнение 1 к С89) проверяет, принадлежит ли с зави- сящему от географической установки набору широких символов, для которых выполняется условие: !iswalnum(а) && 'iswgraph(c) && 'ispunct(c) 12.7 toascii Краткий перечень (не Standord С) #include <ctype.h> int toascii(int c); He входящая в Standard С функция toascii приводит любое целое значение к одному из значений диапазона ASCII-символов (коды от 0 до 127 [177g или 7Fi6]) обнуляя все биты, кроме семи справа (младших значащих). Если аргу- мент уже является ASCII-кодом, результат будет равным ему. 12.8toint Краткий перечень (не Standard С) #include <ctype.h> int toint(char с); He входящая в Standard С функция toint возвращает «вес» шестнадцате- ричных цифр: от 0 до 9 для символов, соответственно, от '0' до '9' и от 10 до 15 для символов от ’а’ до 'С (или от 'А' до 'F'). Поведение функции в случае, если аргумент не является шестнадцатеричной цифрой, зависит от реализации.
356 Глава 12 Пример Эта функция отсутствует в Standard С, но ее нетрудно написать. В следующем ис- ходном тексте предполагается смежность кодов отдельных символов: int toint( int с ) ( if (с >= '0' 44 с <= '9') return с - 'О’; if (с >= ’А' 44 с <= 'F') return с - 'А' + 10; if (с >= 'а* 44 с <= ’f1) return с - 'а* + 10; /* с не является шестнадцатеричной цифрой */ return 0; 12.9 tolower, toupper, towlower, towupper Краткий перечень #include <ctype.h> int tolower(int c); int toupper(int c); #include <wctype.h> wint_t towlower(wint_t c); wint t towupper(wint t c); Если c — буква верхнего регистра, tolower возвратит соответствующую ей букву нижнего регистра; соответственно, toupper возвратит букву верхнего ре- гистра, соответствующую букве с нижнего регистра. В остальных случаях обе функции возвращают значение аргумента. При некоторых географических ус- тановках возможно существование букв верхнего регистра, не имеющих двой- ников в нижнем и наоборот; в этих случаях, опять же, обе функции возвраща- ют неизмененный аргумент. Функции towlower и towupper определены в Дополнении 1 к С89. Если с — некоторый широкий символ, для которого iswupper(c) возвращает true, ad — соответствующий ему широкий символ, для которого iswlower(d) возвращает true, тогда towlower(c) возвращает d, a towupper(d) возвращает с. В остальных случаях, обе функции возвращают значения аргументов. В реализациях, отличных от Standard С, следует соблюдать осторожность в использовании значений, возвращаемых функцией tolower с аргументом, не являющимся буквой верхнего регистра, и функцией toupper с аргументом, не являющимся буквой нижнего регистра. Во многих прежних реализациях эти функции работают правильно только при условии указания аргумента нужно- го регистра. В реализациях, где такие ограничения отсутствуют, эти функции могут дублироваться более быстрыми макросами _tolower и _toupper. Макро- сы предъявляют более жесткие требования к аргументам, но выполняются бы- стрее. Сигнатуры, не входящие в Standard С: #include <ctype.h> int tolower(char c); int toupper(char c); #define _tolower(c) #define _toupper(c)
Обработка символов 357 Пример Если версия функции tolower в библиотеке С ведет себя в отношении произвольно- сти аргументов не лучшим образом, то функция safe_tolower полностью ей иден- тична и допускает использование любых аргументов. Написать безопасный вариант этой функции в виде макроса затруднительно, поскольку оценку аргумента в этом случае приходится выполнять более одного раза (функциями isupper и tolower, а также оператором return): #include <ctype.h> int safe_tolower(int c) { if(isupper(c)) return tolower(c); else return c; } 12.10 wctype.t, wctype, iswctype Краткий перечень #include <wctype.h> typedef ... wctype_t; wctype_t wctype(const char *property); int iswctype(wint t c, wctype t desc) ; Функции wctype и iswctype определены в Дополнении 1 к С89. Они обеспе- чивают богатые возможности классификации широких символов с учетом гео- графической установки. Тип wctype_t — скалярный. Значения этого типа представляют классифика- цию широких символов в зависимости от географической установки. Функция wctype образует значение типа wctype_t, представляющее некоторый класс ши- роких символов. Такой класс обозначается строковым именем property, завися- щим от значения категории LC_CTYPE текущей географической установки. При любой географической установке property может содержать любое из стро- ковых имен, перечисленных в таблице 12.1 вместе с описаниями. Функция iswctype проверяет принадлежность аргумента с классу, указан- ному значением desc. Установка категории LC_CTYPE при вызове функции iswctype должна быть той же, что и при вызове wctype. Имя property Определяет класс, для которого "alnum" iswalnum(c) равно true "olpha" iswalpha(c) равно true "cntrl” iswcntrl(c) равно true "digit" iswdigit(c) ровно true "graph" iswgraph(e) ровно true "lower" iswlower(c) ровно true "print" iswprint(c) равно true "punct" iswpunct(c) ровно true "space" iswspace(c) ровно true
358 Глава 12 Имя property Определяет класс, для которого "upper" iswupper(c) равно true "xdigit" iswxdigit(c) ровно true Пример Выражение iswctype(c, wctype("alnum”)) возвращает значение true, как и iswal- num(c) для любого широкого символа с и при любой географической установке. Это же условие выполняется для других строковых свойств и соответствующих им клас- сификационных функций. Ссылки: LC_CTYPE I 1.5; геогрофическая установка 11,5. 12.11 wctrans.t, wctrans Краткий перечень #lnclude <wctype.h> typedef ... wctrans_t; wctrans_t wctrans( const char *property ); wint t towctrans( wint t c, wctrans t desc ) ; Функции, рассматриваемые в этом разделе, определены в Дополнении 1 к С89. Они выполняют разнообразные отображения широких символов в зави- симости от географической установки. Переменные скалярного типа wctrans_t содержат значения, представляю- щие отображения широких символов в зависимости от географической уста- новки. Функция wctrans образует значение типа wctrans_t, представляющее отображения одних широких символов в другие. Отображение задается стро- ковым именем property, зависящим от значения категории LC_CTYPE теку- щей географической установки. При любой географической установке pro- perty может содержать любое из перечисленных ниже строковых значений: Значение property To же отображение, что и при помощи функции "to lower" towlower(c) "toupper" towupper(c) Обратите внимание на отличие имен property от имен функций. Функция towctrans отображает аргумент с в другой широкий символ, зави- сящий от значения desc. Установка категории LC_CTYPE при вызове функ- ции towctrans должна быть такой же, как и при определении значения desc функцией wctrans. Пример Выражение towctrans(c, wctrans(”tolower")), где с — широкий символ, возвращает при любой географической установке то же значение, что и функция towlower(c). Все сказанное относится, также, ко второй строке property и соответствующей ей функции отображения. Ссылки: LC_CTYPE 20.1; географический параметр 20.1.
Глава 13 Обработка строк По соглашению, строки в С — это массивы символов, оканчивающиеся ну- левым символом ('\0'). Компилятор автоматически добавляет нулевой символ в конец строковых констант, однако программисту приходится самому забо- титься о том, чтобы каждый массив текстовых символов завершался правиль- но. Все функции обработки строк, которые мы рассмотрим в этой главе, напи- саны для строк, завершающихся нулевым символом. Все символы в строке, кроме завершающего нулевого — это ее содержимое. Пустая строка не содержит ни одного символа и представлена лишь указате- лем на нулевой символ — не следует путать его с нулевым (NULL) указателем на символ, который вообще ни на что не указывает. Функции записи символов в строку часто не выполняют проверки переполне- ния, поэтому программисту необходимо самому заботиться о распределении дос- таточного объема памяти для строк (с учетом завершающего нулевого символа). Большинство функций, рассматриваемых в этой главе, объявлены в биб- лиотечном заголовочном файле string.h; несколько функций преобразования из Standard С объявлены в файле stdlib.h. В Standard С тип немодифицируе- мых строковых параметров, как правило, объявляется как const char *, а не char *; целые аргументы и значения, возвращаемые функциями, объявляются не как int, а как size_t. В Дополнении 1 к С89 определен дублирующий набор функций обработки строк широких символов. Аргументы этих функций имеют тип wchar_t * (вместо char *), а имена образуются из имен соответствующих обычных функ- ций заменой первых трех букв со str на wcs. Строки широких символов окан- чиваются нулевым широким символом. При сравнении строк широких симво- лов происходит сравнение составляющих их элементов типа wchar_t. Широ- кие символы не интерпретируются, поэтому возникновение ошибок кодирования невозможно. К строковым функциям относятся также функции управления памятью (глава 14), sprintf (раздел 15.11) и sscanf (раздел 15.8). Ссылки: wchar_t 11.1; широкий символ 2.1.4.
360 Глава 13 13.1 strcat, strncat, vwcscat, wcsncat Краткий перечень #include <string.h> char *strcat( char *dest, const char *src ) ; char *strncat( char ‘dest, const char ‘src, size t n ); #include <wchr.h> wchar_t ‘wcscat) wchar t ‘dest, const wchar t ‘src ); wchar t ‘wcsncat) wchar t ‘dest, const wchar t ‘src, size t n ); Функция strcat присоединяет содержимое строки src в конец строки dest и возвращает результат. Новые символы записываются поверх нулевого сим- вола, завершающего строку dest (и, возможно, других символов, которые мо- гут следовать за ним), затем в конец полученного таким образом сцепления строк добавляется новый нулевой символ. Копирование символов из строки src продолжается до достижения завершающего ее нулевого символа. Следует помнить о необходимости распределения строке dest объема памяти, достаточ- ного для обеих строк. Функция wcscat отличается от strcat только типами аргументов и результата. Пример В следующем фрагменте происходит присоединение в конец строки D трех строк, в результате чего получается строка с текстом "All for one. " (Все за одного.) ftinclude <string,h> char D[2 0] ; D[0] = 1 \0 ' ; /* Пустая строка */ strcat (D, "All "); strcat (D, "for "); strcat (D, "one. "); Функция strncat присоединяет не более n символов строки src в конец стро- ки dest. Если нулевой символ, обозначающий конец строки src, достигается раньше, чем будут скопированы п символов, этот символ копируется и процесс прекращается. Если среди первых п символов строки src нет нулевого, после их копирования в конец строки dest добавляется нулевой символ и процесс, опять же, прекращается — то есть происходит запись п+1 символов. Если зна- чение п — нулевое или отрицательное, функция strncat не производит ника- ких действий и возвращает строку dest в исходном виде. В традиционном С по- следний аргумент функции strncat имеет тип int. Функция wcsncat аналогична strncat, за исключением типов аргументов и возвращаемого значения. Если строки занимают общую память, поведение этих функций оказывает- ся непредсказуемым.
Обработка строк 361 13.2 strcmp, strncmp, wvcscmp, wcsncmp Краткий перечень #include Otting. h> int strcmp( const char *sl, const char *s2 ) ; int strncmp( const char *sl, const char *s2, size t n ); #include <wchr.h> int wcscmp( const wchar_t *sl, const wchar_t *s2 ); int wcsncmp( const wchar t *sl, const wchar t *s2, size t n ); Функция strcmp лексикографически сравнивает содержимое нуль-строки (строки, завершающейся нулевым символом) si с содержимым нуль-строки s2 и возвращает значение типа int, которое меньше нуля, если si меньше s2, рав- но нулю, если si и s2 равны, и больше нуля, если si больше s2. Пример Для проверки только равенства двух строк, можно воспользоваться функцией strcmp в сочетании с операцией логического НЕ: if (!strcmp(sl, s2)) printf("Строки равны\п"); else printf("Строки не равны\п"); Две строки равны, если их содержимое идентично. Строка si лексикографи- чески меньше строки s2, если выполняется одно из следующих двух условий: 1. Строки равны вплоть до некоторой позиции, в которой символ строки si меньше символа строки s2. 2. Строка si короче строки s2, и содержимое si идентично содержимому начальной части s2, по длине равной si. Функция wcscmp (Дополнение 1) аналогична strcmp, за исключением ти- пов аргументов. Функция strncmp аналогична strcmp, за исключением того, что сравнивает не более п первых символов нуль-строки si с соответствующими символами нуль-строки s2. Если длина строки si меньше п, она сравнивается целиком, иначе трактуется как строка длиной п. При нулевом или отрицательном зна- чении п обе строки рассматриваются как пустые, и функция возвращает нуле- вой результат. В традиционном С аргумент п имеет тип int. Функция wcsncmp аналогична strncmp, за исключением типов аргументов. Функция memcmp (раздел 14.2) действует подобно strcmp. Действие функ- ции street! (раздел 13.10) зависит от географической установки. 13.3 strepy, strnepy, vwesepy, wesnepy Краткий перечень #include <string.h> char *strcpy( char *dest, const char *src ); char *strncpy( char *dest, const char ‘sre, size t n );
362 Глава 13 Краткий перечень #include <wchar.h> wchar_t *wcscpy( wchar_t *dest, const wchar_t *src ); wchar t *wcsncpy( wchar t *dest, const wchar t *src, size tn); Функция strcpy копирует содержимое строки src в строку dest, поверх ее содержимого. Копируется все содержимое src, включая нулевой символ в кон- це, даже если src длиннее dest. Функция возвращает полученную в результате строку dest. Функция wcscpy (Дополнение 1 к С89) аналогична strcpy, за исключением типов аргументов. Пример Функцию strcat (раздел 13.1) можно реализовать при помощи функций strcpy и strlen (раздел 13.4) следующим образом: ((include <string.h> char *strcat(char *dest, const char *src) { char *s = dest + strlen(dest); strcpy(s, src); return dest; ) Функция strncpy копирует из src в dest ровно n символов и возвращает мо- дифицированную строку. Если длина строки src меньше п, остальные позиции строки dest заполняются нулевыми символами. Если длина строки src больше или равна п, копируются ровно п символов — то есть усеченный вариант src. Это означает, что процесс завершается копированием нулевого символа только в том случае, если длина src (без учета нулевого символа в конце) оказывается меньше п. В случае нулевого или отрицательного значения п функция strncpy не выполняет никаких действий, возвращая строку dest в исходном виде. В традиционном С аргумент п имеет тип int. Функция wcsncpy (Дополнение 1) аналогична strcpy, за исключением ти- пов аргументов. Функции memcpy и memccpy (раздел 14.3) действуют подобно strcpy. Ре- зультат применения функций strcpy и strncpy, как и их двойников, предна- значенных для строк широких символов, в случае использования двумя стро- ковыми аргументами общей памяти, непредсказуем. В этой ситуации можно использовать функции memmove и wmemmove (раздел 14.3) из Standard С. 13.4 strlen, wcslen Краткий перечень #include <string.h> size t strlen(const char *s) ; ((include <wchar.h> size t wcslen(const wchar t *s);
Обработка строк 363 Функция strlen возвращает число символов в строке s без учета нулевого символа в конце. Пустая строка содержит нулевой символ в первой позиции и имеет длину, равную нулю. В некоторых старых реализациях С данная функция именуется lenstr. Функция wcslen (Дополнение к С89) аналогична strlen, за исключением ти- пов аргументов. 13.5 strchr, st г г ch г, wcschr, wcsrchr Краткий перечень #include <string.h> char *strchr( const char *s, int c ); char *strrchr( const char *s, int c ); #include <wchar.h> wchar_t *wcschr( const wchar_t *s, wchar_t c ); wchar t ‘wcsrchr( const wchar t *s, wchar t c ); Функции, рассматриваемые в этом разделе, выполняют поиск одиночных символов в нуль-строке s. В функциях Standard С заключительный нулевой символ считается частью строки. Следовательно, если с — нулевой символ, любая из функций возвратит номер позиции символа, обозначающего конец строки s. В Standard С аргумент с имеет тип int, в традиционном — char. Все функции возвращают указатель на объект, не являющийся константой; тем не менее, это может быть именно строковая константа, если таковая была указа- на в качестве аргумента. Естественно, попытка записи данных в такую строку приводит к непредсказуемым последствиям. Функция strchr выполняет поиск первого вхождения символа с в строку s. В случае успешного поиска, функция возвращает указатель именно на этот первый символ, иначе — нулевой указатель. Функция wcschr (Дополнение 1 к С89) аналогична strchr, за исключением типов аргументов и возвращаемого значения. Функция strrchr аналогична strchr, но возвращает указатель на последнее вхождение символа с в строку s. Если найти символ не удается, функция воз- вращает нулевой указатель. Функция wcsrchr (Дополнение 1 к С89) аналогична strrchr, за исключени- ем типов аргументов и возвращаемого значения. Функция strpos из традиционного С аналогична strchr, но возвращает зна- чение типа int, указывающее номер позиции первого вхождения символа с в строку s (позиция первого символа в строке считается равной 0). Если найти символ не удается, функция возвращает значение -1. Функция strrpos аналогична strpos, но возвращает номер позиции последнего вхожде- ния символа с в строку s. Как strpos, так и strrpos отсутствуют в Standard С. Функции memchr и wmemchr (раздел 14.1) действуют аналогично strchr и wcschr. В некоторых реализациях С функции strchr и strrchr именуются, соответственно, index и rindex. В некоторых реализациях определена функ- ция scnstr, представляющая собой вариант strpos.
364 Глава 13 Пример Следующая функция how_many определяет при помощи функции strchr число вхо- ждений в строку указанного символа. Параметр s после всех модификаций указыва- ет на часть строки, расположенную после символа, найденного последним: int how_many(const char *s, int c) { int n = 0; if (c == 0) return 0; while (s) { s = strchr(s, c); if (s) n++, s++; } return n; 13.6 strspn, strcspn, strpbrk, strrpbrk, vwcsspn, wcscspn, wcspbrk Краткий перечень ((include <string.h> size_t strspn( const char *s, const char ‘set ); size_t strcspn( const char *s, const char ‘set ); char *strpbrk( const char ‘s, const char ‘set ); #include <wchar.h> size_t wcsspn( const wchar_t *s, const wchar_t ‘set ); size_t wcscspn( const wchar_t *s, const wchar_t ‘set }; wchar t *wcspbrk( const wchar t ‘s, const wchar t ‘set ); Функции этого раздела выполняют проверку наличия или отсутствия в нуль-строке s символов нуль-строки set. Второй аргумент рассматривается как набор символов, порядок следования которых, как и наличие дублирован- ных, не имеют значения. Функция strspn выполняет поиск в строке s первого символа, не входящего в строку set, пропуская символы, которые есть в этой строке. Возвращаемое значение — длина начальной части строки s, состоящей только из символов строки set. Если строка s полностью состоит из символов строки set, функция возвращает ее полную длину (без учета нулевого символа в конце). Если set — пустая строка, функция возвращает нуль. Функция strcspn аналогична strspn, но выполняет поиск первого символа, входящего в строку set, пропуская символы, которых в этой строке нет. Функция strpbrk аналогична strcspn, но возвращает не число пропущен- ных символов, а указатель на первый символ строки s, входящий также в строку set. Если в строке s нет символов, входящих также в строку set, функция возвращает нулевой указатель. Функция strrpbrk, не входящая в Standard С, имеет ту же сигнатуру, что и strpbrk, но возвращает указатель на последний из расположенных в строке s символов, входящих в строку set. Если строка s не содержит символов, входя- щих в строку set, функция возвращает нулевой указатель.
Обработка строк 365 Функции wcsspn, wcscspn и wcspbrk (Дополнение к С89) аналогичны своим двойникам, имена которых начинаются на str, за исключением типов аргу- ментов и возвращаемых значений. Изредка, функции strspn и strcspn используются под именами notstr и instr. Пример Функция is_id проверяет, является ли ее аргумент правильным идентификатором С, strspn — состоит ли он только из букв, цифр и символов подчеркивания. Если это условие выполняется, следует проверка, не является ли первый символ цифрой. Сравните следующий исходный текст с рассмотренным в разделе 12.1: «include <string.h> «define TRUE (1) «define FALSE (0) int is id(const char *s) ( static char *id chars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; if (s == NULL) return FALSE; if (strspnfs, id chars) ’ = strlen(s)) return FALSE; return !isdigit(*s); ) 13.7 strstr, strtok, wcsstr, wcstok Краткий перечень «include <string.h> char *strtok( char *str, const char ‘set }; char *strstr( const char ‘src, const char ‘sub ); «include <wchar.h> wchar_t *wcstok( wchar t ‘str, const wchar t ‘set, wchar t “ptr ) ; wchar_t ‘wcsstr( const wchar t ‘src, const wchar t ‘sub ); Функция strstr — новая в Standard С. Она находит первое вхождение стро- ки sub в строку src и возвращает указатель на начало найденной подстроки. Если строка src не содержит подстроки sub, функция возвращает нулевой ука- затель. Функция wcsstr (Дополнение 1 к С89) аналогична strstr, за исключе- нием типов аргументов и возвращаемых значений. Функцию strtok можно применить для разбивки строки str на части, отде- ляемые друг от друга символами из строки set. Для образования каждой такой части требуется отдельный вызов функции strtok; в серии таких вызовов воз- можно изменение строки set. При первом вызове первый аргумент указывает на всю строку str, в каждом последующем — это нулевой указатель, что трак- туется как приказ продолжать с конца обработанной части. (Исходная строка str не должна модифицироваться, пока не завершится весь процесс.)
366 Глава 13 Разберем этот процесс подробнее. Если строка str не пуста, strtok проходит ее всю, определяя символы, которые входят также в set. Если оказывается, что строка str полностью состоит из символов, входящих в строку set, strtok воз- вращает нулевой указатель; внутренний указатель состояния также устанавли- вается в нуль. Если же в строке str есть символы, не входящие в строку set, внутренний указатель устанавливается на первый такой символ, и далее выпол- нение функции продолжается, как если бы строка str была нулевой. При нулевых str и внутреннем указателе strtok возвращает также нулевой указатель; при этом, внутренний указатель остается прежним. (Это необходи- мо для выявления избыточных вызовов функции strtok, когда разбивка стро- ки на части уже завершена.) Если str — нулевая, в то время как внутренний указатель ссылается на определенный символ, поиск начинается именно с это- го символа. Если поиск приводит к обнаружению символа, входящего в строку set, он заменяется символом '\0', a strtok возвращает значение внутреннего указателя, который переводится на следующий за нулевым символ. Если сим- вол не обнаружен, то strtok возвращает значение внутреннего указателя, и внутренний указатель устанавливается в нуль. Библиотечные средства Standard С никоим образом не могут менять внут- реннее состояние функции strtok — то есть программисту не следует беспоко- иться о том, что та или иная библиотечная функция использует strtok и меша- ет программисту. Функция wcstok (Дополнение 1 к С89) аналогична strtok, за исключением типов аргументов и результата. Кроме того, она вызывается с дополнительным параметром ptr, который соответствует указателю внутреннего состояния функции strtok. Таким образом, wcstok позволяет регулировать свое внутрен- нее состояние. Если первый аргумент функций strstr и wcsstr — указатель на строковую константу, тот же тип имеют и возвращаемые значения, хотя они и не объяв- лены таковыми. Пример Следующая программа считывает текстовые строки со стандартного устройства ввода и разбивает их при помощи функции strtok на «слова» — последовательности симво- лов, разделяемые пробелами, запятыми, точками, кавычками и (или) вопроситель- ными знаками. Образованные слона выводятся на стандартное устройство вывода: ((include <stdio.h> ((include <string.h> ((define LINELENGTH 80 ((define SEPCHARS " . ,?\"\n" int main (void) { char line [LINELENGTH]; char ‘word; while (1) { printf ("ХпСледующая строка? (выход — ввод пустой строки) \п") ; fgets (line, LINELENGTH, stdin); if (strlen(line) <= 1) break; /* выход иэ программы */ printf ("Данная строка состоит иэ следующих слов:\п"); word = strtok(line, SEPCHARS); /* поиск первого слова */ while (word != NULL) { printf (”\"%s\"\n" ,word);
Обработка строк 367 word = strtok(NULL, SEPCHARS); /* поиск следующего слова */ I I } Пример работы с программой: Следующая строка? (выход - ввод пустой строки) "Господи", - сказала она, - "неужели это правда?" Данная строка состоит из следующих слов: "Господи" "сказала" "она" "неужели" "это" "правда" Следующая строка? (выход - ввод пустой строки) 13.8 strtod, strtof, strtold, strtol, strtoll, strtoul, strtoull См. раздел 16.4. 13.9 atof, atoi, atol, atoll См. раздел 16.3. 13.10 strcoll, strxfrm, wcscoll, wcsxfrm Краткий перечень #include <string.h> int strcoll( const char *sl, const char *s2); size_t strxfrm( char *dest, const char *src, size t len ); ttinclude <wchar.h> int wcscoll(const wchar_t *sl, const wchar_t *s2) ; size_t wcsxfrm( wchar t *dest, const wchar t *src, size t len); Функции strcoll и strxfrm предназначены для сортировки строк в зависи- мости от географической установки. Функция strcoll сравнивает строки si и s2, возвращая положительное, нулевое или отрицательное значение, в зави- симости от того, оказывается ли строка si, соответственно, больше, равной или меньше строки s2. Сравнение проводится в соответствии с сортировочны- ми соглашениями, которые определяются географической установкой (LC_COLLATE, устанавливаемой функцией setlocale, раздел 20.1). В отличие от этого, функции strcmp и wcscmp (раздел 13.2) всегда сравнивают строки по обычной схеме сортировки символьного набора (char или wchar_t).
368 Глава 13 Функция wcscoll (Дополнение 1 к С89) аналогична strcoll, за исключением типов аргументов. Функция strxfrm преобразует (как описано ниже) строку src в другую стро- ку, записываемую в массив символов dest, длина которого в символах должна быть не меньше 1еп. Функция возвращает длину записываемой строки (без учета нулевого символа в конце). Если значение, возвращаемое функцией strxfrm, оказывается не меньше, чем 1еп, или же src и dest используют общую память, содержимое dest оказывается неопределенным. Если же значение 1еп равно 0, a dest — нулевой указатель, strxfrm ограничивается вычислением длины преобразованной строки, соответственно длине src, и возвращает это значение. Функция strxfrm преобразует строки таким образом, чтобы можно было воспользоваться функцией strcmp для определения порядка сортировки. То есть если даны строки si и s2, a tl и t2 — строки, полученные преобразовани- ем функцией strxfrm, соответственно, строк si и s2, тогда • strcmp(tl,t2) > 0 если strcoll(si,s2) > О • strcmp(tl,t2) == 0 если strcoll(si,s2) == 0 • strcmp(tl,t2) < 0 если strcoll(si,s2) < 0 Функция wcsxfrm (Дополнение 1 к C89) аналогична strxfrm, за исключени- ем типов аргументов. Для сравнения преобразованных строк широких симво- лов необходимо применять функцию wcscmp. Каждая из функций strcoll и strxfrm обладает определенными преимуще- ствами. Первая не требует распределения ей дополнительной памяти, но ей иногда приходится при каждом вызове выполнять строковые преобразования. Вторая функция может оказаться более быстрой при многократном примене- нии сравнения к одному набору строк. Пример В следующей функции функция strxfrm используется для создания преобразован- ной строки, соответствующей аргументу s. Область памяти для строки распределя- ется динамически. ftinclude <string.h> ftinclude <stdlib.h> char *transform( char *s ) j* Возврат результата применения strxfrm к s */ I char *dest; /* Буфер для размещения преобразованной строки */ size_t length; /* Требуемая длина буфера */ length = strxfrm(NULL,s,0) + 1; dest = (char *) malloc(length); strxfrm(dest, s,length); return dest; I
Глава 14 Функции управления памятью Функции, рассматриваемые в этой главе, применяются для копирования, сравнения и распределения блоков памяти. В Standard С эти функции относят- ся к строковым, а потому объявлены в библиотечном заголовочном файле string.h. В более ранних реализациях они имеют собственный заголовочный файл memory.h. Блоки памяти обозначаются указателями, которые в Standard С имеют тип void *, в традиционном — char *. В Standard С память интерпретируется как массив объектов типа unsigned char; в традиционном С это не определено яв- ным образом, поэтому можно использовать тип char либо unsigned char. Функции управления памятью трактуют нулевой символ как любой другой. В Дополнении 1 к С89 определены пять дополнительных функций для ма- нипуляций с массивами широких символов, обозначаемых указателями типа wchar_t *. Объявления этих функций содержатся в заголовочном файле wchar.h, их имена начинаются на wmem. Упорядочение широких символов происходит по целым значениям, которыми кодируются символы типа wchar_t. Интерпретация широких символов не входит в задачи функций управления памятью, поэтому ошибки кодирования не возникают. Ссылки: wchar_t 11.1; широкий символ 2.1.4. 14.1 memchr, wmemchr Краткий перечень #include <string.h> void *memchr( const void *ptr, int val, size_t len ); #include <wchar.h> wchar t *wmemchr( const wchar t *ptr, wchar t val, size t len ); Функция memchr выполняет поиск первого вхождения значения val в после- довательности из len символов, начинающейся с символа, на который ссылает- ся указатель ptr. Функция возвращает указатель на первый символ с кодом val или нулевой указатель, если такой символ отсутствует. Сравнение очередного символа с со значением val происходит как в выражении (unsigned char) с ==
370 Глава 14 (unsigned char) val. См. также функцию strchr (раздел 13.5). Возвращаемое значение объявлено как указатель на объект, не являющийся константой, одна- ко это будет именно константа, если на таковую указывает первый аргумент. Функция wmemchr (Дополнение 1 к С89) выполняет поиск первого вхождения значения val в последовательности символов длиной len, начинающейся с симво- ла, на который ссылается указатель ptr. Функция возвращает указатель на пер- вый символ с кодом val или нулевой указатель, если такой символ отсутствует. В традиционном С memchr имеет следующую сигнатуру: #include <memory.h> char *memchr(char *ptr, int val, int len ); 14.2 memcmp, wmemcmp Кроткий перечень tfinclude <string.h> int memcmp( const void *ptrl, const void *ptr2, size t len ) ; #include <wchar.h> int wmemcmp( const wchar t *ptrl, const wchar t *ptr2, size t len ); Функция memcmp сравнивает первые len символов, начиная с символа, на который ссылается указатель ptrl, с первыми len символами, начиная с сим- вола, на который ссылается указатель ptr2. Если первая строка оказывается лексикографически меньше второй, функция memcmp возвращает отрица- тельное целое значение, если больше — положительное, если обе строки рав- ны — нуль. См. также описание strcmp (раздел 13.2). Функция wmemcmp (Дополнение 1 к С89) выполняет аналогичное сравне- ние строк широких символов. Упорядочение широких символов происходит по целым значениям их кодов. Функция возвращает отрицательное, нулевое или положительное значение, в зависимости от того, оказывается ли строка, определяемая указателем ptrl, соответственно, меньше, равной или больше строки, определяемой указателем ptr2. В некоторых из прежних реализаций С существует функция bcmp, которая также предназначена для сравнения двух строк, но возвращает нуль, если эти строки равны, и ненулевое значение — если не равны. Функция не определяет, какая из строк больше или меньше другой. Сигнатура функций bcmp и memcmp: #include <memory.h> int bcmp( char *ptrl, char *ptr2, int len ) ; int memcmp( char *ptrl, char *ptr2, int len ); 14.3 memcpy, memccpy, memmove, wmemcpy, wmemmove Краткий перечень #include <string.h> void *memcpy (void *dest, const void *src, size_t len); void *memmove(void *dest, const void *src, size t len);
Функции управления памятью 371 Краткий перечень ffinclude <wchar.h> wchar_t ‘wmemcpy(wchar_t *dest, const wchar_t ‘src, size_t len); wchar t ‘wnemmove(wchar t *dest, const wchar t ‘src, size t len); Функции memcpy и memmove (Standard С) копируют len символов из src в dest и возвращают dest. Отличие memmove состоит в том, что она может ра- ботать со строками, использующими общую память — то есть она действует так, как если бы исходная строка сначала копировалась во временную область памяти, и уже после этого — по конечному адресу (впрочем, функция обходит- ся без создания временной области). Что касается функции memcpy, то в слу- чае, если исходная и конечная строки перекрывают друг друга, результат ее применения оказывается непредсказуем. Правда, существуют версии memcpy, в которых реализована семантика создания временной копии. При наличии обеих версий, следует учитывать, что традиционная, по всей вероятности, ра- ботает быстрее. См. также описание функции strcpy (раздел 13.3). Функции wmemcpy и wmemmove (Дополнение 1 к С89) аналогичны, соот- ветственно, memcpy и memmove, но применяются к массивам широких симво- лов. Обе возвращают строку dest. В некоторых из прежних реализаций С есть, кроме memcpy, функции memccpy и bcopy. memccpy также копирует len символов из src в dest, но пре- кращает процесс после копирования символа со значением val. Если копиро- ванными оказываются все len символов, функция возвращает нулевой указа- тель, иначе — указатель на символ в dest, следующий непосредственно за сим- волом со значением val. Функция Ьсору действует таким же образом, но аргументы исходной и конечной строк указываются в ней в обратном порядке. Сигнатуры этих функций в традиционном С: #include <memory.h> char ‘memcpy( char ‘dest, char ‘src, int len ); char ‘memccpy(char ‘dest, char ‘src, int val, int len); char *bcopy( char ‘src, char ‘dest, int len ); 14.4 memset, wmemset Краткий перечень ffinclude <string.h> void ‘memset( void *ptr, int val, size t len ); ffinclude <wchar.h> wchar t ‘wmemset( wchar t *ptr, int val, size t len ); Функция memset копирует значение val в каждый из len символов, начи- ная с символа, на который ссылается указатель ptr. Предполагается, что сим- волы, адресуемые этим указателем, имеют тип unsigned char. Функция воз- вращает значение ptr. Функция wmemset (Дополнение 1 к С89) аналогична memset, но заполняет массив широких символов.
372 Глава 14 В некоторых из прежних реализаций С существует функция bzero, запол- няющая 1еп символов, начиная с символа, на который ссылается указатель ptr, значением 0. Сигнатуры указанных функций в традиционном С: #include <memory.h> char *memset( char *ptr, int val, int len ); void bzero( char *ptr, int len );
Глава 15 Средства ввода/вывода Язык С обладает обширным набором средств ввода/вывода, в основе кото- рых лежит концепция потока данных. Поток (stream) — это файл или иной объект ввода/вывода, включая терминал и любое иное физическое устройство. Сведения о потоках хранятся в данных типа FILE (определенном, наряду с другими средствами ввода/вывода, в stdio.h). Объект типа FILE создается вызовом функции fopen, возвращающей указатель на созданный объект (ука- затель файлового типа). Указатели этого типа (file pointer) используются в качестве аргументов в большинстве функций ввода/вывода, рассматривае- мых в этой главе. Информация, содержащаяся в объекте типа FILE, включает текущую пози- цию в потоке (указатель позиции в файле), указатели на буферы, используемые потоком, а также данные о том, произошла ли ошибка, и достигнут ли конец файла. Как правило, потоки, если они не связаны с интерактивными устройст- вами, буферизируются. Программист имеет некоторую возможность управлять буферизацией при помощи функции setvbuf, однако потоки реализованы доста- точно основательно, так что надобности заниматься их тонкой настройкой, как правило, не возникает. Есть два основных вида потоков: текстовые и двоичные. Текстовый поток состоит из последовательности текстовых символов, разбитых на строки. Каж- дая строка состоит из некоторого числа (возможно, нуля) символов, за которы- ми следует символ новой строки ’\п', принадлежащий этой же строке. Тексто- вые потоки мобильны (переносимы в другие реализации или на другие плат- формы) только в том случае, если их строки полностью состоят из символов стандартного символьного набора. В отдельных аппаратных и программных составляющих, используемых в определенной реализации некоторой библио- теки функций С, представление текстовых файлов может быть различным (особенно это касается обозначения конца строки), но при вызове библиотеч- ных функций любые представления должны отображаться в стандартное. Standard С требует поддержки текстовых потоков со строками минимум до 254 символов, включая символ новой строки в конце. Двоичные потоки — это последовательности значений данных типа char. По- скольку в С любые данные можно отобразить на массив значений типа char, дво- ичные потоки могут прозрачно записывать внутренние данные. Любая реализация допускает игнорирование различий между текстовыми и двоичными потоками. При запуске программы С автоматически открываются три стандартных тес- товых потока: стандартное устройство ввода (stdin), стандартное устройст- во вывода (stdout), и стандартное устройство сообщений об ошибках (stderr).
374 Глава 15 Ссылки: fopen 15.2; setvbuf 15.3; стандартный символьный набор 2.1. Ввод и вывод широких символов В Дополнении 1 к С89 определены средства ввода/вывода широких симво- лов. Новые функции ввода/вывода широких символов, объявленные в заголо- вочном файле wchar.h, полностью идентичны своим двойникам, применяе- мым для однобайтовых символов, за исключением того, что работают с данны- ми другого типа (wchar_t вместо char). По существу, функции ввода/вывода широких символов можно было бы применять для преобразования многобай- товых последовательностей в однобайтовые и обратно, если бы этот процесс не был прозрачным (незаметным) для программиста. Дополнение 1 не определяет новый тип потока для широких символов, а лишь ориентацию на существующие текстовые и двоичные потоки. После открытия потока, но до того, как качнется выполнение каких-либо операций ввода/вывода, поток никак не ориентирован. Ориентация потока на ввод/вывод однобайтовых либо широких символов происходит вызванной функцией ввода/вывода — в за- висимости от того, будет это функция ввода/вывода однобайтовых либо широких символов. После ориентирования потока, он не может быть переориентирован на ввод/вывод символов другого типа — попытка такой переориентации даст не- предсказуемый результат. Для установки либо проверки ориентации потока можно воспользоваться функцией fwide (раздел 15.2). Если внешнее представление файла — последовательность многобайтовых символов, для такого файла смягчаются некоторые условия: 1. Кодирование многобайтовых символов может включать встроенные ну- левые символы. 2. Файлы не должны начинаться и заканчиваться в начальном состоянии преобразования. Кодировка многобайтовых символов в разных файлах может быть различ- ной. Кодировка файла, определяемая логически, внутренним состоянием пре- образования, задается категорией LC_CTYPE географической установки сразу после установки состояния преобразования и не позднее вызова первой функ- ции ввода/вывода широких символов. После задания состояния преобразова- ния (и правила кодирования), установка LC_CTYPE больше не оказывает влияния на процессы преобразования в соответствующем потоке. Поскольку преобразованию широких символов в многобайтовые может быть сопоставлено некоторое состояние, каждому потоку, ориентированному на ввод/вывод широких символов, сопоставляется скрытый объект mbstate_t. Преобразование в процессе ввода/вывода концептуально происходит в резуль- тате вызова функции mbrtowc или wcrtomb, с использованием скрытого со- стояния преобразования. Функции fgetpos и fsetpos должны записывать это состояние преобразования с позицией файлового указателя. Преобразование в процессе ввода/вывода широких символов может закончиться ошибкой ко- дирования; в этом случае, в переменную еггпо записывается значение EILSEQ. Если разрешена множественная кодировка файлов, кодировка пото- ка, по всей вероятности, будет входить составной частью в объект mbstate_t или, по крайней мере, записываться в него. Ссылки: состояние преобразования 2.1 5; EILSEQ ! 1.2, fgetpos и fsetpos 15.5, mbrtowc 1 1.7; mbstcite_t ПЛ; многобайтовый символ 2.1.5; ориентация 15 2.2; wcrtomb 1 1.7; широ- кие символы 2.1.5.
Средства ввода/вывода 375 15.1 FILE, EOF, wchart, wint_t, WEOF Краткий перечень ffinclude <stdio.h> typedef ... FILE ...; ffdefine EOF (-n) ffdefine NULL ... ffdefine size t ... ffinclude <wchar.h> typedef ... wchar_t; typedef ... wint_t; ffdefine WEOF .. . ffdefine WCHAR_MAX ... ffdefine WCHAR_MIM ... ffdefine NULL ... ffdefine size t ... Тип FILE используется повсеместно в стандартной библиотеке ввода/выво- да для хранения информации управления потоками. Он используется для счи- тывания информации из файлов, ориентированных на ввод/вывод как одно- байтовых, так и широких символов. Значение EOF принято использовать в качестве сигнала достижения конца файла (end of file) — то есть условия, когда считаны все данные. В большинст- ве традиционных реализаций значение EOF равно -1, однако в Standard С это может быть любое отрицательное константное выражение. Значение EOF ино- гда используется не только для обозначения конца файла, поэтому факт дости- жения конца файла следует перепроверять вызовом функции feof (раздел 15.14). Макрос WEOF (Дополнение 1) используется при вводе/выводе широ- ких символов с той же целью, что значение EOF в однобайтовом вводе/выводе. Он возвращает значение типа wint_t (не обязательно wchar_t), и это значение может быть неотрицательным. Верхняя граница интервала значений типа wchar_t обозначается значением WCHAR_MAX, нижняя — значением WCHAR_MIN. Тип size_t и константа NULL нулевого указателя объявлены для удобства в двух заголовочных файлах stdio.h и wchar.h. В Standard С они объявлены также в файле stddef.h, что, впрочем, не создает никаких проблем. Ссылки: wchar_t 2.1.5, 11.1; wint_t 2.1.5, 11.1. 15.2 fopen, fclose, fflush, freopen, fwide Краткий перечень ffinclude <stdio.h> FILE *fopen(const char * restrict filename, const char * restrict mode); int fclose(FILE * restrict stream); int fflush(FILE * restrict stream);
376 Глава 15 Краткий перечень FILE *freopen( const char * restrict filename, const char ★ restrict mode, FILE * restrict stream); #define FOPEN_MAX ... #define FILENAME MAX ... #include <wchar.t> int fwide(FILE * restrict stream, int orient); Функция fopen вызывается с двумя строковыми аргументами, указываю- щими имя файла и режим. Аргумент имени файла сопоставляется создаваемо- му потоку и подчиняется определенным условиям, зависящим от реализации (максимальная длина имени файла определяется значением, возвращаемым макросом FILENAME_MAX, если он определен). Функция возвращает указа- тель типа FILE *, ссылающийся на созданный поток и использующийся в по- следующих операциях ввода/вывода. Если при выполнении функции fopen возникает ошибка, ее код записывается в переменную errno, а функция воз- вращает нулевой указатель. Максимальное число одновременно открытых по- токов не указывается; в Standard С оно определяется значением, возвращае- мым макросом FOPEN_MAX и не должно быть меньше восьми (включая три стандартных потока). Согласно Дополнению 1 к С89, поток, созданный функ- цией fopen, никак не ориентирован и готов к вводу/выводу как однобайтовых, так и широких символов (но не тех и других одновременно). Функция fclose закрывает должным образом открытый поток и очищает все внутренние буферы данных. Если при выполнении функции происходит ошибка, она возвращает значение EOF, иначе — нуль. Пример Ниже приведены примеры открытия и закрытия обычных текстовых файлов. Пре- дусмотрены обработка ошибок и печать диагностики; возвращаемые .значения — та- кие же, как у функций fopen и fclose. #include <errno.h> ^include <stdlo.h> FILE *open_input(const char *filename) /* Открытие файла filename; в случае ошибки, возврат NULL */ ( FILE *f; errno = 0, /* Значение NULL аргумента filename недопустимо для следующей функции. */ if (filename == NULL) filename = "\0"; f = fopen(filenamer"), /* "w“ для open^output */ if (f == NULL) fprintf(stderr, ’*open_input (\” %s\" ) с ошибкой: %s\n*’, filename , strerror (errno)) ; return f; } int closa_file(FILE *f) /* Закрыть файл f */ { int s 0;
Средства ввода/вывода 377 if (f = NULL) return 0; /* Игнорировать это условие */ еггпо = 0; s = fclose(f); if (s == EOF) perror("Закрытие завершилось ошибкой"); return s; } Функция fflush очищает все буферы, связанные с данным выводом, или об- новляет потоковый аргумент. Поток остается открытым. Если возникает ошибка, fflush возвращает EOF, иначе — нуль. Как правило, функция fflush используется только в исключительных обстоятельствах; обычно буферы очи- щаются функциями fclose и exit. Функции freopen указываются в качестве параметров имя файла, режим и указатель открытого потока. Функция сначала пытается закрыть поток, как по вызову функции fclose, но, игнорируя любую ошибку, которая может воз- никнуть. Затем функция вновь открывает файл, используя указанные имя и режим, как это можно было бы сделать вызовом функции fopen, сопоставив новому потоку аргумент stream. Функция возвращает новое значение аргу- мента stream или (при наличии ошибки) нулевой указатель. Одно из основных применений функции freopen — переадресация стандартных потоков вво- да/вывода stdin, stdout и stderr в другой файл. Согласно Дополнению 1 к С89, freopen отменяет ранее установленную ориентацию потока. Ссылки: EOF 15.1; exit 19.3; stdin 15.4. 15.2.1 Файловые режимы Значения, перечисленные в таблице 15.1, могут использоваться для указа- ния файлового режима при вызове функций fopen и freopen. Таблица *15.1. Файловые режимы для функций fopen и freopen Режим’ Описание "г" Открытие существующего фойпо для чтения донных. ”w" Открытие нового файло для записи донных. "а" Создание нового фойпо либо открытие существующего — для зописи данных в конец. "г+” Открытие существующего фойло для записи и считывания донных (от его начало). "w+" Создание нового фойпо или усечение, для модификации, существующего. "а+" Создание нового файла или запись в конец существующего. 1 К любому значению режима можно добовить букву Ь, укозов тем сомым, что поток предназначен для ввода/выводо не символьных, а двоичных донных. Если файл открывается для модификации (в строке режима указан знак +), устанавливаемый поток может использоваться как для ввода, так и для выво- да. Однако операция вывода не может следовать непосредственно за операцией ввода без предварительного вызова функций fsetpos, fseek, rewind или fflush; аналогично, операция ввода не может следовать непосредственно за операцией вывода без предварительного вызова этих же функций или выполнения опера- ции ввода, в результате которой был достигнут конец файла (все эти операции приводят к очистке внутренних буферов).
378 Глава 15 В Standard С допускается добавление к любой из строк режима, перечис- ленных в таблице 15.1, символа Ь, указывающего на использование двоичного (не текстового) потока. В UNIX это различие не существенно, поскольку здесь файлы обоих типов обрабатываются одинаково; в других системах ситуация менее благоприятна. Кроме этого, в Standard С возможно открытие файлов для модификации в двоичном режиме; символ Ь в строке режима может быть указан до или после знака +. В Standard С строка режима может содержать некоторые другие символы, в дополнение к перечисленным выше. Эти дополнительные символы могут ис- пользоваться в некоторых реализациях для указания других атрибутов пото- ков, например: f = fopen("С:\\work\\dict.txt","г,access=lock"); В таблице 15.2 перечислены некоторые свойства каждого из потоковых режимов. Таблица *15.2. Свойства файловых режимов Свойство Режим Г W о г+ W+ о+ Фойл с указанным именем должен уже существовать да нет нет да нет нет Содержимое существующего фойла не сохраняется нет да нет нет до нет Разрешено считывание из потока до нет нет ДО ДО да Разрешена запись в поток нет до До да да да Запись производится в конец потока нет нет до нет нет до 15.2.2 Ориентация файла Функция fwide (Дополнение 1 к С89) применяется для проверки и (или) ус- тановки ориентации потока. Она возвращает положительное, отрицательное или нулевое значение в зависимости от того, будет ли поток после ее вызова ориентирован, соответственно, на ввод/вывод широких символов, однобайто- вых символов либо никак не ориентирован. Аргумент orient определяет, по- пытается ли fwide ориентировать поток. Если orient равен 0, попытка ориен- тирования не предпринимается и возвращаемое значение соответствует ориен- тации в момент вызова функции. При положительном orient, fwide пытается установить ориентацию на ввод/вывод широких символов, при отрицатель- ном — однобайтовых. Попытки могут быть удачными только в отношении по- тока, который пока не ориентирован — то есть только что открыт функцией fopen или freopen; в остальных случаев ориентация остается прежней. Пример Если необходим ввод/вывод широких символов, следует ориентировать поток при помощи функции fwide сразу после его открытия функцией fopen. Ниже приведен пример функции, которая открывает файл в нужном режиме и ориентирует на ввод/вывод широких символов соответственно географической установке. При ус- пешном выполнении, функция возвращает указатель на поток, иначе — нулевой указатель.
Средства ввода/вывода 379 FILE *fopen_wide( const char *filename, /* открываемый файл */ const char *mode, /* режим */ const char *locale)/* географический параметр для выбора кодировки */ { FILE *f = fopen(filename, mode); if (f '= NULL) ( char *old_locale = setlocale(LC_CTYPE, locale); if (old_locale — NULL || fwide(f, 1) <= 0) { fclose(f); /* ошибка в setlocale или fwide */ f = NULL; ) /* возврат географического параметра к исходному значению */ setlocale(LC_CTYPE, old_locale); ) return f; Многобайтовая кодировка определяется при установке ориентации потока. Она зависит от категории LC_CTYPE текущей географической установки в мо- мент определения ориентации. 15.3 setbuf, setvbuf Краткий перечень #include <stdio.h> int setvbuf( FILE * restrict stream, char *b restrict uf, int bufmode, size_t size ); void setbuf( FILE * restrict stream, char * restrict buf ); #define BUFSIZ ... #define _IOFBF ... #define _IOLBF ... tfdefine IONBF .. . Указанные функции позволяют программисту определять тактику буфери- зации потоков в тех редких случаях, когда стандартной буферизации оказыва- ется недостаточно. Они вызываются сразу после открытия потока, но до запи- си или считывания каких бы то ни было данных. setvbuf — функция более общего назначения, взятая из UNIX System V. Ее первый аргумент — это поток, который предстоит регулировать, второй (если это не нулевой указатель) — символьный массив, которым должен быть заме- нен стандартный буфер. Аргумент bufmode указывает тип буферизации, size — объем буфера. Функция возвращает нуль в случае успешного выполнения, не- нулевое значение — в случае неправильных аргументов или иной ошибки. Расширения макросов _IOFBF, _IOLBF и _IONBF дают значения, которые можно использовать в качестве аргумента bufmode. Если bufmode равен _ЮРВР, поток полностью буферизируется, если _IOLBF — буфер очищается при каждой записи символа новой строки или заполнении буфера, если
380 Глава 15 _IONBF — поток не буферизирует данных. При запросе буфера, если buf — не нулевой указатель, массив, на который указывает этот аргумент, должен иметь длину size байт и будет использоваться вместо стандартных буферов. Константа BUFSIZ — это предпочтительный размер буфера. Функция setbuf — упрощенный вариант setvbuf. Выражение setbuf(stream, buf) эквивалентно следующему: ((buf==NULL) ? (void) setvbuf(stream,NULL,_IONBF,0) (void) setvbuf(stream,buf,_IOFBF,BUPSIZ)) Ссылки: EOF 15.1; fopen 15.2; sizet 111 15.4 stdin, stdout, stderr Краткий перечень #include <stdio.h> ((define stderr . . . #define stdin . . . ((define stdout . . . Выражения stdin, stdout и stderr имеют тип FILE *, их значения устанав- ливаются до запуска программы, как указатели на некоторые стандартные текстовые потоки, stdin указывает на входной поток, используемый как «обычное» средство ввода, stdout — на выходной поток («обычное» средство вывода), stderr — на выходной поток для сообщений об ошибках и других не- предвиденных сообщений программы. В интерактивной среде все три потока обычно сопоставляются терминалу, с которого программа запускается. Все по- токи, кроме stderr, буферизируются. Указанные выражения обычно не являются (-значениями и в любом случае не должны изменяться операторами присваивания. Для их модификации можно использовать функцию freopen (раздел 15.2). Пример Выражения stdin, stdout и stderr часто определяются как адреса статических или глобальных дескрипторов потоков: extern FILE _iob[FOPEN_MAX]; ((define stdin (4 _iob[0]) ((define stdout (4_iob[l]) ((define stderr (4_iob[2]) В системах UNIX определены удобные средства сопоставления этих стан- дартных потоков файлам или другим программам при запуске приложения, что в сочетании с некоторыми стандартными соглашениями обеспечивает до- вольно богатые возможности. Согласно Дополнению 1 к С89, stdin, stdout и stderr никак не ориентирова- ны во время запуска программы С. Следовательно, эти потоки можно исполь- зовать для ввода/вывода широких символов, воспользовавшись функцией fwide (раздел 15.2) либо иной функцией ввода/вывода широких символов.
Средства ввода/вывода 381 15.5 fseek, ftell, rewind, fgetpos, fsetpos Краткий перечень ffinclude <stdio.h> int fseek(FILE * restrict stream, long int offset, int wherefrom); long int ftell(FILE * restrict stream); void rewind(FILE * restrict stream) ; ffdefine SEEK_SET 0 ffdefine SEEK_CUR 1 ffdefine SEEK_END 2 typedef ... fpos_t . . . ; int fgetpos( FILE * restrict stream, fpos_t *pos ); int fsetpos( FILE * restrict stream, const fpos t *pos ); Функции, рассматриваемые в этом разделе, обеспечивают произвольный доступ в текстовые и двоичные потоки — как правило, связанные с файлами. 15.5.1 fseek и ftell Функция ftell возвращает текущую позицию в потоке, открытом для ввода и вывода, в форме значения, пригодного для использования в качестве второго аргумента функции fseek. Вызов функции fseek с этим аргументом приводит к установке позиции потока соответственно позиции файла, при которой про- изошел вызов функции ftell. В случае двоичного файла значение, возвращаемое функцией, равно числу символов, предшествующих текущей файловой позиции, в случае текстового, оно зависит от реализации. Возвращаемое значение должно быть пригодно для использования в качестве аргумента функции fseek, значение 0L должно представлять (не обязательно единственным образом) начало файла. Если при выполнении функции ftell происходит ошибка, возвращается значение —1L, а в переменную еггпо записывается положительное значение, зависящее от реализации. Поскольку —1L может оказаться значением реаль- ной файловой позиции, необходимо убедиться в наличии ошибки, сверившись с переменной еггпо. Среди условий, которые могут вызвать ошибку при вы- полнении функции ftell — попытка определения позиции в потоке, сопостав- ленном терминалу, либо позиции, которую нельзя представить в виде объекта типа long int. Функция fseek обеспечивает произвольный доступ в (открытый) поток. По- следняя пара ее аргументов определяет файловую позицию: offset — (длинное) целое со знаком, — смещение в символах от некоторой позиции, wherefrom — точку, от которой это смещение исчисляется. Позиция в потоке определяется, как показано ниже, а функция fseek возвращает нуль при успешном выполне- нии и ненулевое значение — в случае ошибки. Значение переменной еггпо не меняется. Любой индикатор конца файла очищается, любые последствия при- менения функции ungetc отменяются. В Standard С определены константы SEEK_ SET, SEEK_CUR и SEEK_END, представляющие значения для аргу- мента wherefrom. Программистам, работающим с другими реализациями С, следует использовать соответствующие значения либо определить эти макросы.
382 Глава 15 При изменении позиции указателя двоичного файла, новая позиция опре- деляется по следующей таблице: Если wherefrom равен Новая позиция: SEEK.SET или 0 SEEKCUR или 1 SEEKEND или 2 offset символов от начала фойло offset символов от текущей позиции offset символов от конца фойло (отрицательные значения соответствуют позициям внутри файпо, положительные удлиняют файл без определения содержимого) Standard С не требует поддержки значений SEEK_END для двоичных пото- ков. Для текстовых же потоков здесь определен следующий, более ограничен- ный, набор вариантов вызова: Ворионт вызово fseek(stream, OL, SEEK SET) fseek(stream, OL, SEEK_CUR) fseek(stream, OL, SEEK_END) fseek(stream, ftell-pos, SEEK SET) Позиция в (текстовом) потоке В ночоле файло В том же месте (то есть никакого воздействия функции) В конце файпо В позиции, возвращенной функцией ftell для данного потока Эти ограничения установлены с учетом возможного несоответствия пози- ции внутри текстового файла его же внутреннему представлению. Например, может потребоваться позиционирование по номеру записи и смещению внутри нее. Тем не менее, Standard С требует поддержки определения позиции в тек- стовых файлах при помощи функции fseek(stream, OL, SEEK_END), не тре- буя, однако, «осмысленной» аналогичной поддержки для двоичных потоков. Согласно Дополнению 1 к С89, операции позиционирования в файлах при ориентации потока на ввод/вывод широких символов должны выполняться при соблюдении всех ограничений, применимых как к текстовым, так и к дво- ичным файлам. Функции fseek и ftell, в целом, не обладают достаточными возможностями для поддержки потоков, ориентированных на ввод/вывод ши- роких символов, даже в отношении простейших операций позиционирования наподобие определения начала или конца потока. Для этих потоков следует использовать функции fgetpos и fsetpos, рассматриваемые в следующем раз- деле. Функция rewind возвращает указатель потока к его началу. Согласно опре- делению Standard С, вызов функции rewind(stream) эквивалентен следующему: (void) fseek(stream, OL, SEEK_SET) 15.5.2 fgetpos и fsetpos Функции fgetpos и fsetpos — новые для Standard С. Они предназначены для работы с большими файлами, в которых позиция не может быть представ- лена обычным значением целого типа long int (как в случае ftell или fseek). Функция fgetpos записывает значение текущей файловой позиции в объек- те, на который ссылается указатель pos. В случае успешного выполнения, она возвращает нуль, иначе — ненулевое значение с записью кода ошибки (поло- жительное значение, зависящее от реализации) в переменную errno.
Средства ввода/вывода 383 Функция fsetpos устанавливает файловый указатель в позицию, соответст- вующую значению, на которое ссылается указатель *pos, и которое должно быть равно значению, возвращенному ранее функцией fgetpos для этого же потока, fsetpos отменяет любое воздействие функций ungetc и ungetwc. При успешном выполнении, она возвращает нуль, в случае ошибки — ненулевое значение с записью кода ошибки, зависящего от реализации, в переменную еггпо. Согласно Дополнению 1 к С89, объект файловой позиции, используемый функциями fgetpos и fsetpos, должен будет включать представление скрытого состояния преобразования в потоке, ориентированном на ввод/вывод широких символов (то есть значение типа mbstate_t). Это состояние, в сочетании с пози- цией в файле, необходимо для интерпретации следующих многобайтовых сим- волов после операции позиционирования. Если использовать функцию fsetpos в выходном потоке, ориентированном на вывод широких символов, для установки позиции, а затем записать в этот поток несколько широких символов, все последующие символы в этом потоке окажутся неопределенными — ввиду возможной частичной записи поверх су- ществовавших символов или изменения состояния преобразования таким об- разом, что следующие многобайтовые символы невозможно интерпретировать. Ссылки: mbstate_t 11.1; ungetc 15.6. 15.6 fgetc, fgetwc, getc, getwc, getchar, getwchar, ungetc, ungetwc Кроткий перечень ffinclude <stdlo.h> int fgetc(FILE *stream) ; int getc(FILE ‘stream) ; int getchar(void); int ungetc(int c, FILE ‘stream); ffinclude <stdio.h> ffinclude <wchar.h> wint_t fgetwc(FILE ‘stream); wint_t getwc(FILE ‘stream); wint_t getwchar(void); wint t ungetwc(wint t c, FILE ‘stream); Функция fgetc вызывается с входным потоком в качестве аргумента. Она считывает очередной символ в потоке и возвращает его в форме значения типа int. Соответственно, сдвигается внутренний указатель позиции в потоке. Се- рия вызовов функции приводит к последовательному считыванию символов в потоке. В случае ошибки или достижения конца файла, fgetc возвращает EOF. Для подтверждения фактического состояния ошибки или достижения конца файла, следует обратиться к функции feof или ferror. Функция getc идентична fgetc, но для большей эффективности часто реа- лизуется как макрос. Аргумент stream не должен давать никаких побочных эффектов, поскольку допускает многократную оценку.
384 Глава 15 Функция getchar эквивалентна getc(stdin). Подобно getc, getchar часто реализуется в форме макроса. В Дополнении 1 к С89 функции fgetwc, getwc и getwchar аналогичны сво- им однобайтовым двойникам, включая возможные реализации в форме макро- сов, но считывают из потока и возвращают широкие символы. При достиже- нии конца файла эти функции возвращают значение WEOF, в случае ошибки, кроме этого, записывают в переменную еггпо значение EILSEQ. При считыва- нии широких символов происходит преобразование из формата многобайто- вых символов в формат широких, аналогично вызову функции mbrtowc, ис- пользующей внутреннее состояние преобразования в потоке. Функция ungetc проталкивает символ с (преобразованный в unsigned char) в указанном входном потоке назад, так чтобы он мог быть возвращен в резуль- тате вызова функции fgetc, getc или getchar. Если проталкиваются несколько символов, они возвращаются в порядке, обратном проталкиванию (проталки- ваемый последним возвращается первым). Если проталкивание прошло ус- пешно, ungetc возвращает символ с, иначе — EOF. Выполнение операции по- зиционирования в потоке (функции fseek, fsetpos или rewind) приводит к уда- лению протолкнутых символов. После чтения (или удаления) всех протолкнутых символов позиция файлового указателя становится такой, ка- кой была перед проталкиванием. Гарантируется проталкивание минимум одного символа при условии буфе- ризации потока и считывания из потока минимум одного символа с момента последнего выполнения в нем операции fseek, fopen или freopen. Попытка проталкивания значения EOF не дает никакого результата, функция возвра- щает EOF. Вызов функций fsetpos, rewind, fseek и freopen удаляет из потока все протолкнутые символы, не затрагивая внешней памяти, сопоставленной потоку. Функция ungetc удобна для реализации операций сканирования ввода на- подобие scanf. Программа может «заглядывать вперед» на следующий вход- ной символ, считывая его и оставляя на месте, если не подходит. Однако scanf, как и другие библиотечные функции, не может помешать программисту воспользоваться функцией ungetc — то есть даже после вызова, к примеру, scanf, ему доступен минимум один протолкнутый символ. Функция ungetwc (Дополнение 1 к С89) аналогична ungetc. Ссылки: EOF 15.1; feof 15.14, fseek 15.5; fopen 15 2; freopen 1 5 2; scanf 15.8; stdin 15 4. 15.7 fgets, fgetvws, gets Краткий перечень #include <stdio.h> char *fgets(char *s, int n, FILE ‘stream); char ‘gets(char *s); ttinclude <stdio.h> ^include <wchar.h> wchar t ‘fgetws(wchar t ‘s, int n, FILE ‘stream);
Средства ввода/вывода 385 Функция fgets вызывается с тремя аргументами: указателем s на начало символьного массива, целым значением п и указателем на входной поток stream. Символы считываются из входного потока в s до тех пор, пока не будет обнаружен символ новой строки или признак конца файла, или же не будут считаны п— 1 символов. После считывания всех символов, в массив записывает- ся конечный нулевой символ. Если ввод прерывается из-за обнаружения симво- ла новой строки, этот символ записывается в массив перед конечным нулевым символом. При успешном завершении функция возвращает аргумент s. Если признак конца файла обнаруживается до считывания из потока перво- го символа, fgets возвращает нулевой указатель, не меняя содержимого масси- ва s. Если во время операции ввода возникает ошибка, fgets возвращает нуле- вой указатель, а содержимое массива s оказывается неопределенным. В случае возврата функцией нулевого указателя следует проверить, действительно ли достигнут конец файла, воспользовавшись функцией feof (раздел 15.14). Функция gets считывает символы из стандартного входного потока stdin и записывает их в символьный массив s. Однако здесь, в отличие от fgets, когда ввод прерывается обнаружением символа новой строки, этот символ не записы- вается в s. Использование gets чревато опасностью ввиду возможности выхода за пределы отведенного массива символов. Функция fgets в этом отношении бо- лее безопасна, поскольку допускает размещение в s не более п символов. Функция fgetws (Дополнение 1 к С89) аналогична fgets, но применяется к потокам, ориентированным на ввод/вывод широких символов, и именно ши- рокие символы (включая широкий нулевой символ в конце) записывает в s. Функция gets не имеет двойника, применимого к широким символом, и это еще один повод избегать ее применения. Ссылки: feof 15.14; stdin 15.4. 15.8 fscanf, fwscanf, scanf, wscanf, sscanf, swscanf Краткий перечень #include <stdio.h> int fscanf(FILE * restrict stream, const char * restrict format, ...); int scanf(const char * restrict format, ...); int sscanf(char *s, const char * restrict format, ...); #include <stdio.h> #include <wchar.h> int fwscanf( FILE * restrict stream, const wchar_t * restrict format, ...); int wscanf( const wchar_t * format, ...); int swscanf( wchar t *s, const wchar t ‘format, ...); Функция fscanf анализирует форматированный входной текст, считывая символы из потока, указанного первым аргументом, и преобразуя последова- тельность символов соответственно строке форматирования. Могут потребо-
386 Глава 15 ваться также дополнительные аргументы, в зависимости от содержимого стро- ки форматирования. Все аргументы после строки форматирования должны быть указателями; преобразованные значения, считываемые из потока, запи- сываются в объекты, обозначаемые этими указателями. Функции scanf и sscanf подобны fscanf. В случае scanf, символы считыва- ются из стандартного входного потока stdin, в случае sscanf — из строки s. При попытке считывания за концом строки s, эта функция действует так же, как fscanf или scanf при попытке считывания за концом файла. Операция ввода может быть прервана из-за достижения конца файла либо несоответствия символа, считываемого из входного потока, строке форматиро- вания. Указанные функции возвращают число успешных присваиваний (запи- си считанных значений в переменные или распределенную область памяти) до прерывания выполнения по любой причине. Если конец файла при вводе дос- тигается прежде, чем произойдет какой-либо конфликт или присваивание, обе функции возвращают EOF. Если происходит конфликт, символ, который по- служил его причиной, остается непрочитанным до следующей операции ввода. В Дополнении 1 к С89 определен набор функций форматного ввода широ- ких символов, аналогичных функциям fscanf, scanf и sscanf. Семейство функ- ций wscanf работает со строками форматирования и потоками, состоящими из широких символов. Любые преобразования из любых внутренних многобайто- вых последовательностей внешних файлов прозрачны для программиста. Ниже мы рассмотрим однобайтовые функции, описание поведения функций обработки широких символов легко получить заменой «символов» или «бай- тов» «широкими символами», если не указано иное. Кроме этого, в Дополнении 1 определено расширение форматных строк Standard С, разрешающее указывать каждый из аргументов s, с и операцию преобразования [ с одним спецификатором размера, обозначающим, что данный аргумент является указателем на широкий символ или строку широких симво- лов. Подробнее см. в описании соответствующих операций преобразования. 15.8.1 Строка форматирования Строка форматирования — это шаблон требуемой формы входных данных. В Standard С она определена следующим образом: в случае семейства scanf — по- следовательность многобайтовых символов, от начала до конца принадлежащих одному регистру, в случае семейства wscanf — последовательность широких сим- волов. Ипользование строки форматирования, однако, нельзя представить как обычную операцию сравнения строки форматирования с данными потока. Содер- жимое строки форматирования можно подразделить на три категории: 1. Пробельные символы. Пробельный символ в строке форматирования указывает необходимость удаления прочитанных пробельных символов. Таким образом, следующим символом во входной последовательности будет первый символ, не являющийся пробельным. Следует иметь в виду, что последовательность нескольких пробельных символов в стро- ке форматирования воспринимается так же, как один символ. Следова- тельно, любая последовательность пробельных символов в строке форма- тирования соответствует любой последовательности (произвольной дли- ны) пробельных символов во входном потоке. 2. Спецификации преобразования. Спецификация преобразования начи- нается со знака процентов (%), структуру же ее мы подробно рассмот- рим ниже. Число символов, считанных из входного потока, зависит от
Средства ввода/вывода 387 операции преобразования. Как правило, преобразование продолжается до достижения: (а) конца файла; (б) пробельного или любого иного не- подходящего символа; (в) заданного максимального числа считанных символов. Считанные символы, как правило, преобразуются (напри- мер, в численные значения) и записываются в область, обозначенную аргументом-указателем, следующим за строкой форматирования. 3. Другие символы. Любой символ, кроме пробельных и знака процентов, должен совпадать со следующим символом, считываемым из потока. Если совпадения не происходит, возникает конфликт, операция преоб- разования прерывается, символ, послуживший причиной конфликта, остается в потоке для считывания в следующей операции ввода. После строки форматирования необходимо указать нужное число аргумен- тов-указателей. Если этих аргументов будет больше необходимого числа, лиш- ние будут проигнорированы, при их недостаточном их числе результат будет непредсказуем. Ошибка в спецификации преобразования также дает непред- сказуемый результат. Конец последовательности операций, выполненных в соответствии с каждой спецификацией преобразования, помечается специ- альным маркером. 15.8.2 Спецификации преобразований Спецификация преобразования начинается со знака процентов (%), за ко- торым следуют элементы спецификации в следующем порядке: 1. Необязательная отмена присваиваний, обозначаемая звездочкой (*). Операции преобразования, помеченные таким образом, выполняются обычным образом, но считанные символы никуда не записываются, по- этому такой операции не соответствует ни один аргумент-указатель. 2. Необязательный максимальный размер поля, обозначаемый положи- тельным целым в десятичной системе счисления. 3. Необязательный спецификатор размера, обозначаемый одной из сле- дующих последовательностей символов: hh, h, 1 (буква 1), 11 (буква 1-бук- ва 1), j, г, t или L. Операции преобразования, к которым эти специфика- торы могут применяться, перечислены в таблице 15.3. Спецификаторы hh, 11, j, г и t определены впервые в С99. 4. Обязательная операция преобразования (или спецификатор преобразо- вания), обозначаемая (за одним исключением) одним символом: а, с, d, е, f, g, i, n, о, p, s, u, x, % или [. Исключением, как раз, является опе- рация [, задающая включение в спецификацию всех символов до за- крывающей скобки J. Синтаксис и смысл спецификаций преобразования для fscanf и fprintf ана- логичны, но имеют некоторые отличия. Следует считать синтаксис строк фор- матирования для функций fprintf и fscanf схожим лишь в общих чертах и пользоваться оригинальной документацией для каждого из вариантов. Пример Ниже перечислены некоторые различия между преобразованиями, которые выпол- няют функции fscanf и fprintf: Операция преобразования [ специфична для fscanf.
388 Глава 15 fscanf не допускает каких бы то ни было спецификаций точности, применимых к fprintf, как и флажков, используемых в fprintf (-, +, пробела, О и #). Явно заданный размер поля — это минимум для fprintf, но максимум для fscanf. В отличие от fprintf, где звездочкой обозначается вычисляемый аргумент ширины поля, в fscanf звездочка обозначает отмену присваивания. Это, пожалуй, наиболее существенное различие. Если не указано иное, все операции преобразования игнорируют пробель- ные символы в начале строки и не учитывают их при определении максималь- ного размера поля. Это никак не относится к пробельным символам в конце строки. Такие символы (например, символ новой строки) просто не считыва- ются, если это не задано явно. Считывание же их может приводить к неожи- данным результатам, поскольку один пробельный символ в строке форматиро- вания задаст считывание последовательности пробельных символов во вход- ном потоке, что может привести к попытке считывания за пределами строки. Не существует способа непосредственного определения удачного либо не- удачного считывания текстовых символов. Точно так же, невозможно непосред- ственно определить успешное либо неудачное завершение операций преобразо- вания, включающих отмену присваиваний. Значения, возвращаемые соответст- вующими функциями, содержат только число успешных присваиваний. Операции преобразования очень сложны. Краткий их перечень приведен в таблице 15.3, далее следует подробное обсуждение. Таблица *15.3. Преобразования во входном нотоке (scaHf, fscanf, sscanf) Символ преобразования (буква) Спецификатор размера Тип аргумента Формат ввода d нет int * [-|+]dd...d i' hh char * [-|+][O[x]]dd...d2 Н short * 1 long * II5 long long * 1 intmaxt * Z size_t * г ptrdiff_t * и нет unsigned * H+]dd...d о hh unsigned char * [-|+]dd...d3 X h 1 unsigned short * unsigned long * [-|+][0x]dd...d4 IIs unsigned long long * i uintmax t * z size_t * г ptrdiff_t * с нет chor * Последовательность символов фикси- рованной длины. Если указом специфи- катор 1, символы многобайтовые.
Средства ввода/вывода 389 Символ преобразования (буква) Спецификатор размера Тип аргумента Формат ввода || wchar_t * S нет char * Последовательность символов, кроме пробельных. Если укозон спецификатор 1, символы многобайтовые. 1' wchar_t * р' нет void ** Последовательность символов наподобие начинающейся с %р в функции fprintf п1 нет int * Отсутствует, число считанных символов записывается в аргументе. hh char * h short * 1 long * IIs long long * i intmax_t * z size_t * t ptrdiff_t * а5 f, е, д нет float * Любоя констонто или десятичное значение с пловоющей десятичной запятой (точкой). 1 double * Целая констонто — возможно, с символом — или + впереди. L' long double * 1 нет char * Последовательность символов из просматриваемого множества. Если указан спецификатор 1, символы многобайтовые. I1 wchar_t * 1 Дополнение к С89. 2 Основание системы счисления определяется по первым цифрой, кок в случае констонт С. 3 Число предполагается восьмеричным, 4 Число предполагается шестнадцатеричным, независимо от наличия префикса Ох. 5 Дополнение к С99, Преобразование d Выполняется преобразование целого типа со знаком. Принимается один ар- гумент типа int *, short * или long *, в зависимости от спецификации размера. Формат считываемого числа соответствует требуемому для функции strtol (wcstol или wscanf) при значении аргумента base, равном 10 — то есть после- довательность десятичных цифр, возможно, со знаком — или + впереди. Если значение оказывается слишком велико, чтобы можно было выразить его в форме целого со знаком в заданном размере, результат будет непредсказуе- мым.
390 Глава 15 Преобразование i Выполняется преобразование целого типа со знаком. Принимается один ар- гумент типа int *, short * или long * в зависимости от спецификации размера. Формат считываемого числа соответствует требуемому для функции strtol (wcstol или wscanf) при значении аргумента base, равном 0 — то есть это це- лая константа С, без суффикса, возможно, со знаком - или + впереди и восьмеричным (О) либо шестнадцатеричным (Ох) префиксом. Если значение оказывается слишком велико, чтобы можно было выразить его в форме целого со знаком в заданном размере, результат будет непредсказуемым. Преобразование и Выполняется преобразование целого типа со знаком. Принимается один ар- гумент типа unsigned *, unsigned short * или unsigned long * в зависимости от спецификации размера. Формат считываемого числа соответствует требуемому для функции strtoul (wcstoul или wscanf) при значении аргумента base, равном 10 — то есть после- довательность десятичных цифр (возможно, со знаком — или + впереди). Если значение оказывается слишком велико, чтобы можно было выразить его в фор- ме целого со знаком в заданном размере, результат будет непредсказуемым. Преобразование о Выполняется преобразование восьмеричного целого без знака. Принимает- ся один аргумент типа unsigned *, unsigned short * или unsigned long * в зави- симости от спецификации размера. Формат считываемого числа соответствует требуемому для функции strtoul (wcstoul или wscanf) при значении аргумента base, равном 8 — то есть последо- вательность восьмеричных цифр (возможно, со знаком — или + впереди). Если значение оказывается слишком велико, чтобы можно было выразить его в фор- ме целого со знаком в заданном размере, результат будет непредсказуемым. Преобразование х Выполняется преобразование шестнадцатеричного целого без знака. При- нимается один аргумент типа unsigned *, unsigned short * или unsigned long * в зависимости от спецификации размера. Формат считываемого числа соответствует требуемому для функции strtoul (wcstoul или wscanf) при значении аргумента base, равном 16 — то есть после- довательность шестнадцатеричных цифр (возможно, со знаком — или + впере- ди). Операция выполняется над полным набором шестнадцатеричных цифр: 0123456789abcdefABCDEF. Если значение оказывается слишком велико, что- бы можно было выразить его в форме целого со знаком в заданном размере, ре- зультат будет непредсказуемым. В некоторых реализациях языка С, отличных от Standard, допускается обо- значение операции эквивалентного преобразования буквой X. Преобразование с Выполняется преобразование одного или некоторого числа символов. При- нимается один аргумент ссылочного типа: char * или, при наличии специфи- катора размера 1, wchar_t *. В операции с пробельные символы в начале стро- ки не пропускаются. Преобразование, которому подвергаются вводимые сим- волы, зависит от наличия спецификатора размера 1 и от функции, выполняющей преобразование (scanf или wscanf). Возможные варианты пере- числены в таблице 15.4.
Средства ввода/вывода 391 Таблица *15.4. Преобразования с во входном потоке Функция Спецификатор размера Тип аргумента Ввод Преобразование scanf нет char * СИМВОЛЫ Символы копируются без преобразования 1 wchar_t * многобайтовые символы В широкие символы, как функцией mbrtowc wscanf нет char * широкие символы В многобайтовые символы, как функцией wcrtomb 1 wcharjt * широкие символы Символы копируются без преобразования Если размер поля не указан, считывается ровно один символ (если указа- тель позиции не находится в конце файла). В последнем случае, операция пре- образования завершается ошибкой. Считанное значение записывается в об- ласть памяти, обозначенную следующим аргументом-указателем. Если размер поля указан, аргумент-указатель должен ссылаться на начало массива символов, спецификатор размера поля — указывать число считывае- мых символов. Операция преобразования завершается ошибкой, если конец файла достигается раньше, чем будет считано заданное число символов. Счи- танные символы записываются последовательно в элементы массива. Конец образованной таким образом строки нулевым символом не обозначается. Преобразование s Считывание строки. Используется один аргумент-указатель типа char * или, при наличии спецификатора размера 1 (Дополнение 1 к С89), wchar_t *. В операции s пробельные символы в начале строки всегда пропускаются. Символы считываются до достижения конца файла, пробельного символа (который не считывается) или считывания заданного числа символов. Если ко- нец файла достигается прежде считывания хотя бы одного непробельного сим- вола, операция преобразования считается завершенной аварийно. Вводимые символы могут подвергаться преобразованию, в зависимости от наличия спе- цификатора размера 1, а также от того, какая функция вызвана — scanf или wscanf (см. таблицу 15.5). Если спецификатор 1 указан функции scanf, ввод завершается обработкой первого пробельного символа; это происходит преж- де, чем вводимые символы будут интерпретированы как многобайтовые. Цепочка считанных символов обязательно завершается нулевым символом. Операция преобразования s опасна тем, что если не указать максимальный размер поля, общее число считанных символов может превысить объем рас- пределенной для них памяти. Операция s с явным указанием размера поля отличается от такой же опера- ции с. В операции с пробельные символы не пропускаются и считывается ров- но столько (широких) символов, сколько указано, если только прежде не будет достигнут конец файла. Что касается операции s, то здесь пропускаются толь- ко пробельные символы в начале строки, обнаружение же любого пробельного символа после считывания хотя бы одного непробельного приводит к заверше- нию операции. Кроме этого, конец последовательности символов, считанных в операции s, обозначается нулевым символом.
392 Глава 15 Таблица *15.5. Преобразования ввода в операции s Функция Спецификатор Тип размера аргумента Ввод Преобразование scanf нет char * символов Символы копируются без преобразования 1 wchar_t ♦ многобайтовых символов Преоброзовоние в широкие символы, как в функции inbrtowc wscanf нет chor * широких символов Преобразование в многобайтовые символы, кок в функции wcrtomb 1 wchar_t ♦ широких СИМВОЛОВ Символы копируются без преобразования Преобразование р Преобразование указателей. Требуется один аргумент типа void **. Формат считываемого значения зависит от реализации, но чаще всего совпадает с фор- матом, получаемым в результате преобразования %р в функциях семейства printf. Интерпретация указателя также зависит от реализации, однако если программа считывает указатель, который сама же и записала в течение данно- го сеанса выполнения программы, считанное значение будет верным. Преобра- зование р впервые введено в Standard С. Преобразование п В данном случае не происходит ни преобразования, ни считывания симво- лов — только запись в аргумент числа символов, обработанных на данный мо- мент функцией семейства scanf. Аргумент должен иметь тип int *, short * или long * — в зависимости от спецификации размера. Преобразование п впервые введено в Standard С. Преобразования a, f, е и g Преобразования десятичных значений с плавающей десятичной запятой (точкой). В С99 преобразование а разрешено и идентично при вводе преобразо- ваниям f, е и g. Указывается один аргумент-указатель типа float *, double * или long double — в зависимости от спецификации размера. Формат считываемого числа соответствует требуемому для функции strtod (wcstod или wscanf) — то есть последовательность десятичных или шестнадца- теричных цифр (возможно, со знаком - или + впереди); возможно также нали- чие десятичной точки и экспоненциального множителя, показатель степени которого — целое число со знаком. Строки INF, INFINITY, NAN и NAN(...), независимо от регистра, означают специальные значения с плавающей точ- кой. Применение шестнадцатеричных значений с плавающей точкой впервые реализовано в С99. Считываемые символы интерпретируются как представление в форме чисел с плавающей точкой и преобразуются в эту форму при заданном размере. Если не считана ни одна цифра или, как минимум, не считана ни одна цифра до по- явления экспоненциального множителя, значение принимается равным нулю. Если не считана ни одна цифра после считывания буквы, представляющей экспоненциальный множитель, показатель степени принимается равным нулю. Если считанное значение оказывается слишком велико или слишком мало для представления в форме с плавающей точкой указанного размера, воз- вращается значение HUGE_VAL (со знаком), а в переменную еггпо записыва-
Средства ввода/вывода 393 ется значение ERANGE. В реализациях, не совместимых со Standard С, воз- вращаемое значение и значение, записываемое в errno, могут быть иными. Если считанное значение входит в интервал допустимых значений, но не мо- жет, тем не менее, быть представлено в форме числа с плавающей точкой ука- занного размера, происходит округление либо усечение значения определен- ным образом. Операции преобразования a, f, е и g совершенно идентичны; каждая из них допускает любую форму представления значений с плавающей точкой. В неко- торых реализациях допускается обозначение этих операций буквами G и Е. Преобразование % Ожидается ввод одного знака процентов. Однако поскольку этим знаком по- мечается спецификация преобразования, для сопоставления одному знаку процентов приходится писать два. Аргумент-указатель не используется. Фла- жок отмены присваивания, размер поля, спецификация размера в данном пре- образовании не действуют. Преобразование [ Происходит считывание строки с использованием одного аргумента-ука- зателя типа char * или wchar_t * (при наличии спецификатора размера 1). В процессе преобразования [ пробельные символы в начале строки не пропус- каются. В спецификации преобразования точно указываются символы, кото- рые могут быть считаны. За символом [ в строке форматирования должны следовать еще несколько символов, последним из которых должен быть ]. Все символы, вплоть до ], являются составной частью спецификации преобразо- вания, именуемой сканируемым множеством (scanset). Циркумфлекс О, следующей непосредственно за [, обозначает логическую операцию НЕ — в этом случае, сканируемое множество состоит из всех символов, кроме ука- занных между символами и ]. Сканируемое множество рассматривается именно как математическое множество. Символ [, располагающийся между начальной и конечной скобками [], рас- сматривается как любой другой символ. Аналогично, любой символ ", не сле- дующий непосредственно за [, трактуется как обычный символ. В Standard С, символ ], следующий непосредственно за первым символом [, рассматривается как принадлежащий сканируемому множеству, следующий — как обозначаю- щий конец спецификации преобразования. Если символ ] следует непосредст- венно за флажком ", он не принадлежит сканируемому множеству, а следую- щий символ ] завершает набор, не принадлежащий сканируемому множеству. Подобная трактовка символа ] не поддерживается в некоторых ранних реали- зациях С. Пример Преобразование Сканируемое множество %[abca] Символы а, Ь и с %[Aabca] Все символы, кроме а, Ь и с %[[] Символ [ %[]] Символ ] %[ At] Пробел, запятая и символ горизонтальной табуляции
394 Глава 15 Символы считываются, пока не будет достигнут конец файла или символ, не принадлежащий множеству сканирования, либо (если указан размер поля) до считывания указанного числа символов. Затем, если не указана отмена присваивания (*), считанные символы записываются в объект, обозначенный аргументом-указателем, как это делается в операции преобразования s, вклю- чая любые преобразования в многобайтовые символы или обратно (см. табли- цу 15.5). Далее, в конец записанной последовательности символов добавляется нулевой символ. Спецификация размера в операции преобразования [ не дей- ствует. Подобно преобразованию s, данная операция преобразования опасна тем, что если не указать максимальный размер поля, общее число считанных сим- волов может превысить объем распределенной для них памяти. Ссылки: EOF 15.1; fprintf 1511, stdin 15.4. 15.9 fputc, fputwc, putc, putwc, putchar, putwchar Краткий перечень #include <stdio.h> int fputc(int c, FILE ‘stream); int putc(int c, FILE ‘stream); int putchar(int c): #include <stdio.h> tfinclude <wchar.h> wint_t fputwc(wchar_t c, FILE ‘stream); wint_t putwc(wchar_t c, FILE ‘stream); wint t putwchar(wchar t c); Функция fputc вызывается co значением символа и выходным потоком в качестве аргументов. Она выполняет запись символа в текущей позиции по- тока и возвращает этот же символ в форме значения типа int. Последователь- ность вызовов функции приводит к записи в поток последовательности симво- лов. В случае ошибки функция возвращает вместо символа, который должен был быть записан, EOF. Функция putc действует аналогично fputc, но обычно реализуется как мак- рос. Выражения, указываемые в качестве аргументов, не должны вызывать никаких побочных эффектов, поскольку могут оцениваться более одного раза. Функция putchar выполняет запись символа в стандартный выходной по- ток stdout. Подобно функции putc, putchar обычно реализуется в форме мак- роса, а потому очень эффективна. Оператор вызова putchar(c) эквивалентен putc(c, stdout). В Дополнении 1 к С89 определены функции вывода широких символов fputwc, putwc и putwchar, аналогичные соответствующим однобайтовым. В случае ошибки каждая из этих функций возвращает WEOF. В случае ошиб- ки кодирования в переменную еггпо записывается значение EILSEQ. Ссылки: EOF 15 1: stdout- 1 5.4.
Средства ввода/вывода 395 15.10 fputs, fputws, puts Краткий перечень ffinclude <stdio.h> int fputs (const char *s, FILE ‘stream); int puts(const char *s); ffinclude <stdio.h> ffinclude <wchar.h> int fputws(const wchar t *s, FILE ‘stream); Функция fputs вызывается с нуль-строкой и выходным потоком в качестве аргументов. Функция выполняет запись в поток всех символов строки, кроме заключительного нулевого. В случае ошибки fputs возвращает EOF, иначе — положительное значение. Функция puts аналогична fputs но выполняет запись только в поток stdout; после записи всех символов строки s выполняется запись символа но- вой строки (даже если этот символ уже есть в строке s). Несколько реализаций для UNIX, не относящихся к Standard С, содержат ошибку в функции fputs: в случае пустой строки s эта функция возвращает не- определенное значение. Программистам необходимо учитывать эту особен- ность. В Дополнении 1 к С89 определена новая функция fputws, аналогичная fputs. В случае ошибки эта функция возвращает EOF (вместо WEOF); если это ошибка кодирования, функция fputws записывает в переменную еггпо значе- ние EILSEQ. Ссылки: EOF 15.1; stdout 15.4. 15.11 fprintf, printf, sprintf, snprintf, fwprintf, wprintf, swprintf Краткий перечень ffinclude <stdio.h> int fprintf(FILE * restrict stream, const char * restrict format, ...); int printf(const char * restrict format, ...); int sprintf(char * restrict s, const char * restrict format, ...); int snprintf( char * restrict s, size_t n, const char * restrict format, ...); // C99 ffinclude <stdio.h> ffinclude <wchar.h> int fwprintf( FILE * restrict stream, const wchar_t * restrict format, ... ); int wprintf(const wchar_t * restrict format, ...); int swprintf(wchar t *s, size t n, const wchar t ‘format, ...);
396 Глава 15 Функция fprintf выполняет форматирование данных, выводимых в поток, указанный первым аргументом. Второй аргумент — строка форматирования. Последовательность выводимых символов генерируется в соответствии со строкой форматирования и выводится в указанный поток. Функция printf подобна fprintf, но выполняет вывод в стандартный выход- ной поток stdout. Функция sprintf помещает символы, предназначенные для вывода, в стро- ковый буфер s. После вывода всех символов в s помещается конечный нулевой символ. Программист должен сам побеспокоиться о том, чтобы строка s имела достаточный размер для размещения всего вывода, генерированного операци- ей форматирования. В отличие от этого, функция swprintf вызывается с указа- нием числа широких символов, включая нулевой в конце, которые должны быть записаны в строку вывода s. В С99 определена также функция snprintf, вызываемая с указанием числа выводимых «нешироких» символов. В случае ошибки в процессе вывода каждая из перечисленных функций возвращает EOF, иначе — некоторое другое значение. В Standard С, как и в большинстве современных реализаций, это число символов, записанных в выходной поток. В случае функции sprintf в это число не входит нулевой символ в конце строки. Standard С допускает возврат в случае ошибки любого отрицательного значения. В С89 (Дополнение 1) определены версии перечисленных функций, предна- значенные для вывода строк широких символов: fwprintf, wprintf и swprintf. Эти функции преобразуют дополнительные аргументы в строки широких сим- волов соответственно спецификаторам преобразований. Мы будем ссылаться на эти функции как на семейство wprintf или просто функции wprintf, в отли- чие от исходных однобайтовых функций printf. В Дополнении 1, кроме этого, определен спецификатор размера 1, применимый в операциях преобразований сиз функций printf и wprintf. В С99 введены операции преобразований шестнадцатеричных значений с плавающей точкой а и А, а также модификаторы размера hh, 11, j, z и t. Ссылки: EOF 15.1; шестнодцотеричный формат с плавающей точкой 2 7 2; scanf 158; Stdout 15.4; широкие символы 2.1.4 15.11.1 Формат вывода Строка форматирования, если она не содержит спецификаций преобразова- ний, представляет собой обычный текст, копируемый в точности. В Standard С строка форматирования — это (неинтерпретируемая) последовательность мно- гобайтовых символов, от начала до конца принадлежащих одному регистру. В случае функции wprintf, это строка широких символов. В спецификации преобразования может быть указана обработка некоторого числа дополнительных аргументов, что приводит к генерированию в операции форматного преобразования символов, не указанных явно в строке форматиро- вания. Следует точно указывать число аргументов и тип каждого, в соответст- вии со спецификациям преобразований в строке форматирования. Избыточные аргументы игнорируются, недостаток же приводит к непредсказуемым резуль- татам. Результат ошибок в спецификациях преобразований также непредсказу- ем. Спецификации преобразований для ввода и вывода схожи между собой; раз- личия между ними рассматриваются в разделе 15.8.2. Конец последовательно- сти операций, выполненных в соответствии с каждой спецификацией преобразования, помечается специальным маркером.
Средства ввода/вывода 397 Последовательность (широких) символов, подвергаемых преобразованию, можно подразделить на три составляющих: преобразуемое значение, отражаю- щее значение преобразуемого аргумента; префикс (как правило, +, - или про- бел); и заполнение — последовательность пробелов или нулей, дополняющая выводимую последовательность символов до минимально необходимого разме- ра. Префикс всегда устанавливается перед преобразуемым значением. В зави- симости от спецификации преобразования заполнение может предшествовать префиксу, отделять его от преобразуемого значения или следовать за преобра- зуемым значением. На следующем рисунке показаны примеры форматирова- ния выводимых значений. -34 ------------нЧ Заполнение Значение Префикс 1.27Е+02 -----------I--------- Значение Заполнение (Нет префикса) 0x00ОООООООООЕ7 Префикс Заполнение Значение 15.11.2 Спецификации преобразований Ниже, под терминами символы, буквы и т.д. мы будем подразумевать обыч- ные текстовые (однобайтовые) символы, в том числе буквы в случае использо- вания функций printf, и, соответственно, широкие символы и буквы, если ис- пользуются функции wprintf. Например, в функции wprintf спецификации преобразования начинаются с широкого знака процентов (%) и состоят из сле- дующих элементов (в порядке их следования). 1. Некоторого числа (включая нуль) символов-флажков (—, +, О, #, про- бел), изменяющих смысл операции преобразования. 2. Необязательного минимального размера поля в форме десятичной це- лой константы. 3. Необязательной спецификации точности в форме точки, за которой может следовать десятичное целое. 4. Необязательной спецификации размера вида 11, 1, L, h, hh, j, z или t. 5. Операции преобразования, обозначаемой одной из следующих букв: а, А, с, d, е, Е, f, g, G, i, n, о, p, s, u, x, X, %. Спецификаторы размера L и h, а также операции преобразований i, p и n введены в C89, спецификаторы размера 11, hh, j, z и t, а также операции преоб- разований а и А — в С99. Последней в спецификации указывается буква преобразования. Ниже пока- зана разбивка на составляющие спецификации преобразования %-#O12.4hd: % -#о 12 .4 h Начало спецификации--1 Флажки---- Буква преобразования ---Модификатор размера Минимальный размер поля-- ---Точность
398 Глава 15 15.11.3 Флажки преобразовании Задание флажков необязательно. Каждый флажок уточняет смысл основ- ной операции преобразования: - Выровнивоние значения по левому краю поля 0 Зополнение нулями (не пробелами) Обязательное указание зноко (+ или -) пробел Обязательное указание знака — или пробела tt Использование варианта основной операции преобразования Ниже дается подробное описание каждого флажка. Флажок - Этот флажок указывает выравнивание преобразованного значения по лево- му краю поля — то есть заполнение, если оно имеет место, происходит справа от значения. В отсутствие знака «минус», преобразованное значение выравни- вается по правому краю. Этот флажок действует только в случае явного указа- ния минимального размера поля, если преобразованное значение оказывается меньше этого размера; в остальных случаях, значение заполняет поле полно- стью, так что заполнение не требуется. Флажок О Если указан флажок О (нуль), для заполнение поля слева от преобразован- ного значения используется символ "О". Этот флажок действует только в слу- чае явного указания минимального размера поля, если преобразованное значе- ние оказывается меньше этого размера. В преобразованиях целых типов этот флажок подавляется спецификацией точности. В отсутствие флажка О заполнение выполняется пробелами. Кроме этого, место справа от значения в любом случае заполняется пробелами. Флажок + Если указан флажок +, результат преобразования численного значения все- гда будет выводиться со знаком (+ или -). Отрицательные значения в любом случае выводятся со знаком —. Данный флажок действует только в операциях преобразований a, A, d, е, Е, f, g, G и i. Флажок пробел При наличии этого флажка, если первый символ в преобразованном чис- ленном значении не является знаком + или —, это значение дополняется про- белом слева, независимо от заполнения (слева или справа), заданного флаж- ком —. Если в спецификации преобразования указаны одновременно флажки пробел и +, пробел игнорируется, поскольку в этом случае преобразованное значение всегда будет иметь знак. Данный флажок действует только в опера- циях преобразований a, A, d, е, Е, f, g, G и i. Флажок # При наличии этого флажка, применяется альтернативный вариант основ- ной операции преобразования. Данный флажок действует только в операциях преобразований а, А, е, Е, f, g, G, i, о, х и X. Подробнее о воздействии флажка # см. в описаниях соответствующих операций преобразования.
Средства ввода/вывода 399 15.11.4 Минимальный размер поля Минимальный размер поля (необязательный параметр) указывается в фор- ме десятичной целой константы. Эта константа должна представлять собой не- пустую последовательность десятичных цифр, первая из которых не должна быть нулем (она может быть принята за флажок 0). Если число символов в преобразованном значении (включая префикс) оказывается меньше указан- ного размера поля, свободное место заполняется нулями или пробелами, если больше — поле увеличивается до размера значения (заполнение в этом случае не применяется). Размер поля можно задать звездочкой (*); в этом случае, минимальный раз- мер поля указывается аргументом типа int. Отрицательное значение размера поля дает непредсказуемый результат. Пример Следующие два обращения к функции printf дают одинаковый результат, int width=5, value; printf ("%5d", value); printf ("%*d", width, value); 15.11.5 Точность Необязательная спецификация точности имеет вид точки, за которой сле- дует (необязательное) десятичное целое. Эта спецификация применяется для управления следующими параметрами вывода: 1. минимальным числом цифр, выводимых в преобразованиях d, i, о, и, х и X; 2. числом цифр справа от десятичной точки в преобразованиях е, Е и f; 3. числом значащих цифр в преобразованиях g и G; 4. максимальным числом символов, записанных из строки в преобразовании s. Если за точкой не следует целое значение, оно предполагается равным нулю, что, как правило, не равнозначно отсутствию спецификации точности. Иначе точность можно задать точкой со звездочкой; в этом случае использу- ется аргумент типа int, который и задает точность. Если звездочкой задаются, одновременно, размер поля и точность, аргумент размера поля должен быть указан перед аргументом точности. 15.11.6 Спецификация размера Перед некоторыми операциями преобразования может задаваться модифи- катор размера, обозначаемый одной из следующих последовательностей букв: 11 (буква 1-буква 1), 1 (буква 1), L, h, hh, j, г или t. Буква 1, в сочетании с операциями преобразования d, i, о, и, х и X, указы- вает, что аргумент преобразования имеет тип long или unsigned long, в сочета- нии с преобразованием п — тип long *. В С89 модификатор 1 может использо- ваться также в сочетании с преобразованием с; аргумент в этом случае имеет тип wint_t или — в случае преобразования s, — wchar_t *. Модификатор 1 не
400 Глава 15 действует с преобразованиями а, А, е, Е, f, F, g и G. Следует всегда помнить, что 1 и L — разные модификаторы. Модификатор П в сочетании с операциями преобразования d, i, о, и, х и X указывает, что аргумент преобразования имеет тип long long int или unsigned long long int. В сочетании с преобразованием п модификатор 11 указывает, что аргумент имеет тип long long int *. Этот модификатор введен в С99. Буква h в сочетании с операциями преобразования d, i, о, и, х и X указывает, что аргумент преобразования имеет тип short или unsigned short. То есть несмот- ря на то, что аргумент был бы преобразован в тип int или unsigned в процессе обычного преобразования, он должен быть заранее преобразован в тип short или unsigned short. В сочетании с преобразованием п модификатор h указывает, что аргумент имеет тип short *. Модификатор размера h введен в С89. Модификатор hh в сочетании с операциями преобразования d, i, о, и, х и X указывает, что аргумент преобразования имеет тип char или unsigned char. То есть несмотря на то, что аргумент был бы преобразован в тип int или unsigned в процессе обычного преобразования, он должен быть заранее преобразован в тип char или unsigned char. В сочетании с преобразованием п модификатор hh указывает, что аргумент имеет тип signed char *. Модификатор размера hh определен в С99. Буква L в сочетании с операциями преобразования а, А, е, Е, f, F, g и G указывает, что аргумент имеет тип long double. Модификатор размера L вве- ден в С89. Следует различать модификаторы L и 1; последний никак не дейст- вует в перечисленных операциях. Модификатор j в сочетании с операциями преобразования d, i, о, и, х и X указывает, что аргумент преобразования имеет тип intmax_t или uintmax_t. В сочетании с преобразованием п модификатор j указывает, что аргумент име- ет тип intmax_t *. Модификатор размера j введен в С99. Модификатор z в сочетании с операциями преобразования d, i, о, и, х и X указывает, что аргумент преобразования имеет тип size_t. В сочетании с пре- образованием п модификатор z указывает, что аргумент имеет тип size_t *. Модификатор размера z введен в С99. Модификатор t в сочетании с операциями преобразования d, i, о, и, х и X указывает, что аргумент преобразования имеет тип ptrdiff_t. В сочетании с преобразованием п модификатор t указывает, что аргумент имеет тип ptrdiff_t *. Модификатор размера t введен в С99. 15.11.7 Операции преобразования Операция преобразования обозначается одним символом: а, А, с, d, е, Е, f, g, G, i, n, о, p, s, u, x, X или %. Указанное преобразование определяет разре- шенные символы флажка и размера, требуемый тип аргумента и форму выво- да. Перечень операций преобразования приведен в таблице 15.6, далее каждая из операций рассматривается в отдельности. Таблица *15.6. Спецификации преобразований вывода Преобра- зование Фложки: - + # 0 пробел Модификатор размера Тип аргумента Точность по умолчанию1 Вывод d, i2 - + 0 пробел нет int 1 dd..,d h short -dd . d 1 long +dd...d
Средства ввода/вывода 401 Преобро- зовоние Фпажки: - + # 0 пробел Модификатор размера Тип аргумента Точность по умолчанию1 Вывод и - + 0 пробел нет h 1 unsigned ink unsigned short unsigned long 1 dd...d о - + И 0 пробел нет h 1 unsigned int unsigned short unsigned long 1 00., ,0 000...0 х, X — + # 0 пробел нет h 1 unsigned int unsigned short unsigned long 1 НН...Н Oxhh...h OXhh,.,h f - + # 0 пробел нет 1 L double double long double 6 d. d.d...d -d...d.d...d +d..,d.d,..d е, Е - + # 0 пробел нет 1 L double double long double 6 d,d.,.de+dd -d.d...dE-dd g,G - + # 0 пробел нет 1 L double double long double 6 как e, E или f а. А3 - + # 0 пробел нет 1 L double double long double 6 Oxh.h...hp+dd -0Xh,h,..hP- dd С - нет I4 int wint_t 1 c S нет Р char * wchar_t * X CC...C Р2 в зависимости ат реализации нет void * 1 в зависимости от реализации п2 нет h 1 int * short * long * нет нет % нет нет нет % ' Если отсутствует, принимается точность по умолчонию. 2 Введено в С89. Преобразования i и d при выводе эквивалентны, 3 Введено в С99, 4 Введено в С89 (Дополнение 1). Преобразования d и i Выполняется преобразование десятичных чисел со знаком. В отсутствие модификатора размера аргумент должен иметь тип int, при наличии модифи- катора h или 1 — соответственно, тип short или long. Для совместимости с fscanf в Standard С определена операция i; чтобы обеспечить единообразие вывода, эта операция опознается на выводе, где она идентична операции d. Преобразованное значение — это последовательность десятичных цифр, представляющих абсолютное значение аргумента. Длина этой последователь- ности минимальна, но не меньше заданной точности. Преобразованное значе- ние для соответствия заданной точности может содержать ведущие нули, не зависящие от заполнения, которое может внести дополнительные нули. Если
402 Глава 15 точность равна 1 (значение по умолчанию), преобразованное значение не будет содержать ведущих нулей, за исключением случая, когда оно само равно нулю (выводится один О). Если и точность, и аргумент равны 0, преобразованное значение представляет пустую строку. Формирование префикса происходит следующим образом. Если аргумент отрицателен, префикс представляет знак «минус». Если аргумент положите- лен и установлен флажок +, префикс представляет знак «плюс». Если аргу- мент неотрицателен, указан флажок пробел и не указан флажок +, префикс представляет пробел. В остальных случаях, префикс отсутствует (равен пустой строке). Флажок # в преобразованиях d и i не действует. Примеры преобразо- вания d приведены в таблице 15.7. Таблица 15.7. Примерь! преобразования d Пример формата Пример вывода Значение = 45 Пример вывода Значение = -45 %12d 45 -45 %012d 000000000045 -00000000045 % 012d 00000000045 -00000000045 %+12d +45 -45 %+012d +00000000045 -00000000045 %-12d 45 -45 %- 12d 45 -45 %-+12d +45 -45 %12.4d 0045 -0045 %-12.4d 0045 -0045 Преобразование и Выполняется преобразование десятичных чисел со знаком. В отсутствие спе- цификатора размера аргумент должен иметь тип unsigned, если установлен спе- цификатор h или I — соответственно, тип unsigned short или unsigned long. Преобразованное значение — это последовательность десятичных цифр, со- ставляющих значение аргумента. Длина этой последовательности минимальна, но не меньше заданной точности. В зависимости от заданной точности преобра- зованное значение может содержать ведущие нули, не зависящие от нулей, ко- торые могут быть внесены в результате заполнения. Если точность равна 1 (по умолчанию), преобразованное значение не будет содержать ведущих нулей, за исключением случая, когда оно само равно нулю (выводится один О). Если и точность, и аргумент равны 0, преобразованное значение представляет пустую строку. Префикс всегда пуст. Флажки +, пробел и # в преобразовании и не дей- ствуют. Примеры преобразований и приведены в таблице 15.8. Таблица 15.8. Примеры преобразования и Пример формата Пример вывода Значение ~ 45 Пример вывода Значение •-= -45 %14и 45 4294967251 %014и 00000000000045 00004294967251
Средства ввода/вывода 403 Пример формата Пример вывода Значение = 45 Пример вывода Значение = -45 %#14и 45 4294967251 %#014и 00000000000045 00004294967251 %-14и 45 4294967251 %-#14и 45 4294967251 %14.4и 0045 4294967251 %-14.4и 0045 4294967251 Преобразование о Выполняется преобразование восьмеричного беззнакового значения. В отсут- ствие модификатора размера аргумент должен иметь тип unsigned, при наличии модификатора h или I — соответственно, тип unsigned short или unsigned long. Преобразованное значение — это последовательность восьмеричных цифр, представляющих значение аргумента. Длина этой последовательности мини- мальна, но не меньше заданной точности. Преобразованное значение для соот- ветствия заданной точности может содержать ведущие нули, не зависящие от заполнения, которое может внести дополнительные нули. Если точность равна 1 (значение по умолчанию), преобразованное значение не будет содержать ве- дущих нулей, за исключением случая, когда оно само равно нулю (выводится один 0). Если и точность, и аргумент равны 0, преобразованное значение пред- ставляет пустую строку. Если задан флажок #, префикс равен О, иначе — пус- той строке. Флажки + и пробел в преобразовании о не действуют. Примеры преобразования о приведены в таблице 15.9. Таблица 15.9. Примеры преобразования о Пример формата Пример вывода Значение = 45 Пример вывода Значение = -45 %14о 55 37777777723 %014о 00000000000055 00037777777723 %#14о 055 037777777723 %#014о 00000000000055 00037777777723 %-14о 55 37777777723 %-#14о 055 037777777723 %14.4о 0055 37777777723 %-#14.4о 00055 037777777723 Преобразования х и X Выполняется преобразование шестнадцатеричных беззнаковых численных значений. В отсутствие модификатора размера аргумент должен иметь тип unsigned, при наличии модификатора h или I — соответственно, тип unsigned short или unsigned long. Преобразованное значение — это последовательность шестнадцатеричных цифр, представляющих значение аргумента. Длина этой последовательности минимальна, но не меньше заданной точности. В операции х используется на- бор цифр 0123456789abcdef, в операции X — 0123456789ABCDEF. Преобра-
404 Глава 15 зованное значение для соответствия заданной точности может содержать веду- щие нули, не зависящие от заполнения, которое может внести дополнитель- ные нули. Если точность равна 1, преобразованное значение не будет содержать ведущих нулей, за исключением случая, когда оно само равно нулю (выводится один О). Если и точность, и аргумент равны 0, преобразованное значение представляет пустую строку. Если точность не указана, она предпо- лагается равной 1. Если задан флажок #, префикс равен Ох (операция х), или ОХ (операция X); в отсутствие флажка # префикс отсутствует (равен пустой строке). Флажки + и пробел не действуют. Примеры преобразований х и X приведены в табли- це 15.10. Таблица 15.10. Примеры преобразований х и X Пример формата Пример вывода Значение = 45 Пример вывода Значение = -45 %12х 2d ffffffd3 %012х 00000000002d 0000ffffffd3 %#12Х 0X2D 0XFFFFFFD3 %#012Х 0X000000002D 0X00FFFFFFD3 %-12х 2d ffffffd3 %-#12х 0x2d 0xffffffd3 %12.4х 002d ffffffd3 %-#12.4х 0x002d 0xffffffd3 Преобразование с Аргумент выводится как символ либо широкий символ. Используется один аргумент. Флажки +, пробел и #, а также спецификатор точности в операции преобразования с не действуют. Преобразование, которому подвергается сим- вол-аргумент, зависит от того, указан ли спецификатор размера I, и от вызы- ваемой функции — printf или wprintf. Возможные варианты перечислены в таблице 15.11. В таблице 15.12 приведены примеры преобразования с. Таблица 15.11. Преобразование с Функция Спецификатор Тип аргумента Преобразование размера printf нет int Аргумент преобразуется в unsigned char И копируется Б выходной поток. 1 wintt Аргумент преобразуется в wchar_t, затем в многобайтовые символы, кок функцией wcrtomb и выводится. wprintf нет int Аргумент преобразуется в широкие символы, кок функцией btowc, и копируется в выходной поток. 1 wint_t Аргумент преобразуется в wchar_t и копируется в выходной поток ' Состояние преобразования в функции wcrtomb устанавливается в нуль до преобразования символа
Средства ввода/вывода 405 Таблица 15.12. Примеры преобразования с Пример формата Пример вывода Значение = %12с ★ %012с 0000000000* %-12с ★ Преобразование s Аргумент выводится в форме строки. Используется один аргумент. В отсут- ствие спецификатора размера 1 аргумент должен иметь тип указателя на мас- сив символов любого типа. Если спецификатор 1 указан, аргумент должен иметь тип wchar_t * и ссылаться на последовательность широких символов. Префикс — всегда пустая строка. Флажки +, пробел и # в преобразовании s не действуют. В отсутствие спецификатора точности, преобразованное значение представ- ляет последовательность символов строкового аргумента вплоть до нулевого (широкого) символа в конце (не включая этот символ). Если указан специфи- катор точности р, преобразованное значение состоит из первых р символов либо всей выходной строки, кроме нулевого символа в конце — в зависимости от того, какой из вариантов окажется короче. Если указана точность, строка аргумента при достаточной ее длине не должна обязательно оканчиваться ну- левым символом. При записи многобайтовых символов (функция printf со спе- цификатором 1) частичная запись многобайтового символа невозможна, а по- тому число выводимых байт может оказаться меньше р. Преобразование, которому подвергается строка аргумента, зависит от нали- чия спецификатора размера 1 и от того, какая из функций вызвана — printf или wprintf. Возможные варианты перечислены в таблице 15.13. Примеры преобразования s приведены в таблице 15.14. Таблица 15.13. Преобразование s Функция Спецификатор размера Тип аргумента Преобразование printf нет char * Символы строки аргумента колируются в выходной поток. I wchar_t * Широкие символы строки аргумента преобразуются в многобайтовые символы, как функцией wcrtomb1. wprintf нет char * Многобайтовые символы строки аргумента преобразуются в широкие символы, как функцией mbrtowc1 I wchar_t * Широкие символы строки аргумента колируются в выходной поток. 1 Состояние преобразования в функции wcrtomb или mbrtowc устанавливается в нуль до преоброзова- ния первого символа. В последующих преобразованиях используется состояние, модифицированное по предыдущим символом.
406 Глава 15 Таблица 15.14. Примеры преобразования s Пример формата Пример вывода Значение - "zap" Пример выводе Значение - "longish*' %12s zap longish %12.5s zap longi %012s OOOOOOOOOzap OOOOOlongish %-12s zap longish Преобразование р Аргумент должен иметь тип void *, и он выводится в формате, зависящем от реализации. В большинстве случаев это тот же формат, какой получается в результате преобразований о, х или X. Этот вид преобразования редко встре- чается вне пределов Standard С. Преобразование п В отсутствие спецификатора размера аргумент должен иметь тип int *; если указан спецификатор 1 или h, аргумент должен иметь тип, соответственно, long * или short *. В этом преобразовании вывода символов не происходит. Вместо этого в заданную целую переменную записывается число уже выведен- ных символов. Этот вид преобразования редко встречается вне пределов Standard С. Преобразования f и F Выполняется преобразование численного значения с плавающей десятичной запятой (точкой). Принимается один аргумент типа double или, при наличии спецификатора размера L, long double. Если указать аргумент типа float, он пре- образуется в процессе обычного преобразования аргументов в тип double. Вслед- ствие этого % f при выводе значений float не срабатывает. Преобразованное значение представляет последовательность десятичных цифр — возможно, с десятичной точкой, — отображающую приближенное аб- солютное значение аргумента. Десятичной точке должна предшествовать ми- нимум одна цифра. Спецификатор точности задает число цифр после десятич- ной точки. Если точность равна нулю, цифры после десятичной точки отсутст- вуют; если не установлен флажок #, отсутствует и сама точка. Если точность не указана, по умолчанию она предполагается равной 6. Если значение не может быть представлено в полном размере, преобразо- ванное значение является результатом округления до нужного числа десятич- ных цифр. В некоторых реализациях С такое округление может выполняться с ошибками. В С99, если значение с плавающей точкой представляет бесконечность, по- сле преобразования в операции f оно имеет вид inf, —inf, infinity или —infinity (в зависимости от реализации). Если значение с плавающей точкой равно NaN (не число), преобразованное будет иметь вид пап, -пап, пап(...) или -пап(...), где "..." — зависящая то реализации последовательность букв, цифр и симво- лов подчеркивания. В операции F значения бесконечности и NaN преобразу- ются в верхний регистр. Флажки # и 0 никак не влияют на преобразование значений бесконечности и NaN. Префикс вычисляется следующим образом. Если аргумент отрицательный, префикс представляет знак «минус»; если неотрицательный и установлен фла-
Средства ввода/вывода 407 жок Н--знак «плюс»; если неотрицательный и установлен флажок пробел, но не установлен 4--пробел. В остальных случаях префикс — пустая строка. Примеры преобразования f приведены в таблице 15.15. Таблица 15.15. Примеры преобразования f Пример формата Пример вывода Значение = 12.678 Пример вывода Значение = -12.678 %10.2f 12.68 -12.68 %010.2f 000000012.68 -00000012.68 % 010.2f 00000012.68 -00000012.68 %+10.2f +12.68 -12.68 %+010.2f +00000012.68 -00000012.68 %-10.2f 12.68 -12.68 %- 10.2f 12.68 -12.68 %-+10.4f +12.6780 -12.6780 Преобразования е и Е Выполняется преобразование десятичного значения с плавающей точкой. Принимается один аргумент; в отсутствие спецификатора размера, он имеет тип double, если указан спецификатор L — тип long double. Разрешен также аргумент типа float, как в преобразовании f. Дается описание только преобра- зования е; преобразование Е отличается только заменой буквы е в обозначении экспоненциального множителя буквой Е. Преобразованное значение представляет десятичную цифру, за которой могут следовать десятичная точка, еще некоторое число десятичных цифр, буква е, знак «плюс» или «минус» и, наконец, еще две или более десятичных цифр. Если значение не равно нулю, часть его, предшествующая букве е, составляет значе- ние в интервале от 1.0 до 9.99... Часть после буквы е — это показатель степени в форме десятичного целого со знаком. Значение первой части, умноженное на 10 в степени, равной значению второй, примерно равно значению аргумента. Число цифр в показателе степени одинаково для всех значений и определяется диапазо- ном значений чисел с плавающей точкой в данном представлении. Примеры пре- образований е и Е приведены в таблице 15.16. Таблица 15.16. Примеры преобразований е и Е Пример формата Пример вывода Значение = 12.678 Пример вывода Значение = -12.678 %10.2е 1.27е+01 -1.27е+01 %010.2е 00001.27е+01 -0001.27е+01 % 010.2е 0001.27е+01 -0001,27е+01 %+10.2Е +1.27Е+01 -1.27Е+01 %+010.2Е +0001.27Е+01 -0001.27Е+01 %-10.2е 1.27е+01 -1.27е+01 %- 10.2е 1.27е+01 -1.27е+01 %-+10.2е +1.27е+01 -1.27е+01
408 Глава 15 Спецификатор точности задает число цифр после десятичной точки. Если точность не указана, по умолчанию она предполагается равной 6. Если точ- ность равна нулю, цифры после десятичной точки отсутствуют; если не уста- новлен флажок #, отсутствует и сама точка. Если значение не может быть представлено в полном размере, преобразованное значение является результа- том округления значения точного. Префикс вычисляется как в преобразова- нии f. Значения бесконечности и NaN преобразуются как в f и F. Преобразования g и G Выполняется преобразование десятичного значения с плавающей точкой. Принимается один аргумент; в отсутствие спецификатора размера он имеет тип double, если указан спецификатор L — тип long double. Разрешен также аргумент типа float, как в преобразовании f. Дается описание только преобра- зования g; единственное отличие преобразования G — использование преобра- зования Е вместо е. Если задать точность меньше 1, она будет установлена рав- ной 1. Если точность не указать, по умолчанию будет принято значение 6. Преобразование g начинается так же, как f или е — выбор зависит от преоб- разуемого значения. В спецификации Standard С сказано, что преобразование е используется лишь в случае, если показатель степени в экспоненциальном множителе этого преобразования оказывается меньше -4 либо больше или равным заданной точности. Некоторые реализации используют е в случае, ко- гда показатель степени меньше -3 или больше заданной точности. Далее, из преобразованного (в операции f или е) значения удаляются млад- шие нули справа от десятичной точки. Если после точки не остается цифр, удаляется и сама точка. Если установлен флажок #, удаления нулей и деся- тичной точки не происходит. Префикс вычисляется так же, как в преобразованиях f и е. Значения беско- нечности и NaN обрабатываются как в преобразованиях f и F. Преобразования а и А Эти преобразования введены в С99. Выполняется преобразование шестна- дцатеричного численного значения с плавающей точкой и знаком. Использу- ется один аргумент, который, в отсутствие спецификатора размера, должен иметь тип double, при наличии спецификатора L — тип long double. Как и в других преобразованиях значений с плавающей точкой, допускается ис- пользование аргумента типа float. Мы будем рассматривать только преобразо- вание а; А отличается от него только использованием букв верхнего регистра для обозначения шестнадцатеричных цифр, префикса (ОХ) и экспоненциаль- ного множителя (Р). Преобразованное значение состоит из шестнадцатеричных цифр, за которы- ми могут следовать десятичная точка и еще несколько шестнадцатеричных цифр, далее буква р, знак «плюс» или «минус» и одна или несколько десятич- ных цифр. Если значение не равно нулю и не денормали.зовано, первая (слева) цифра не равна нулю. Часть, следующая за буквой р — это степень, в которую возводится число 2 (экспоненциальный множитель), представляемая десятич- ным целым со знаком. Спецификатор точности указывает число шестнадцатеричных цифр, выво- димых справа от десятичной точки; если он не указан, число этих цифр будет достаточным для полного вывода значения типа double. (Если FLTRADIX = 2, это соответствует точности по умолчанию.) Если указана нулевая точность, после десятичной точки не выводится ни одной цифры; в отсутствие флажка #, не выводится и сама точка. Если значение с плавающей точкой не может
Средства ввода/вывода 409 быть представлено точно в заданном числе цифр, происходит его округление. Префикс вычисляется как в преобразовании f. Значения бесконечности и NaN обрабатываются как в преобразованиях f и F. Преобразование % Выводится один знак процентов. Поскольку этот знак зарезервирован для обозначения начала операции преобразования, для вывода одного знака при- ходится писать два. Аргументы не используются, префикс — пустая строка. В Standard С не допускается установка каких бы то ни было флажков, ми- нимального размера, точности или модификаторов размера; данная специфи- кация преобразования всегда имеет следующий вид: % %. В других реализа- ция допускается заполнение — как в любой другой операции преобразования. Например, задав спецификацию вида % 05%, можно получить на выходе 0000%. Флажки +, пробел и #, спецификации точности и размера не действу- ют в любой реализации. Пример Следующая пара строк — это куин (самавоспраизводящаяся программа). Если ее за- пустить, она передает собственную копию на стандартное устройство вывода. Пер- вая строка оказалась слишком длинной, чтобы разместиться на странице, поэтому ее пришлось разбить на две, задав после выражения % cmain() обратную косую чер- ту. char*f="char*f=%c%s%c, q='%c', n='%cn',b='%c%c';%cmain ()\ {printf(f,q,f,q,q,b,b,b,n,n);}%c", q='"',n='\n',b='\\’; main(){printf(f,q,f,q,q,b,b,b,n,n);} Следующая программа в одну строку — почти куин (опять же, пришлось разбить строку после ";main()). Предоставим читателю возможность самому определить, по- чему эта программа — не совсем куин. cbar*f="char*f=%c%s%c;main(){printf (f, 34 , f, 34);)”;main()\ {printf(f,34,f,34);) 15.12 v[x]prlntf, v[x]scanf Краткий перечень {{include <stdarg.h> #include <stdio.h> int vfprintf(FILE * restrict stream, const char * restrict format, va_list arg); int vprintf(const char * restrict format, va_list arg); int vsprintf(char *s, const char * restrict format, va_list arg); int vfscanf(FILE * restrict stream, const char * restrict format, va_list arg); // C99 int vscanf(const char * restrict format, va_list arg); // C99 int vsscanf(const char * restrict s, const char * restrict format, va list arg); // C99
410 Глава 15 Краткий перечень #include <stdarg.h> #include <stdio.h> #include <wchar.h> int vfwprintf(FILE * restrict stream, const wchar_t * restrict format, va_list arg) int vwprintf(const wchar_t * restrict format, va_list arg); int vswprintf(wchar_t * restrict s, size_t n, const wchar_t * restrict format, va_list arg); int vfwscanf(FILE * restrict stream, const wchar_t * restrict format, va_list arg); // C99 int vswscanf(const wchar_t * restrict s, const wchar_t * restrict format, va_list arg); // C99 int vwscanf(const wchar t * restrict format, va list arg); // C99 Функции vfprintf, vprintf и vsprintf аналогичны, соответственно, функци- ям fprintf, printf и sprintf, но вызываются с дополнительными аргументами (переменное число аргументов — см. описания библиотек varargs и stdarg, раздел 11.4). Аргумент arg должен быть инициализирован макросом va_start и, возможно, последующими вызовами va_arg. Эти функции полезны в усло- виях, когда программисту необходимо определить собственные функции с пе- ременным числом аргументов и использованием средств форматного вывода. Данные функции не обращаются к va_end. В Дополнении 1 к С89 определены функции vfwprintf, vwprintf и vswprintf, являющиеся аналогами, соответственно, функций fwprintf, wprintf и swprintf. В С99, кроме этого, определены соответствующие функции ввода vfscanf, vscanf и vsscanf, а также их двойники для ввода широких символов — vfwscanf, vwscanf и vswscanf. Пример Предположим, нам необходима написать функцию общего назначения trace, кото- рая выводит имя некоторой (указанной) функции и значения ее аргументов. Любая функция, трассируемая таким образом, вызывается оператором вида: trace (name, format, parml, parm2,..., parmN) Здесь name — имя трассируемой функции, format — строка форматирования, управляющая выводом значений аргументов parml, parm2, .... parmN. Например: int f(int x, double у) /* Трассировать эту функцию. */ ( trace ("f","x=%d, y=%f, x, у); ) Ниже приведена возможная реализация функции trace в традиционном С: ((include <varargs.h> If include <stdio .h> void trace(va_alist) va_dcl ( va_list args; char *name,
Средства ввода/вывода 411 char * format; va_start(args); name = va_arg(args,char *); format = va_arg(args,char *) ; fprintf(stderr,entering %s(", name); vfprintf(stderr, format, args); fprintf(stderr,")\n"); va_end(args); ) 15.13 fread, fwrite Краткий перечень ttinclude <stdio.h> size_t fread( void * restrict ptr, size_t element_size, size_t count, FILE * restrict stream); size_t fwrite( const void * restrict ptr, size_t element_size, size_t count, FILE * restrict stream); Функции fread предназначена для ввода данных из двоичных файлов, fwrite — соответственно, для вывода. В обоих случаях, stream — входной или выходной поток, ptr — указатель на массив с числом элементов count и дли- ной каждого элемента element_size. Функция fread считывает элементы указанного размера в количестве, не превышающем count, записывая их в указанный массив, и возвращает число считанных элементов. Это число может быть меньше count, если обнаружива- ется конец файла. В случае ошибки функция возвращает нуль. Действитель- ное состояние ошибки или обнаружения конца файла определяется при помо- щи функций ferror и feof. Если значение count или element_size равно нулю, считывания данных не происходит, функция возвращает нуль. Пример Следующая программа считывает из входного файла объекты структурного типа и выводит число считанных объектов. Закрытие файла происходит по команде exit: /* Счет числа элементов типа "struct S" в файле "in.dat" */ ttinclude <stdio.h> static char *FileName = "in.dat"; struct S { int a,b; double d; char str[103]; ); int main(void) { struct S buffer; int items_read = 0; FILE *in_file = fopen(FileName,"r") ; if (in_file = NULL) ( fprintf(stderr,"?He удается открыть %s\n" , FileName) ; exit(1); ) while (fread((char *) sbuffer. sizeof(struct S), 1, in_file) == 1) items_read++; if (terror(in_file))
412 Глава 15 ( fprintf(stderr?Ошибка ввода, файл %s запись %d\n", FileName,items_read+l); exit(l); } printf ("Готово; считано %d элементов\п", items_read) ; return 0; ) Функция fwrite выполняет запись элементов размера element_size в коли- честве count из указанного массива и возвращает число записанных элемен- тов. Если не происходит ошибки, это число равно count. В традиционном С аргумент element_size имеет тип unsigned, ptr — тип char *, count — тип int. Ссылки: exit 19.3; feof, terror 15.14; fseek, ftell 15 5. 15.14 feof, ferror, dearerr Краткий перечень ttinclude <stdio.h> int feof(FILE ‘stream); int ferror(FILE ‘stream); void clearerr(FILE ‘stream); В качестве аргумента функции feof указывается входной поток. Если во время считывания из входного потока обнаруживается конец файла, функция возвращает ненулевое значение, иначе — нуль. Следует иметь в виду, что об- наружение конца файла происходит не после считывания всех данных, а в ре- зультате попытки считывания данных, которых больше не осталось —то есть за последним символом. Данная функция вызывается, если произошла ошиб- ка ввода. Функция ferror возвращает состояние ошибки потока. Если во время счи- тывания данных из потока произошла ошибка, ferror возвращает ненулевое значение, иначе — нуль. Если в данном потоке произошла ошибка, функция ferror при многократных вызовах будет сообщать о ней (возвращать ненулевое значение) до тех пор, пока не будет выполнен сброс состояния ошибки функ- цией clearerr. Сброс происходит также в результате закрытия потока функци- ей fclose. Функция clearerr сбрасывает состояние ошибки или достижения конца фай- ла в указанном потоке; после этого функция ferror будет показывать отсутствие ошибок в данном потоке до тех пор, пока не произойдет новая ошибка. 15.15 remove, rename Краткий перечень ttinclude <stdio.h> int rename( const char *oldname, const char ‘newname); int remove(const char * filename);
Средства ввода/вывода 413 Функция remove удаляет файл с указанным именем. В случае успешного удаления она возвращает нуль, иначе — ненулевое значение. Строка, на кото- рую ссылается указатель filename, неизменяема. Само понятие удаления может зависеть от реализации, но программа не сможет открыть файл, который удали- ла. Результат операции удаления в условиях, когда файл открыт или не сущест- вует, зависит от реализации. В традиционном С эта функция отсутствует; вме- сто нее, как правило, используется функция unlink из UNIX. Функция rename меняет имя файла с oldname на newname; в случае успеш- ного выполнения она возвращает нуль, иначе — ненулевое значение. Строки, на которые ссылаются указатели oldname и newname, не изменяются. Если oldname — имя открытого или несуществующего файла, или же файл с име- нем newname уже существует, результат применения функции rename зави- сит от реализации. 15.16 tmpfile, tmpnam, mktemp Краткий перечень ftinclude <stdio.h> FILE *tmpfile(void) ; char *tmpnam(char *buf); #define L_tmpnam ... #define TMP MAX ... Функция tmpfile создает файл и открывает его при помощи функции fopen в режиме "w+b" (в традиционном С — в режиме ”w+"). В случае успешного выполнения функция возвращает указатель на созданный файл, иначе — ну- левой указатель. Созданный файл предназначается для использования только в течение выполнения программы и удаляется при ее завершении. После запи- си данных в файл указатель файловой позиции можно установить в его начало для считывания вызовом функции rewind. Функция tmpnam применяется для создания файлов, имена которых не конфликтуют с именами существующих файлов. Новый файл можно открыть при помощи функции fopen в любом из режимов, которые она поддерживает. Файлы, создаваемые функцией tmpnam — не временные, а потому не удаля- ются автоматически с завершением программы. Если аргумент buf равен NULL, tmpnam возвращает указатель на строку имени нового файла; эта стро- ка может изменяться последующими обращениями к tmpnam. Если аргумент buf не равен NULL, он должен указывать на массив, состоящий не менее чем из L tmpnam символов; tmpnam копирует в этот массив строку имени создан- ного файла и возвращает указатель buf. Если происходит ошибка, функция возвращает нулевой указатель. В Standard С определено значение ТМР_МАХ, устанавливающее максимальное число последовательных обращений к функ- ции tmpnam, при котором она генерирует уникальные имена. Это число долж- но быть не менее 25. Функция традиционного С mktemp имеет ту же сигнатуру, что и tmpnam, но в ней аргумент buf (шаблон) должен указывать на строку с шестью буквами X в конце; поверх них будут записаны другие буквы и цифры, которые и обес- печат уникальность файлового имени. Функция возвращает значение указате- ля buf. Чтобы функция генерировала уникальные имена, ей при каждом вызо-
414 Глава 15 ве необходимо задавать новый шаблон. В реализациях для UNIX в качестве строки ХХХХХХ часто используется идентификатор процессов. В Standard С функция mktemp отсутствует. Пример Следующий пример иллюстрирует распространенную вредную привычку: ptr = fopen (mktemp("/tmp/abcXXXXXX"),"w+"); Если строковая константа не модифицируема, выполнение этого оператора приве- дет к ошибке. Кроме этого, в данной ситуации программист лишается возможности ссылаться на строку файлового имени. Гораздо удобнее (и не менее эффективен) сле- дующий вариант: char filename[]="/tmp/abcXXXXXX"; ptr = fopen (mktemp (filename),"w+");
Глава 16 Утилиты общего назначения Средства, рассматриваемые в этой главе, объявлены в заголовочном файле stdlib.h. Они подразделяются на несколько категорий: • Распределение памяти • Генерирование случайных чисел • Преобразования чисел и целочисленная арифметика • Взаимодействие со средой • Поиск и сортировка • Многобайтовые и широкие символы, преобразование строк 16.1 malloc, calloc, mlalloc, clalloc, free, cfree Краткий перечень ftinclude <stdlib.h> void *malloc(size_t size); void *calloc(size_t elt_count, size_t elt_size); void ‘realloc(void *ptr, size_t size); void free(void *ptr); Функция malloc распределяет область памяти, достаточную для размеще- ния объекта, объем которого (определенный при помощи sizeof) равен size. Функция возвращает указатель на первый элемент области, гарантированно соответствующий правильному выравниванию данных любого типа. После этого, вызывающая функция может воспользоваться операцией преобразова- ния типов для задания нового типа указателю. Если по какой-либо причине распределить память не удается, функция возвращает нулевой указатель. Если запрошен объем памяти, равный 0, функции Standard С возвращают ну- левой либо ненулевой указатель; в любом случае, этот указатель не ссылается ни на какой объект. Распределенная память не инициализируется каким бы то ни было образом, поэтому вызывающая функция не может анализировать ее содержимое. Поскольку каждая область, распределенная функцией malloc, должна выравниваться под любой тип, ее объем устанавливается кратным ве- личине выравнивания — чаще всего, восьми байтам.
416 Глава 16 Пример Функция, вызывающая процедуру распределения памяти, как правило, возвращает указатель на переменную соответствующего типа. Предположим, Т — тип некоторо- го объекта, которому необходимо распределить память. Это может быть структура, массив или текстовый символ. Т *NewObject(void) ( Т *objptr = (Т *) malloc(sizeof(Т)); if (objptr==NULL) printf("NewObject: Завершен аварийно!\n"); return objptr; 1 Приведение типа (T *) в Standard С не обязательно, поскольку malloc возвращает указатель типа void * и разрешено неявное его преобразование в тип objptr. В тради- ционном С функция malloc возвращает значение типа char *, и неявное преобразо- вание типа в операции присваивания может повлечь вывод предупреждения. При- ведение типа необходимо для совместимости с C++. Функция calloc распределяет область памяти, достаточную для размеще- ния массива из elt_count элементов, каждый размером elt_size (обычно зада- ваемым sizeof). Распределенная область побитово обнуляется, функция воз- вращает указатель на ее первый элемент. Если распределение памяти завер- шается по какой-либо причине неудачей, либо заданы нулевые значения elt_count или elt_size, функция возвращает те же значения, что и malloc в аналогичной ситуации. Следует иметь в виду, что побитовое обнуление не равнозначно обнулению значения с плавающей точкой или указателя. Функция realloc изменяет объем области памяти, которая была распределе- на одной из стандартных функций, без изменения содержимого этой области. При необходимости, содержимое копируется в новую область памяти. Функ- ция возвращает указатель на модифицированную (возможно, новую) область памяти. Если запрос не удается выполнить, функция возвращает нулевой ука- затель, а область памяти остается неизменной. Если первый аргумент функ- ции realloc — нулевой указатель, она действует аналогично функции malloc. Если ptr не равен нулевому указателю, но равен нулю аргумент size, функция realloc (как и malloc) возвращает нулевой указатель либо указатель, не ссы- лающийся ни на один реальный объект, прежняя же область освобождается. Если объем новой области меньше объема прежней, часть содержимого (в кон- це прежней области) утрачивается. Если объем новой области больше объема прежней, все содержимое сохраняется с добавлением свободной части в конце. Эта новая часть никак не инициализируется, поэтому вызывающая функция должна действовать в предположении, что она содержит неопределенную ин- формацию. Если функция realloc возвращает указатель, отличный от первого аргумента, это означает, что прежняя область памяти освобождена и не может больше использоваться. Пример Ниже приведен типичный пример применения функции realloc для увеличения размера динамического массива, обозначаемого указателем samples. (Обращение к элементам такого массива должно происходить посредством индексных выраже- ний: любой указатель на массив, после вызова функции realloc, может оказаться недействительным.)
Утилиты общего назначения 417 #include <stdlib.h> «define SAMPLE_INCREMENT 100 int sample_limit = 0; /* Максимальный размер массива */ int saniple_count = 0; /* Число элементов в массиве */ double *samples = NULL; /* Будет ссылаться на массив */ int AddSample( double new_sample ) /* Добавление элемента в конец массива */ { if (sample_count < sample_limit) { samples[sample_count++] = new_sample; ) else { /* Распределение нового, большего массива. */ int new_limit = sample_limit + SAMPLE_INCREMENT; double *new_array = realloc(samples, new_limit * sizeof(double)); if (new_array — NULL) { /★ Неудачное увеличение объема, остаются прежние элементы. */ fprintf(stderr,"?Добавление элемента: недостаточно памяти\п"); } else { samples = new_array; sample_limit = new_limit; samples[sample_count++] = new_sample; 1 ) return sample count; ) Функция free освобождает область памяти, распределенную функцией mal- loc, calloc или realloc. Эта функция вызывается с аргументом, представляю- щим указатель, возвращенный одной из указанных функций распределения па- мяти. Если это нулевой указатель, функция не выполняет никаких действий. Освобожденная область памяти никак не может использоваться. Результат ис- пользования ссылки на такую область (висячего указателя) непредсказуем, как и двойная попытка освобождения распределенной области. В автономных конфигурациях с ограниченным объемом памяти програм- мист обладает возможностью непосредственного управления распределением памяти при помощи функции malloc и других. Динамически распределяемая память в этом случае именуется heap (куча). Во многих программах С, предна- значенных для автономной среды, функция malloc не используется, а потому в динамической памяти нет надобности. Определение объема динамически распределяемой памяти зависит от реализации. Ссылки: преобразования при присваивании 6.3.2. 16.1.1 Традиционные средства распределения памяти Краткий перечень традиционных и альтернативных средств char *malloc(unsigned size); char *mlalloc (unsigned long size); char *calloc(unsigned elt_count, unsigned elt_size); char *clalloc(unsigned long elt_count, unsigned long elt_size); void free (char *ptr); void cfree (char *ptr) ; char ‘realloc(char *ptr, unsigned size); char *relalloc(char *ptr, unsigned long size);
418 Глава 16 В традиционных реализациях С заголовочные файлы для этих средств, как правило, отсутствуют, поэтому программистам приходится самим объявлять нужные функции. Аргумент size функций распределения памяти первоначально имел тип unsigned int. Однако этот тип, зачастую, оказывался слишком мал, поэтому пришлось создать новые версии функций распределения памяти с аргументом size типа unsigned long. Эти функции возвращают значения типа char *, при- водимые явным образом к типу соответствующего объекта. Традиционная функция free освобождает область памяти, распределенную функцией malloc, mlalloc, realloc или relalloc, функция cfree — память, рас- пределенную функцией calloc или clalloc. Результат вызова функции free или cfree с нулевым указателем в традиционных реализациях определяется этими реализациями. 16.2 rand, srand, RAIMD_MAX Краткий перечень ttinclude <stdlib.h> int rand(void); void srand(unsigned seed); tfdefine RAND MRX . . Серия обращений к функции rand дает последовательность псевдослучай- ных целых значений (в интервале от 0 до максимального значения соответст- вующего типа включительно). В Standard С верхняя граница значений, воз- вращаемых функцией rand, задается значением RAND_MAX, которое не мо- жет быть меньше 32 767. Перед использованием функции rand генератор псевдослучайных чисел можно инициализировать, воспользовавшись функцией srand. Инициализа- ция генератора с одним и тем же значением параметра функции srand приво- дит к генерированию идентичных псевдослучайных последовательностей. По- следовательность псевдослучайных чисел, генерированная функцией rand без инициализации генератора, идентична полученной после инициализации функцией srand со значением аргумента, равным 1. Стандартные библиотечные функции Standard С не обращаются к функци- ям rand и srand каким бы то ни было образом, позволяющим влиять на про- граммируемое генерирование последовательности псевдослучайных чисел. 16.3 atof, atoi, atol, atoll Краткий перечень ttinclude <stdlib.h> double atof ( const char *str ) ; int atoi ( const char ‘str ); long atol ( const char *str ); long long atoll( const char ‘str ); И C99
Утилиты общего назначения 419 Эти функции, преобразующие начало строки str в численное значение, оп- ределены во многих реализациях UNIX. В Standard С они определены для со- вместимости в форме функций strtox (см. раздел 16.4). Следует отдавать пред- почтение именно этим функциям; если же обращение к ним не дает нужного результата, их поведение не определено. Если не считать поведение при возникновении ошибок, эти функции дейст- вуют аналогично своим менее специализированным двойникам: ((include <stdlib.h> double atof(const char *str) ( return strtod(str, (char **) NULL); } int atoi(const char *str) { return (int) strtol(str, (char **) NULL, 10); } long atol(const char * str) ( return strtol(str, (char **) NULL, 10); } long long atoll(const char * str) ( return strtol(str, (char **) NULL, 10); } 16.4 strtod, strtof, strtold, strtol, strtoll, strtoul, strtoull Краткий перечень ((include <stdlib.h> double strtod(const char * restrict str, char ** restrict ptr); float strtof(const char * restrict str, char ** restrict ptr); long double strtold(const char * restrict str, char ** restrict ptr); long strtol(const char * restrict str, char ** restrict ptr, int base); long long strtoll ( const char * restrict str, char ** restrict ptr, int base ); unsigned long strtoul( const char * restrict str, char ** restrict ptr, int base ); unsigned long long strtoull( const char * restrict str, char ** restrict ptr, int base ); Функции преобразования строковых значений в численные strtod и strtol были заимствованы в Standard С из UNIX System V. Для полноты в С89 опре- делена функция strtoul. Функции strtof, strtold, strtoll и strtoull были введе- ны в С99. В целом эти функции обеспечивают большую управляемость преоб- разования, чем, к примеру, семейство sscanf. Кроме этого, в С99 определены функции strto[u]imax (раздел 21.8). Аргумент str каждой из этих функций указывает на преобразуемую строку, ptr (если это не нулевой указатель) — на указатель типа char *, устанавливае- мый этими функциями на первый символ строки str, следующий непосредст- венно за преобразованной ее частью. Если ptr — нулевой указатель, он игно- рируется. Если str начинается с пробельных символов (определяемых функци- ей isspace), эти символы пропускаются перед преобразованием.
420 Глава 16 Существуют версии перечисленных функций, предназначенные для преоб- разования широких символов (см. разделы 24.4 и 21.9). Преобразование чисел с плавающей точкой Функции преобразования чисел с плавающей десятичной запятой (точкой) strtod, strtof и strtold преобразуют значения, состоящие из необязательного зна- ка «плюс» или «минус», за которым следует одна из следующих конструкций: 1. последовательность десятичных цифр — возможно, с одной десятичной точкой, — за которой следует экспоненциальный множитель (описание его дано в разделе 2.7.2); 2. символ Ох или ОХ, за которым следуют непустая последовательность шестнадцатеричных цифр и необязательный двоичный экспоненциаль- ный множитель, описание которого дано в разделе 2.7.2; 3. строка INF или INFINITY (независимо от регистра); 4. строка NAN или NAN(...), независимо от регистра, где "..." — любая последовательность букв, цифр и символов подчеркивания. Самая длинная последовательность символов, соответствующая одной из перечисленных конструкций, преобразуется в значение с плавающей точкой, которое и возвращается. Тип возвращаемого значения зависит от того, какая из функций вызвана. Формат получаемого численного значения отличается от собственного синтаксиса С для констант с плавающей точкой (раздел 2.7.2) тем, что может быть указан необязательный знак + или —, не требуется деся- тичной точки (вместо нее может быть указан другой знак, в зависимости от географической установки) и отсутствует какой бы то ни было суффикс значе- ния с плавающей точкой (f, F, 1 или L). Если преобразование оказывается невозможным из-за того, что строка не соответствует ни одной из числовых моделей (либо просто пуста), возвращает- ся нулевое значение, аргументу *ptr присваивается значение str, а в перемен- ную еггпо записывается значение ERANGE. Если при преобразовании проис- ходит переполнение, возвращается значение HUGE_VAL, HUGE_VALF или HUGE_VALL (с соответствующим знаком). Если при преобразовании происхо- дит потеря значимости, возвращается нуль. При переполнении либо потере значимости в переменную еггпо записывается значение ERANGE. Это не поз- воляет отличать неправильное численное значение от ситуации потери значи- мости, кроме как по значениям, устанавливаемым в *ptr. В некоторых тради- ционных реализациях, если строка не соответствует ни одной числовой моде- ли, в переменную еггпо записывается значение EDOM. Преобразование шестнадцатеричных значений с плавающей точкой, беско- нечности и NaN с помощью strtod впервые были определены в С99. Строки INF и INFINITY интерпретируются именно как бесконечность. Если тип воз- вращаемого значения не содержит представления бесконечности, соответст- вующая ситуация трактуется как переполнение. Строки NAN и NAN(...) обо- значают тихое (quiet) NaN. Если NaN непредставимо в возвращаемом значе- нии, исходное значение рассматривается как непреобразуемое. Если географический параметр не равен "С", возможно принятие дополни- тельных форматов преобразования значений с плавающей точкой. Преобразование целых Функции целочисленных преобразований strtol, strtoll, strtoul и strtoull преобразуют начало строки аргумента, соответственно, в целые типы long int,
Утилиты общего назначения 421 long long int, unsigned long int или unsigned long long int. Требуемый формат входной строки (в зависимости от основания системы счисления base) одина- ков во всех случаях, включая необязательный знак + или —. Какие бы то ни было суффиксы (1, L, и или U) отсутствуют. Если указано основание 0, число (после необязательного знака) должно быть в формате десятичной, восьмеричной или шестнадцатеричной константы. Ос- нование системы счисления выводится из формата числа. Если указано основа- ние от 2 до 36 включительно, число должно состоять из непустой последова- тельности букв и цифр, представляющих число в соответствующей системе счисления. Букв от а до z (или от А до Z) используются для представления зна- чений от 10 до 35. Допустимо использование только букв, представляющих зна- чения меньше основания. Особый случай — основание 16, при котором число может начинаться (после знака) с символов Ох или ОХ, которые игнорируются. Если преобразование оказывается невозможным, каждая из функций воз- вращает нуль, аргументу *ptr присваивается значение str, в еггпо записывает- ся значение ERANGE. При переполнении преобразуемого числа функции воз- вращают LONG_MAX, LONG_MIN, LLONG_MAX, LLONG_MIN, ULONG_ MAX или ULLONG_MAX (в зависимости от типа и знака возвращаемого зна- чения); в еггпо записывается значение ERANGE. Если географический параметр не равен "С", возможно принятие дополни- тельных целочисленных форматов. Ссылки: десятичная константа 1J-, еггпо 11.2; константа с плавающей точкой 2.7; шестна- дцатеричная константа 2.7; HUGE_VAL гл. 17; целая константа 2.7; функция isspace 12.6; LONGMAX, LONG_MIN, ULONGMAX 5 11; NaN 5 2; восьмеричная константа 2.7; мар- кер типа 1J. 16.5 abort, atexit, exit, .Exit, EXITFAILURE, EXIT_SUCCESS Краткий перечень ftinclude <stdlib.h> #define EXIT_FAILURE ... #define EXIT_SUCCESS . . . void exit (int status); void _Exit(int status); // C99 void abort(void); int atexit(void (*func)(void)); Функции exit, _Exit и abort завершают выполнение программы. Управле- ние не передается вызывающей функции. Функция exit выполняет нормальное завершение программы со следующи- ми операциями «очистки»: 1. (Только Standard С). Вызываются все функции, зарегистрированные функцией atexit, в порядке, обратном порядку их регистрации; каждая функция вызывается столько раз, сколько раз она была зарегистриро- вана. 2. Все выходные потоки очищаются, все открытые — закрываются.
422 Глава 16 3. Файлы, созданные функцией tmpfile, удаляются. 4. Управление возвращается среде, в которой программа была запущена, со значением состояния. По соглашению, принятому во многих системах, успешное завершение про- граммы отмечается значением состояния 0, тогда как ненулевые значения со- ответствуют различным ситуациям аварийного завершения. В Standard С ус- пешное завершение отмечается значением 0 либо возвращаемым макросом EXIT_SUCCESS, аварийное — значением макроса EXIT_FAILURE; смысл ос- тальных значений зависит от реализации. Возврат целого значения функцией main действует подобно вызову функции exit с этим же значением. Функция _Exit отличается от exit тем, что не обращается к обработчикам выхода, зарегистрированным функцией atexit, как и к обработчикам сигна- лов, зарегистрированным функцией signal. Выполнение остальных завершаю- щих операций — например, закрытие потоков, — зависит от реализации. Функция _Exit введена в С99; в некоторых реализациях подобные функции могут существовать под именем _exit. Функция abort завершает программу аварийно. Обращения к функциям, зарегистрированным atexit, не происходит. Выполнение других завершаю- щих операций зависит от реализации. Значение, возвращаемое системе, зави- сит от реализации, но должно обозначать аварийность завершения. В Standard С и множестве традиционных реализаций обращение к функции abort транс- лируется в специальный перехватываемый сигнал (в Standard С — SIGABRT). Если этот сигнал игнорируется либо происходит возврат управления из обра- ботчика, в реализациях Standard С выполнение, тем не менее, завершается; другие реализации могут разрешать возврат управления вызывающей функ- ции. Функция abort может вызываться также при ошибках диагностики (раз- дел 19.1). Функция atexit введена в Standard С. Она регистрирует функции, которые должны вызываться при вызове функции exit или возврате управления функ- цией main. Эти функции не вызываются при аварийном завершении програм- мы — например, при помощи функции abort или raise. Любая реализация должна предусматривать регистрацию не менее 32 функций. Функция atexit возвращает нуль в случае успешной регистрации, иначе — ненулевое значе- ние. Возможности отмены регистрации функции не существует. Зарегистриро- ванные функции вызываются в порядке, обратном порядку их регистрации, до выполнения функцией exit любых завершающих операций. Каждая из этих функций вызывается без аргументов и имеет тип возвращаемого значения void. Зарегистрированная функция не должна обращаться к объектам с клас- сом памяти auto или register (например, через указатель), за исключением оп- ределенных ею самой. Многократная регистрация функции означает много- кратный же ее вызов. В некоторых реализациях традиционного С подобные средства реализованы под именем onexit. Пример В следующем примере функция main открывает файл и регистрирует функции за- вершения, которые этот файл закроют в случае обращения к функции exit. (Функ- ция exit закрывает все файлы, но программисту может понадобиться закрыть этот файл первым.) ((include <stdlib.h> ((include <stdio.h> ((include <assert.h>
Утилиты общего назначения 423 FILE *Open_File; void cleanup(void) ( if (Open_File •= NULL) fclose(Open_File); ) int main (void) { int status; Open_File = fopen("out.dat","w") ; status = atexit(cleanup); assert(status — 0); ) Ссылки: assert 19.1; fflush 15.2; atexit 19.5; функция main 9.9; raise 19.6; оператор return 8.9; signal 19.6; tmpfile 15.16; тип void 5.9. 16.6 getenv Краткий перечень #include <stdlib.h> char * getenv( const char ‘name ); Функция getenv вызывается с одним аргументом — указателем на строку, интерпретируемую некоторым зависящим от реализации образом как имя, по- нятное для среды, в которой она выполняется. Функция возвращает указатель на другую строку, представляющую «значение» аргумента name. Если аргу- мент не содержит значения, возвращается нулевой указатель. Программиро- вать модификацию возвращаемой строки нельзя, но при следующем обраще- нии к функции getenv поверх нее может быть записана новая. В традиционном С связки (имя, значение) могут быть доступны функции main в форме параметра env функции main (раздел 9.9). Переменная среды ус- танавливается, обычно, функцией setenv. 16.7 system Краткий перечень ftinclude <stdlib.h> int system( const char *conunand ); Функция system передает строковый аргумент командному процессору (или оболочке) операционной системы для выполнения некоторым зависящим от реализации образом. Значение, возвращаемое функцией system, также зави- сит от реализации, но чаще всего это код завершения команды. В Standard С функция system может вызываться с нулевым аргументом; в этом случае она возвращает 0, если командный процессор в системе отсутствует, иначе — не- нулевое значение.
424 Глава 16 16.7.1 exec Краткий перечень традиционного С execl (char ‘name, char *argi, . .., NULL); execlp(char ♦name, char *argi, . .., NULL); execle(char ♦name, char *argi, . ... NULL, char *envp[]) execv (char ♦name, char *argv[ ] ) execvp(char ♦name, char *argv[ ] ) execve(char ♦name, char *argv[], char ‘envp [ ]) ; Существуют формы функции ехес, не входящие в Standard С. Это, главным образом, функции из систем UNIX. Каждая из них преобразует текущий про цесс в новый, запустив программу, располагающуюся в файле name. Каждая из функций отличается набором аргументов, описывающих новый процесс. 1. execl, execlp и execle — функции с переменным числом аргументов, по- следний из которых должен быть нулевым указателем. Первым аргу- ментом должен быть name — имя запускаемой программы. 2. Функции execv, execvp и execve используют указатель на массив аргу- ментов, оканчивающийся нулевым значением, который передается функции main. Элемент argv[O] должен совпадать с аргументом name — именем запускаемой программы. 3. Функции execle и execve также указывают явным образом «среду» для нового процесса. Параметр envp — массив строковых указателей, окан- чивающийся нулевым значением. Каждая строка имеет форму "имя= значение". (В других версиях ехес указатель среды вызывающего про- цесса передается новому процессу неявным образом.) 4. Функции execlp и execvp аналогичны функциям, соответственно, execl и execv, однако здесь система ищет нужный файл в некотором наборе каталогов, содержащих программы (как правило, задаваемом значени- ем переменной среды path или PATH). После запуска нового процесса аргументы, указанные функции ехес, пере- даются его функции main (раздел 9.9). 16.8 bsearch, qsort Краткий перечень ftinclude <stdlib.h> void *bsearch( const void *key, const void ‘base, size t count, size_t size, int (‘compar)(const void *the_key, const void *a_value)); void qsort( void ‘base, size_t count, size_t size, int (‘compar)(const void ‘elementl, const void *element2) );
Утилиты общего назначения 425 Функция bsearch выполняет поиск в массиве из count элементов (каждый размером в size символов), на первый из которых ссылается указатель base. Аргументы функции сошраг — это указатели на ключ и на элемент массива; функция возвращает отрицательное, нулевое или положительное значение, в зависимости от того, оказывается ли значение ключа, соответственно, мень- ше, равным или больше значения элемента. Перед просмотром массив должен быть отсортирован в возрастающей последовательности (соответственно функ- ции сошраг). Функция bsearch возвращает указатель на элемент массива, со- ответствующий ключу, или нулевой указатель, если такой элемент не найден. Функция qsort сортирует массив из count элементов, на первый элемент ко- торого ссылается аргумент base. Размер каждого элемента в символах указы- вается аргументом size. Функция сошраг вызывается с аргументами, пред- ставляющими указатели на два элемента, и возвращает -1, если первый эле- мент «меньше» второго, 1, если первый элемент «больше» второго, и 0, если оба элемента «равны». Массив сортируется в возрастающей последовательно- сти (соответственно функции сошраг). Начало и конец каждого вызова сошраг в указанных функциях помечают- ся специальными маркерами. Пример Функция fetch использует bsearch для поиска в сортированном массиве структур Table. Проверка значений ключей выполняется функцией key_compare. Обратите внимание, что fetch, прежде всего, вставляет ключ в фиктивный (дополнительный) элемент (key_elem); это позволяет использовать key_compare как в bsearch, так и в qsort (раздел 20.6): «include <stdlib.h> «define COUNT 100 struct elem {int key; int data; } Table[COUNT]; int key_compare(const void * el, const void * e2) ( int vl = ((struct elem *)el)->key; int v2 = ((struct elem *)e2)->key; return (vl<v2) ? -1 : (vl>v2) ? 1 : 0; } int fetch(int key) /* Возврат элемента данных Table, соответствующего ключу, или 0, если ключ не существует. */ ( struct elem «result; struct elem key_elem; key_elem.key = key; result - (struct elem «) bsearch( (void *) skey_elem, (void *) STablefO], (size_t) COUNT, sizeof(struct elem), key_compare); if (result = NULL) return 0; else return result->data; ) Пример В функции sort_table, qsort используется для сортировки таблицы из предыдущего примера. Для сравнения элементов используется та же функция key_compare:
426 Глава 16 void sort_table(void) /* Сортировка Table по значениям ключей */ { qsort( (void *)Table, (size_t) COUNT, sizeof(struct elem), key_compare ); ) 16.8.1 Формы традиционного С Сигнатуры функций bsearch и qsort в традиционном С имеют вид: char *bsearch( char *key, char ‘base, unsigned count, int size, int (‘compar)( char *the_key, char *a_value)); void qsort( char ‘base, unsigned count, int size, int (‘compar)( char *elementl, char *element2)); 16-9 abs, labs, llabs, div, Idiv, lldiv Краткий перечень If include <stdlib.h> int abs(int X); long labs(long int X) ,- long long llabs(long long int x); // C99 typedef div_t; typedef ... ldiv_t; typedef .,. lldiv t, // C99 div_t div(int n, int d); ldiv_t Idiv(long n, long d); lldiv_t lldiv(long long n, long long d); // C99 Функции этого раздела выполняют арифметические операции с целыми числами. В Standard С они объявлены в заголовочном файле stdlib.h, в тради- ционном — в math.h. Каждая из функций abs, labs и llabs (последняя — в С99) возвращает абсолютное значение аргумента. Функции отличаются друг от друга только типами аргументов и возвращаемых значений. Абсолютные значения аргументов с плавающей точкой вычисляются семейством фукнций fabs (заголовочный файл math.h), целочисленные максимальные значения —
Утилиты общего назначения 427 функцией imaxabs, объявленной в inttypes.h. Функции вычисления абсолют- ных значений настолько просты в реализации, что некоторые компиляторы трактуют их как встроенные, что допускается в Standard С. Три функции div, Idiv и Ildiv (последняя — в С99) вычисляют одновремен- но частное и остаток деления п на d. Эти функции отличаются друг от друга только типами аргументов и возвращаемых значений. Типы div_t, ldiv_t и lldiv_t (последний — в С99) — это структуры из двух составляющих — quot (частное) и rem (остаток, порядок следования не определен), — соответствен- но, типов int, long int и long long int. Возвращаемое значение частного quot — то же, что получается в операции n/d, остаток rem — то же, что n%d. Поведе- ние функций в случае нулевого d, либо если частное или остаток не могут быть представлены в предписанных типах, не определено (это не обязательно ошиб- ка выхода за пределы допустимых значений), что позволяет найти в каждой реализации наилучшее решение. Для деления максимальных целых значений предназначена функция imaxabs из inttypes.h. Функции деления эффективны благодаря тому, что большинство компью- теров могут вычислять частное и остаток одновременно, что, конечно же, бы- стрее раздельного выполнения операций / и %. Ссылки: tabs 17.2; imaxabs 21.7; imaxdiv 21.7 16.10 mblen, mbtowc, wctomb Краткий перечень ttinclude <stdlib.h> typedef ... wchar_t; #define MB_CUR_MAX ... int mblen(const char *s, size_t n) ; int mbtowc(wchar_t *pwc, const char *s, size_t n); int wctomb(char *s, wchar t wchar); В Standard С есть поддержка обработки расширенных наборов символов, за- висящих от географической установки и слишком больших, чтобы можно было представить их при помощи одного объекта типа char. Для таких наборов в Standard С определена схема внутреннего и внешнего представлений. Считает- ся, что внутренне код символа расширенного набора совпадает с кодом широко- го символа — то есть объектом целочисленного типа wchar_t, зависящим от реализации. Строки символов из расширенных символьных наборов (строки широких символов) могут быть представлены как объекты типа wchar_t[J. Внешне отдельный широкий символ может быть представлен в виде последова- тельности обычных символов — как многобайтовый символ, соответствующий данному широкому. Обсуждение многобайтовых и широких символов см. в раз- деле 2.1.5, символьных наборов и кодировок — в разделе 2.9. Набор функций преобразования символов, описываемых в этом разделе, был расширен в Дополнении 1 к С89 за счет добавления новых функций мно- гократного запуска, в числе которых mbrlen, btowc, wctob, mbrtowc и wcrtomb. Новые функции более гибки и управляемы. Они объявлены в заго- ловочном файле wchar.h, описание их дано в разделе 24.2.
428 Глава 16 16.10.1 Кодировка и состояния преобразования В этом разделе мы рассмотрим некоторые характеристики преобразований из многобайтовых символов в широкие и обратно, используя терминологию, которая применима ко многим функциям, обсуждаемым в этой главе. Мы не будем выделять особо какие бы то ни было отдельные представления широких либо многобайтовых символов, обратив лишь внимание на особую роль в любом представлении нулевого символа окончания строки '\0'. Много- байтовая кодировка, в общем случае, зависит от состояния — то есть смысл от- дельных символов в ней может зависеть от предшествующих. Исходные варианты функций Standard С, рассматриваемые в этой главе, сохраняют информацию о внутреннем состоянии, соответствующем преобразо- ванию последнего символа. В новых функциях, введенных в Дополнении 1, для сохранения состояния предусмотрен тип mbstate_t, что позволяет обраба- тывать несколько строк одновременно. Однако если значение нового аргумен- та состояния не определено, каждая функция использует собственное внутрен- нее состояние. Никакие иные функции не могут влиять на внутреннее регист- ровое состояние. Максимальное число байт, необходимых для представления многобайтово- го символа при текущей географической установке, задается (неконстантным) выражением MB_CUR_MAX. Большинство функций, в которых в качестве аргумента используется указатель s на многобайтовый символ, принимают также целое значение п, указывающее максимальное число обрабатываемых байтов. Конечно же значение п не должно быть больше MB_CUR_MAX, но оно может быть меньше, что ограничит число преобразуемых символов. При наличии текущего состояния преобразования, указателя s на много- байтовый символ и длины п, возможны следующие варианты: 1. Некоторое число байт, не более и, начиная с байта, на который указы- вает аргумент s, могут образовать правильный многобайтовый символ, соответствующий одному широкому символу wc. Соответственно долж- но быть изменено состояние преобразования. Если wc окажется нуле- вым широким символом, можно сказать, что таковым является аргу- мент s. 2. Все и байт, обозначенные аргументом s, могут составлять лишь начало правильного многобайтового символа, который не удается определить полностью. В этом случае, s представляет неполный многобайтовый символ. При значении п не меньше MB_CUR_MAX, эта ситуация мо- жет возникнуть, если s содержит избыточные символы смены регистра. 3. п байт, начиная с s, могут образовать неправильный многобайтовый символ — то есть эти байты не могут образовать, в текущей кодировке, ни правильный, ни неполный многобайтовый символ. Изменение категории LC_CTYPE географической установки (раздел 20.1) может привести к изменению кодировки символов и сделать регистровое со- стояние неопределенным. Значение MBCURMAX должно определять доста- точно места для размещения символов смены регистра. Ссылки: mbstate 1 24 1
Утилиты общего назначения 429 16.10.2 Функции длины Функция mblen проверяет до и байтов строки, начиная с байта, обозначен- ного аргументом s, чтобы определить, составляют ли эти байты правильный многобайтовый символ соответственно текущему регистровому состоянию. Если они составляют такой символ, функция возвращает число байт, его со- ставляющих, иначе — значение -1. Если s — нулевой указатель, а кодировка многобайтовых символов, соответственно географической установке, зависит от состояния, mblen возвращает ненулевое значение. Как побочный эффект, выполняется сброс в исходное внутреннее состояние. 16.10.3 Преобразование в широкие символы Функция mbtowc преобразует многобайтовый символ s в широкий символ в соответствии с внутренним состоянием преобразования. Если аргумент pwc — не нулевой указатель, результат записывается в объект, обозначенный этим аргументом. Функция возвращает число байтов, образующих многобай- товый символ. Если s — неполный или неправильный многобайтовый символ, функция возвращает -1. Если s — нулевой указатель, а кодировка многобай- товых символов, соответственно географической установке, зависит от состоя- ния, mbtowc возвращает ненулевое значение. Как побочный эффект, выполня- ется сброс в исходное внутреннее состояние. Пример Ниже приведен пример реализации функции mbstowcs (раздел 16.11), в которой ис- пользуется обращение к функции mbtowc: #include <stdlib.h> size_t mbstowcs (wchar__t *pwcs, const char *pmbs, size__t n) { size__t i = 0; /* индекс выходного массива */ (void) mbtowc(NULL,NULL,0); /* Исходное состояние регистра */ while (*pmbs && i < n) { int len = mbtowc(&pwcs[i++], pmbs,MB_CUR_MAX); if (len == -1) return (size_t) -1; pmbs += len; /* перейти к следующему многобайтовому символу */ ) return i; } Ссылки: mbstate_t 24.1; многобайтовые символы 2.1.5; size._» 11.1; WEOF 11.1. 16.10.4 Преобразование из широких символов Функция wctomb преобразует широкий символ wc в многобайтовое пред- ставление (соответственно текущему регистровому состоянию) и записывает результат в массив символов, обозначенный указателем s. Массив должен иметь длину не менее MB_CUR_MAX символов. Меняется состояние преобра- зования, нулевой символ в конце не добавляется. В случае правильной коди- ровки символов функция возвращает число символов, записанных в s, ина- че — -1. Если s — нулевой указатель, а кодировка многобайтовых символов, соответственно географической установке, зависит от состояния, wctomb воз-
430 Глава 16 вращает ненулевое значение. Как побочный эффект, выполняется сброс в ис- ходное внутреннее состояние. 16.1*1 mbstowcs, wcstombs Краткий перечень #include <stdlib.h> size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n); size t wcstombs(char *s, const wchar t *pwcs, size t n): Функции Standard С, рассмотренные в этом разделе, выполняют преобразо- вание из строк широких символов в последовательности многобайтовых и об- ратно. В Дополнении 1 к С89 определены многократно запускаемые версии этих функций mbsrtowcs и wcsrtombs, объявленные в заголовочном файле wchar.h (см. раздел 24.3). 16.11.1 Преобразование в строки широких символов Функция mbstowcs преобразует последовательность многобайтовых симво- лов в строке s, оканчивающейся нулевым символом, в соответствующую по- следовательность широких символов, записываемую в массив, обозначенный указателем pwcs. Многобайтовые символы в s должны начинаться в исходном регистровом состоянии и оканчиваться нулевым символом. Каждый многобай- товый символ, до нулевого символа в конце включительно, преобразуется как в функции mbtowc. Процесс преобразования заканчивается после записи в массив п широких символов, достижения конца s (в этом случае в выходной массив записывается также нулевой широкий символ), или в результате ошиб- ки (в зависимости от того, какое из этих событий произойдет первым). Функ- ция возвращает число широких символов, записанных в массив (без учета ну- левого широкого символа в конце) или — в случае ошибки, — значение -1 (приведенное к типу sizet). Если функция на выходе устанавливает pwcs как нулевой указатель, сим- волы в выходной массив не записываются, аргумент длины и игнорируется. Если pwcs — не нулевой указатель и в выходной массив оказываются запи- санными п широких символов, процесс преобразования входной строки много- байтовых символов прекращается до обработки нулевого символа в конце строки. В этом случае указатель, обозначенный src, устанавливается на адрес, следующий за адресом многобайтового символа, преобразованного последним. Происходит изменение состояния преобразования (не обязательно возврат в исходное состояние) и функция возвращает значение п. Преобразование входной строки многобайтовых символов может прекра- титься раньше времени также в случае ошибки преобразования. В этом случае указатель, обозначенный src, переводится на многобайтовый символ, попытка преобразования которого привела к ошибке. Функция возвращает значение 1 (приведенное к типу size_t), в переменную errno записывается значение EILSEQ, состояние преобразования оказывается неопределенным.
Утилиты общего назначения 431 16.11.2 Преобразование из строк широким символов Функция wcstombs преобразует последовательность широких символов, на- чиная со значения, обозначенного указателем pwcs, в последовательность мно- гобайтовых символов, записываемую в символьный массив, обозначенный ука- зателем s. Каждый широкий символ преобразуется как в функции wctomb. По- следовательность входных широких символов должна оканчиваться нулевым широким символом. Выходная последовательность многобайтовых символов будет начинаться в исходном регистровом состоянии. Преобразование прекра- щается, когда в массив s оказываются записанными и символов, когда достига- ется конец массива pwcs (в этом случае в конец массива s добавляется нулевой символ) или в случае ошибки преобразования (в зависимости от того, какое из этих событий произойдет первым). Функция возвращает число широких симво- лов, записанных в массив (без учета нулевого широкого символа в конце) или — в случае ошибки, — значение -1 (приведенное к типу size_t). Если функция на выходе устанавливает s как нулевой указатель, записи данных в выходной массив не происходит, а аргумент длины п игнорируется. Если s — не нулевой указатель и в выходной массив оказываются записан- ными п байт, процесс преобразования входной строки широких символов пре- кращается до обработки нулевого символа в конце строки. В этом случае ука- затель, обозначенный src, устанавливается на адрес, следующий за адресом широкого символа, преобразованного последним. Происходит изменение со- стояния преобразования (не обязательно возврат в исходное состояние) и функция возвращает значение и. Преобразование входной строки широких символов может прекратиться раньше времени также в случае ошибки преобразования. В этом случае указа- тель, обозначенный src, переводится на широкий символ, попытка преобразо- вания которого привела к ошибке. Функция возвращает значение -1 (приве- денное к типу size_t), в переменную еггпо записывается значение EILSEQ, со- стояние преобразования оказывается неопределенным. Пример В следующем примере происходит считывание строки многобайтовых символов (mbs), преобразование ее в строку широких символов (wcs), затем — обратное преоб- разование в строку многобайтовых символов (mbs2). Заполнение преобразованными символами всего выходного массива считается ошибкой, поскольку в конце необхо- димо записать еще и нулевой символ. «include <stdlib.h> «include <stdio.h> «define MAX_WCS 100 «define MAX_MBS (100*MB_CUR_MAX) wchar_t wcs[MAX_WCS+1]; char mbs[MAX_MBS], mbs2[MAX_MBS]; size_t len_wcs, len_rnbs; /* Считывание многобайтовой строки, проверка ошибок */ if (!fgets(nbs, MAX_MBS, stdin)) abort () ; /★ Преобразование в строку широких символов, проверка ошибок */ len_wcs = mbstowcs(wcs, mbs, MAX_WCS); if (len_wcs = MAX_WCS I I len_wcs — (size_t)-l) abort () ;
432 Глава 16 /* Преобразование в строку многобайтовых символов, проверка ошибок */ len_mbs = wcstombs(mbs2, wcs, MAX_MBS); if (len_mbs == MAX_MBS || len_mbs == (size_t)-l) abort(); Ссылки: состояние преобразования 2.1.5; многобайтовый символ 2.1.5; широкий символ 2.1.5.
Глава 17 Математические функции Функции, описываемые в этом разделе, объявлены в библиотечном заголо- вочном файле math.h. Standard С содержит несколько дополнительных функ- ций, объявленных в stdlib.h. Комплексные математические функции объявле- ны в файле complex.h (С99). Ниже перечислены некоторые общие особенности функций, которые нам предстоит рассмотреть в этой главе. Типы аргументов До появления С99 все библиотечные операции над значениями с плаваю- щей десятичной запятой (точкой) выполнялись только с аргументами типа double. Это происходило даже тогда, когда операция задавалась над значения- ми типа float, вследствие автоматического преобразования таких аргументов в тип double. В С99 определен параллельный набор математических функций с аргументами типов float и long double, имена которых образуются добавле- нием суффиксов, соответственно, f и 1 (эль) к именам исходных функций. Наличие нескольких вариантов математических функций с разными типами аргументов позволяет программисту влиять на эффективность функций и пре- образование типов при их вызове, но за счет мобильности программы. Замена типа переменной с double на long double ведет к необходимости замены имен множества функций — иначе неизбежно возникнут проблемы с точностью, по- скольку аргументы типа long double, соответственно прототипам функций double, преобразуются именно в этот тип. Поэтому в С99 в заголовочном файле tgmath.h, определен набор макросов общего типа (type-generic macros) (раздел 17.12). Эти макросы, имена которых совпадают с именами соответствующих библиотечных функций, вызывают нужную функцию в зависимости от типа пе- реданных аргументов — в точности так, как это происходит в операциях сложе- ния или умножения. Если требуется обращение именно к исходным функциям, применение указанных макросов отменяется директивой #undef (или можно просто не подключать файл tgmath.h). Поскольку написание макросов общего типа в С невозможно, эти макросы встроены в реализацию С99. Обработка ошибок Для математических функций характерны ошибки двух основных видов, которые не во всех реализациях С обрабатываются достаточно основательно. Если входной аргумент не входит в область значений, определенную для дан- ной функции, либо равен одному из специальных значений — например, бес- конечности или NaN, возникает ошибка выхода за пределы допустимых зна-
434 Глава 17 чений (domain error), в переменную еггпо (раздел 11.2) записывается значение EDOM, а функция возвращает значение, которое зависит от реализации. Чаще всего это нуль, но в некоторых реализация может быть значение, которое мож- но считать более осмысленным — например, NaN (не число). Если значение, возвращаемое функцией, не может быть представлено в оп- ределенном ей типе, возникает ошибка переполнения (range error). В этом слу- чае в переменную еггпо записывается значение ERANGE, а функция возвра- щает максимальное значение, возможное в представлении данного типа с тем же знаком, какой должен был бы иметь правильный результат. В С89 это зна- чение макроса HUGE_VAL. В С99 определены два таких макроса — HU- GE_VALF и HUGE VALL, что позволяет отличать условия реальной ошибки от ситуации, когда вычисления можно продолжить, позволив функции воз- вратить значение бесконечности или NaN. Если возвращаемое функцией значение не может быть представлено из-за того, что слишком мало, она должна возвратить нуль; произойдет ли в этом слу- чае запись в переменную еггпо значения ERANGE — зависит от реализации. 17.1 abs, labs, llabs, div, Idiv, lldiv Эти функции объявлены в файле stdlib.h (см. раздел 16.9). 17.2 fabs Краткий перечень ffinclude <math.h> double fabs (double x); float fabsf(float x); // C99 long double fabsl (long double x); // C99 Функции fabs возвращают абсолютное значение аргумента. Функции вычис- ления целых абсолютных значений (abs, labs и llabs) объявлены в файле stdlib.h. Ссылки: abs, labs, llabs 16.9; макросы общего типа 17 12 17.3 ceil, floor, Irint, llrint, Iround, llround, nearbyint, round, rint, trunc Краткий перечень include <math.h> // Все введены в C99, кроме ceil, floor ffdouble ceil (double x); float ceilf (float x); long double ceill (long double x); double floor (double x); float floorf (float x) ; long double floorl {long double x);
Математические функции 435 Краткий перечень double nearbyint (double х); float nearbyintf (float x); long double nearbyintl (long double x); double rint (double x); float rintf (float x); long double rintl (long double x); long int Irint (double x); long int Irintf (float x) ; long int Irintl (long double x); long long int llrint (double x); long long int llrintf (float x); long long int llrintl (long double x) double round (double x); float roundf (float x) ; long double roundl (long double x); long int Iround (double x); long int Iroundf (float x); long int Iroundl (long double x); long long int llround (double x); long long int llroundf (float x); long long int llroundl (long double x); double trunc (double x); float truncf (float x); long double truncl (long double x); Каждая из перечисленных функций вычисляет целое значение, «ближай- шее» к значению аргумента с плавающей точкой. Несмотря на возврат всеми функциями целых значений, у многих из них эти значения принадлежат к типу с плавающей точкой. Причина — в ограниченности диапазона значе- ний целых типов. Все функции этого раздела, кроме функций ceil и floor, вве- дены в С99, и все имеют соответствующие макросы общего типа. Те из функ- ций, которые возвращают значения с плавающей точкой, при вызове со значе- нием аргумента, равным бесконечности, возвращают это же значение (с правильным знаком). • Функции ceil возвращают наименьшее целое, которое больше или равно х. е Функции floor возвращают наибольшее целое, которое меньше или рав- но х. е Функции round возвращают целое значение, ближайшее к х; если х ока- зывается ровно посередине между двумя целыми значениями, функция round возвращает то из них, которое больше по абсолютному значению (то есть расположено дальше от нуля). • Функции trunc возвращают целое значение, ближайшее к х в направле- нии нуля. Таким образом, они действуют как floor(x) при положитель- ных х, как ceil(x) — при отрицательных. е Функции nearbyint возвращают целое значение, ближайшее к х в теку- щем направлении округления (см. fenv.h). е Функции Irint и llrint аналогичны nearbyint, но возвращают округлен- ное значение целого типа. Если округленное значение не может быть представлено в целом типе, результат непредсказуем.
436 Глава 17 • Функции rint аналогичны nearbyint, но генерируют исключение «неточ- ное значение» (inexact), если возвращаемое значение не равно значению аргумента — то есть если аргумент не равен целому значению. Ссылки: направление округления 22.4; макросы общего типа 17.12. 17.4 fmod, remainder, remquo Краткий перечень If include <math.h> // Все, кроме fmod, введены в С99 double fmod (double x, double y); float fmodf (float x, float y); long double fmodl (long double x, long double y); double remainder (double x, double y); float remainderf (float x, float y); long double remainder1 (long double x, long double y); double remquo (double x, double у, int ‘quo); // C99 float remquof(float x, float y, int * *quo); // C99 long double remquol (long double x, long double у, int *quo); Каждая из перечисленных функций возвращает приближенное значение с плавающей точкой остатка деления х/у — то есть значения г = х - п*у для некоторого целого п. Функции отличаются друг от друга способом выбора зна- чения п, но во всех случаях значение г меньше абсолютного значения у. Все функции, кроме fmod, введены в С99, и для каждой определен макрос общего типа. • В функциях fmod значение п определяется как trunc(x/i/). Это означает, что г будет иметь тот же знак, что и х. • В функциях remainder и remquo п определяется как round(x/i/); если х/у оказывается ровно посередине между двумя целыми, выбирается четное. Знак г может не совпадать со знаком х. Функции remquo возвращают те же значения, что и функции remainder, но эни, кроме этого, записывают в аргумент *quo значение, знак которого совпа- дает со знаком х/у и которое сравнимо по модулю 2‘ с величиной частного х/у. Выбор целого значения к зависит от реализации, но это значение всегда боль- пе или равно 3. Следовательно, значение *quo совпадает в младших значащих >итах с частным х/у, из чего можно извлечь пользу в некоторых вычислениях, связанных с предварительной обработкой аргументов и не доступных в биб- тиотеках С. При у равном 0 в любой реализации, соответствующей Standard С, может торождаться ошибка выхода за пределы допустимых значений либо выпол- 1яться возврат функцией пулевого значения. В некоторых из прежних реали- :аций С в этом случае функции возвращают значение х. Несмотря на то, что .начение остатка производно от операции х/у, остаток может быть определен гравильно даже в случае, когда частное не удается представить в заданном ипе. Не следует путать функцию fmod с функцией modf (раздел 17.5), извлекаю- цей целую и дробную части из значения с плавающей точкой. Ссылки: round 17.4, trunc 17.4; макросы общею типа 17 12
Математические функции 437 17.5 frexp, Idexp, modf, scalbn Краткий перечень #include <math.h> // Все, кроме frexp, введены в C99 double frexp (double x, int ‘nptr); float frexpf(float x, int *nptr); long double frexpl(long double x, int *nptr); double Idexp (double x, int n); float Idexpf(float x, int n); long double Idexplflong double x, int n); double modf (double x, double *nptr); float modff(float x, float *nptr); long double modfl (long double x, long double *nptr); double scalbn (double x, int n); float scalbnf(float x, int n) ; long double scalbnl(long double x, int n); double scalbln (double x, long int n); float scalblnf(float x, long int n); long double scalblnl(long double x, long int n); Большинство функций этого раздела введены в С99, все они имеют соответ- ствующие макросы общего типа. Функции frexp разбивают число с плавающей точкой х на дробную часть f и показатель степени экспоненциального множителя п, так что либо f равно 0.0, либо 0.5 < |f| < 1.0 и f*2n равно х. В каждом случае возвращается дробная часть; как побочный эффект, в переменную, обозначенную указателем nptr, записывается значение п. Если значение х равно нулю, оба возвращаемые зна- чения также равны нулю. Если х — не является числом с плавающей точкой, возвращаются неопределенные значения. Функции Idexp обратны функциям frexp; они вычисляют .значение х*2п. Возможна ошибка переполнения. Функции modf разбивают число с плавающей точкой на дробную (/) и це- лую (п) части, так что |f| < 1.0, a f+n равно х. f и п имеют тот же знак, что и х. Каждая из функций возвращает дробную часть; как побочный эффект, в пере- менную, обозначенную указателем nptr, записывается значение п. Имя modf выбрано не совсем удачно — функция вычисляет не модуль числа, а остаток деления двух чисел. Не следует путать функцию modf с функцией fmod (раз- дел 17.3), вычисляющей остаток деления двух чисел с плавающей точкой. Есть сведения, что в некоторых из прежних реализаций С функции modf опре- делены иным образом; это можно проверить, просмотрев документацию по библиотекам С. В С99 функция modf не имеет соответствующего макроса об- щего типа. Функции scalbn и scalbln умножают значение с плавающей точкой х на масштабный коэффициент Ьп. где h равно FLT_RADIX. Считается, что приме- нение этих функций дает выигрыш в скорости по сравнению с обычным вы- числением Ьп и умножением полученного значения на х. Возможна ошибка пе- реполнения.
438 Глава 17 17.6 exp, exp2, expml, ilogb, log, loglO, loglp, Iog2, logb Краткий перечень ffinclude <math.h> // Все, кроме exp, log, loglO, введены в C99 double exp (double x); float expf(float x); long double expl(long double x); double exp2 (double x); float exp2f(float x); long double exp21(long double x); double expml(double x); float expmlf(float x); long double expmil(long double x); double log (double x); float logf(float x); long double logl(long double x); double loglO (double x); float loglOf(float x); long double logl01(long double x); double loglp (double x); float loglpf(float x); long double loglpldong double x) ; double log2 (double x); float log2f(float x); long double log21(long double x); int ilogb (double x); int ilogbf(float x) ; int ilogbl(long double x); Большинство функций этого раздела введены в С99 и имеют соответствую- щие макросы общего типа. Функции ехр вычисляют значение ех, где е — основание натуральных лога- рифмов. Функции ехр2 вычисляют 2х, функции expml — ех—1 (при малых х функция expml дает более точное значение, чем ехр(х)—1). Во всех случаях при больших значениях аргумента может возникать ошибка переполнения. До С99 существовала только функция ехр. Функции log вычисляют натуральный логарифм числа х, функции loglO -- десятичный логарифм, функции log2 — двоичный. При отрицательном х про- исходит ошибка выхода за пределы допустимых значений, при х, равном или близком к нулю — ошибка переполнения (в сторону -<>-•). либо возврат - -*> без ошибки. В некоторых прежних реализациях С нуль трактуется как ошибка выхода за пределы допустимых значений, а функция log определена под име- нем In. До С99 существовали только функции log и loglO. Функции logb и ilogb извлекают показатель степени экспоненциального .множителя из значения аргумента х с плавающей точкой. Вспомним, что бук- вой Ъ обозначается основание представления с плавающей точкой в стандарт- ной модели, и что это основание объявлено как FLT_RADIX в файле float.h. Нормализация аргумента х не требуется. Функции logb возвращают целый по- казатель степени экспоненциального множителя в форме числа с плавающей
Математические функции 439 точкой; если х равно 0, возможна ошибка выхода за пределы допустимых зна- чений. Функции ilogb возвращают показатель степени в форме значения цело- го типа, как при приведении возвращаемого значения logb к типу int, за ис- ключением следующих случаев: если х равно 0, ilogb возвращает FP_ILOGB; если х равно °° или -«>, ilogb возвращает INT_MAX; если х равно NaN, ilogb возвращает FP_ILOGBNAN. Ссылки: представление с плавающей тачкой 5.2; FLT_RADIX 5.2; макросы общего ти- па 17.12. 17.7 cbrt, fma, hypot, pow, sqrt Краткий перечень #include <math.h> // Все, кроме pow, sqrt, введены в С99 double float long double double float long double double float long double double float long double double float long double cbrt (double x); cbrtf(float x); cbrtl(long double x); hypot (double x, double y); hypotf(float x, float y); hypotl(long double x, long double y); fma (double x, double у, double z); fmaf(float x, float y, float z); fmal(long double x, long double y, long double z); pow( double x, double y); powf(float x, float y); powl(long double x, long double y); sqrt (double x); sqrtf(float x) ; sqrtlflong double x); Функции pow вычисляют xy. Если при ненулевом х, у равно нулю, функ- ция возвращает 1.0. Если аргумент х равен нулю, а у положителен, функция возвращает нуль; если х отрицателен, а у не равен целому значению, или х ра- вен нулю, а у равен нулю или отрицателен, возникает ошибка выхода за преде- лы допустимых значений. Возможна также ошибка переполнения. Функции hypot вычисляют квадратный корень из выражения х2+у2. Они более умело управляются с переполнением и потерей значимости, чем это де- лает программист, задающий такую операцию в обычной форме. Функции fma вычисляют (х * у) + г. Вычисление выполняется как бы с бесконечной точностью, и лишь возвращаемое значение округляется до точ- ности соответствующего типа. Функции sqrt вычисляют квадратный корень из неотрицательного значе- ния х. В случае отрицательного х возникает ошибка выхода за пределы допус- тимых значений. Функции cbrt вычисляют кубический корень из х. Ссылки: макросы общего типа 17.12. 17.8 rand, srand, RAND_MAX Эти функции объявлены в файле stdlib.h (см. раздел 16.2).
440 Глава 17 17.9 cos, sin, tan, cosh, sinh, tanh Краткий перечень ((include <math.h> double float long double cos (double x); cosf(float x); // C99 cosl(long double x) ; // C99 double float long double sin (double x); sinf(float x); // sinl(long double C99 x) ; // C99 double float long double tan (double x); tanf(float x); // tanl(long double C99 x) ; II C99 double float long double cosh (double x); coshf(float x); / / coshl(long double C99 x) ; // C99 double float long double sinh (double x); sinhf(float x); // sinhl (long double C99 x) ; // C99 double float long double tanh (double x); tanhf(float x) ; // tanhl (long double C99 x) ; И C99 Функции cos вычисляют тригонометрический косинус аргумента х, кото- рый должен быть указан в радианах. Ошибки выхода за пределы допустимых значений или переполнения не возникают, но значимость результата падает с ростом х. Функции sin и tan — это, соответственно, тригонометрические функции си- нуса и косинуса. В функции tan возможно возникновение ошибки переполне- ния, когда аргумент является нечетным кратным л/2. Приведенное выше пре- достережение относительно больших значений аргументов касается также функций sin и tan. Функции cosh, sinh и tanh вычисляют, соответственно, гиперболические ко- синус, синус и тангенс аргумента х. При больших абсолютных значениях аргу- мента функции sinh или cosh возможно возникновение ошибки переполнения. Ссылки: макросы общего типо 17.12. 17.10 acos, asin, atan, atan 2, acosh, asinh, atanh Краткий перечень ((include <math.h> // Все, кроме acos, asin, atan, atan2, введены в C99 double acos (double x); float acosf(float x) ; long double acosl(long double x); double asin (double x) ; float asinf(float x) ; lang double asinl(long double x);
Математические функции 441 Краткий перечень double atan(double х); float atanf(float x) ; long double atanl(long double x); double atan2(double у, double x); float atan2f(float y, float x); long double atan21(long double y, long double x); double acosh (double x); float acoshf(float x); long double acoshl(long double x); double asinh (double x); float asinhf(float x); long double asinhlflong double x); double atanh (double x); float atanhf(float x) ; long double atanhl(long double x); Функции acos вычисляют главное значение арккосинуса аргумента х, вы- раженное в радианах и находящееся в интервале между Ойл. Область значе- ний этих функций из-за ошибок округления определяется приближенно. При значении аргумента меньше -1.0 или больше 1.0 возникает ошибка выхода за пределы допустимых значений. Функции asin вычисляют главное значение арксинуса аргумента х, выра- женное в радианах и находящееся в интервале между -л/2 и л/2. При значе- нии аргумента меньше -1.0 или больше 1.0 возникает ошибка выхода за пре- делы допустимых значений. Функции atan вычисляют главное значение арктангенса аргумента х, вы- раженное в радианах и находящееся в интервале между -л/2 и л/2. Ошибка выхода за пределы допустимых значений не возникает. В некоторых ранних реализациях С эта функция именуется arctan. Функции atan2 вычисляют главное значение арктангенса аргумента у/х. Для определения квадранта учитываются знаки обоих аргументов. В декарто- вой системе координат, результат представляется как угол между положи- тельной полуосью х и прямой, проведенной из точки начала координат в точку (х, у). Результат выражается в радианах и находится в интервале между -л и л. При нулевом х, результат равен л/2 или -л/2, в зависимости от того, поло- жительно или отрицательно значение у. Если х и у равны нулю, происходит ошибка выхода за пределы допустимых значений. Функции acosh вычисляют (неотрицательный) гиперболический арккоси- нус аргумента х. Если х<1, происходит ошибка выхода за пределы допусти- мых значений. Функции asinh вычисляют гиперболический арксинус аргумента х. Функции atanh вычисляют гиперболический арктангенс аргумента х. Если х<—1 или х>1, происходит ошибка выхода за пределы допустимых значений. Если х=-1 или х=1, возможна ошибка переполнения. Ссылки: мокросы общего типа 17.12,
442 Глава 17 17.11 fdim, fmax, fmin Краткий перечень #include <math.h> // Все введены в C99 double fdim (double x, double y); float fdimf(float x, float y); long double fdiml(long double x, long double y); double fmax (double x, double y); float fmaxf(float x, float y) ; long double fmaxl(long double x, long double y); double fmin (double x, double y); float fminf(float x, float y); long double fminl(long double x, long double y); Функции fdim вычисляют положительную разность x и у — то есть возвра- щает х-у если х>у, +0 если х<у. Функции fmax определяют больший (в сторону +°°) из двух аргументов, функции fmin — меньший (в сторону -«>). В обоих случаях если один аргу- мент — число, другой — NaN, возвращается число. Ссылки: NaN 5.2; макросы общего типа 17.12. 17.12 Макросы общего типа В С99 определен набор макросов общего типа (type-generic macros), повышаю- щих мобильность программ С, содержащих математические и (или) комплекс- ные функции. Расширения этих макросов — это вызовы определенных библио- течных функций в зависимости от типов их аргументов. Для использования мак- росов необходимо подключить к исходному тексту программы библиотечный заголовочный файл tgmath.h,. включающий библиотечные заголовочные файлы math.h и complex.h. Макросы общего типа перечислены в таблице 17.1, где буквой Т помечен общий тип: float, double, long double, float complex, double complex или long double complex. Выражение REAL(T) обозначает действительный тип того же размера, что и комплексный общий тип. Большинство функций принимают только один аргумент, однако некоторые из них вызываются с более чем од- ним аргументом общего типа, другие — также и с дополнительными аргумен- тами определенных (не общих) типов. Эти аргументы никак не должны зави- сеть от общих типов. Кроме этого, в таблице перечислены действительные и (или) комплексные функции, вызываемые с аргументами определенных ти- пов. Существуют правила составления имен функций на основе имени исход- ной библиотечной версии типа double: имена комплексных функций должны иметь префикс с, функции с аргументами типов float или float complex — суффикс f, функции с аргументами типов long double и long double complex — суффикс 1. Пример Трактовка макросов общего типа мажет зависеть от реализации, однако макрос sqrt, к примеру, может быть реализован следующим образом:
Математические функции 443 #define sqrt(x) \ ((sizeof(x) == sizeof(float)) ? sqrt(x) (sizeof(x) == sizeof(double)) ? sqrtf(x) : sqrtl(x)) При «вызове» макроса общего типа из таблицы 17.1 с общими аргументами определенных типов выбор вызываемой функции подчиняется правилам, пе- речисленным ниже. После выбора функции аргументы преобразуются в соот- ветствующие ей типы и далее, при наличии прототипа функции, подвергаются обычным преобразованиям аргументов. 1. Если хотя бы один из общих аргументов имеет тип long double complex, вызывается версия функции, возвращающая значение типа long double complex. Если такой функции не существует, результат не- предсказуем. 2. Иначе, если хотя бы один из общих аргументов имеет тип double complex, вызывается версия функции, возвращающая значение типа double comp- lex. Если такой функции не существует, результат непредсказуем. 3. Иначе, если хотя бы один из общих аргументов имеет тип float comp- lex, вызывается версия функции, возвращающая значение типа float complex. Если такой функции не существует, результат непредсказуем. 4, Иначе, если хотя бы один из общих аргументов имеет тип long double, вызывается версия функции, возвращающая значение типа long double. Если такой функции не существует, но существует функция типа long double complex, вызывается эти функция. 5. Иначе, если хотя бы один из общих аргументов имеет тип double или хотя бы один из общих аргументов имеет целый тип, вызывается вер- сия функции, возвращающая значение типа double. Если такой функ- ции не существует, но существует функция типа double complex, вызы- вается эта функция. 6. Иначе вызывается функция, возвращающая тип float (до этого пункта доходит только вызов, в котором все аргументы имеют тип float). Если такой функции не существует, но существует функция типа float complex, вызывается эта функция. Таблица 17.1. Макросы общего типа Макрос общего типа (tgmath.h) Действительные функции (math.h) Комплексные функции (complex.h) Т acos(Т х) acos, acosf, acosl cacos, cacosf, cacosl Т acosh(T х) acosh, acoshf, acoshl cacosh, cacoshf, cacoshl Т asin(Т х) asin, asinf, asinl easin, casinf, casinl Т asinh(T х) asinh, asinhf, asinhl casinh, casinhf, casinhl Т atan(Т х) atan, atanf, atanl catan, catanf, catanl Т atan2(Ту, Т х) atan2, atan2f, atan21 Т atanh(T х) atanh, atanhf, atanhl catanh, catanhf, catanhl Т carg(T х) carg, cargf, cargl Т cbrt (Т х) cbrt, cbrtf, cbrtl Т ceil(Т х) ceil, ceilf, ceill
444 Глава 17 Макрос общего типа (tgmath.h) Действительные функции (moth.h) Комплексные функции (complex.h) REAL(T) cimag(Т х) Т con j(Т х) Т copysign (Т х, Ту) copysign, copysignf, copysignl cimag, cimagf, cimagl conj, conjf, conjl Т cos(Т х) cos, cosf, cosl ccos, ccosf, ccosl Т cosh(T х) Т cproj(Т х) REAL(Т> creal(Т х) Т erf(Т х) Т erfc(Т х) cosh, coshf, coshl erf, erff, erfl erfc, erfcf, erfcl ccosh, ccoshf, ccoshl cproj, cprojf, cproj1 creal, crealf, creall Т ехр(Т х) Т ехр2(Т х) Т expml(Т х) exp, expf, expl exp2, exp2f, exp21 expml, expmlf, expml1 cexp, cexpf, cexpl Т fabs(Т х) Т fdim(T х, Т у) Т floor(Т х) Т fma(T х, Ту, Т z) Т fmax(T х, Ту) Т fmin(T х, Т у) Т fmod(T х, Т у) Т frexp(T value, int *ехр) Т hypot(T х, Ту) int ilogb(Т х) Т ldexp(T х, int ехр) Т 1 gamma (Т х) long long int llrint(T x) long long int llround(T x) fabs, fabsf, fabsl fdim, fdimf, fdiml floor, floorf, floorl fma, fmaf, fmal fmax, fmaxf, fmaxl fmin, fminf, fminl fmod, fmodf, fmodl frexp, frexpf, frexpl hypot, hypotf, hypotl ilogb, ilogbf, ilogbl Idexp, Idexpf, Idexpl Igamma, 1gammaf, Igammal llrint, llrintf, llrintl llround, llroundf, llroundl cabs, cabsf, cabsl T log(T x) T loglO(T x) T loglp(T x) T log2(T x) T logb(T x) long int lrint(T x) long int lround(T x) нет T nearbyint(T x) log, logf, logl loglO, loglOf, loglOl loglp, loglpf, loglpl log2, log2f, log21 logb, logbf, logbl Irint, Irintf, Irintl Iround, Iroundf, Iroundl modf, modff, modfl nearbyint, nearbyintf, nearbyinti clog, clogf, clogl
Математические функции 44! Макрос общего типа (tgmath.h) Действительные функции (math.h) Комплексные функции (complex.h) Т nextafter(T х) Т nexttoward(Т х, long double у) nextafter, nextafterf, nextafterl nexttoward, nexttowardf, nexttowardl Т pow(T х, T у) Т remainder(Т х, Ту) Т remquo(Т х, Ту, int *quo) Т rint(Т х) Т round(Т х) Т scalbln(T х, long int п) Т scalbn(Т х, int п) pow, powf, powl remainder, remainderf, remainderl remquo, remquof, remquo1 rint, rintf, rintl round, roundf, roundl scalbln, scalblnf, scalblnl scalbn, scalbnf, scalbnl cpow, cpowf, cpowl Т sin(T х) sin, sinf, sinl csin, csinf, csinl Т sinh(T х) sinh, sinhf, sinhl csinh, csinhf, csinhl Т sqrt(Т х) sqrt, sqrtf, sqrtl csqrt, csqrtf, csqrtl Т tan(T х) tan, tanf, tanl ctan, ctanf, ctanl Т tanh(T х) Т tgamma(Т х) Т trunc(T х) tanh, tanhf, tanhl tgamma, tgammaf, tgammal trunc, truncf, truncl ctanh, ctanhf, ctanhl 17.13 erf, erfc, Igamma, tgamma Краткий перечень ttinclude <math.h> // Все введены в С99 double erf(double x); float erff(float x); long double erfl(long double x); double erfc(double x); float erfcf(float x); long double erfcl(long double x); double Igamma(double x); float Igammaf(float x); long double Igammal(long double x); double tgamma(double x); float tgammaf(float x); long double tgammal(long double x); Функция erf вычисляет интеграл ошибок V71 о Функция erfc вычисляет выражение 1—erf(x), то есть: ул ..
446 Глава 17 Функция Igamma вычисляет натуральный логарифм модуля гамма-функ- ции х: log|r(x)| Функция tgamma вычисляет гамма-функцию аргумента х: Г(х) 17.14 fpclassify, isfinite, isinf, isnan, isnormal, signbit Краткий перечень #include <math.h> // Все введены в C99 int fpclassify (действительное-значение-с-ллавающей-точкой x); #define FP_INFINITE ... ftdefine FP_NAN ... #define FP_NORMAL ... ftdefine FP_SUBNORMAL ... ftdefine FP_ZERO ... int isfinite(действительное-значение-с-плавающей-точкой x); int isinf(действительное-значение-с-ллввающей-точкой x); int isnan(действительное-значение-с-ппаваяей~кчкой x) ; int isnormal(действительное-значение-с-плавающей-точкой x); int signbit(действительное-з.чачекие-с-плавающей-точкой x); Макросы этого раздела, параметры которых обозначены как «действитель- ное-значение-с-плавающей-точкой>>, являются макросами общего типа; аргу- ментом такого макроса может быть выражение любого действительного типа с плавающей десятичной точкой. Поскольку выражения, возвращающие зна- чения с плавающей точкой, могут вычисляться с большей точностью, чем со- ответствующие «семантические» типы, макросам приходится выполнять пре- образование аргументов до их проверки в соответствующий тип. В соответст- вии со стандартом С, нормализованное число в формате long double может превратиться в субнормальное в формате double и превратиться в нуль в фор- мате float. Макрос fpclassify возвращает одно из следующих значений: FP_INFINITE, FP_NAN, FP_NORMAL, FP_SUBNORMAL или FP_ZERO. Каждый из этих макросов представляет отдельное целое константное выражение. В некоторых реализациях С могут быть определены дополнительные классификационные макросы, имена которых начинаются на FP_ с последующей заглавной буквой. Макрос isfinite возвращает ненулевое значение тогда и только тогда, когда его аргумент не равен бесконечности или NaN. Субнормальные численные значения конечны. Макрос isinf возвращает ненулевое значение тогда и только тогда, когда его аргумент равен бесконечности (с любым знаком). Макрос isnan возвращает ненулевое значение тогда и только тогда, когда его аргумент равен NaN. Макрос isnormal возвращает ненулевое значение тогда и только тогда, ко- гда его аргумент нормализован. Если значение аргумента равно нулю, субнор- мально, бесконечно или равно NaN, макрос возвращает нуль.
Математические функции 447 Макрос signbit возвращает ненулевое значение тогда и только тогда, когда его аргумент отрицателен. 17.15 copysign, nan, nextafter, nexttoward Краткий перечень ftinclude <math.h> // Все введены в С99 double copysign (double x, double y); float copysignf(float x, float y); long double copysignl(long double x, long double y); double nan (const char *tagp); float nanf(const char *tagp); long double nanl(const char *tagp); double nextafter (double x, double y); float nextafterf(float x, float y); long double nextafterl(long double x, long double y); double nexttoward (double x, long double y); float nexttowardf(float x, long double y); long double nexttowardl(long double x, long double y); Функции этого раздела обрабатывают численные значения с плавающей точкой. Функция copysign возвращает значение аргумента х со знаком аргумента у. Функция пап возвращает «тихое» значение NaN, содержимое которого указано строкой, обозначенной указателем tagp (если данная реализация С поддерживает тихие значения NaN). В остальных случаях функция пап воз- вращает нуль. Вызовы пап(”последовательность-символов") пап("") пап(NULL) эквивалентны, соответственно, следующим: strtod("NAN(последовательность-символов)", (char **) NULL) strtod("NAN()", (char **) NULL) strtod("NAN", (char **) NULL) Обращения к nanf и nanl отображаются, соответственно, в обращения к strtof и strtodl. Функции nextafter возвращают следующее представимое значение с пла- вающей точкой после х в направлении у. Если такого конечного значения не существует, возникает ошибка переполнения. Если х и у равны, возвращается у. Необходимо следить за тем, чтобы происходило преобразование аргументов в типы формальных параметров и возвращаемых значений (даже при вызове через макрос), поскольку важно точное представление в форме числа с пла- вающей точкой. Функции nexttoward эквивалентны функциям nextafter, но их аргумент у всегда имеет тип long double. Ссылки: значение NaN 5.2; strtod 13.8.
448 Глава 17 17.16 isgreater, isgreaterequal, isless, islessequal, islessgreater, isunordered Краткий перечень ((include <math.h> // Все введены в C99 int isgreater (действительное-значение-с-плаваюдей-точкой x, действительное-значение-с-плавающей-точкой у); int isgreaterequal (действительте-значение-с-плаваюшей-точкой x, действительное-значение-с-плавакжей-точкой у); int isless (действительное-значение-с-плавающей-точкой x, действительное-значение-с-плавающей-тсчкой у); int islessequal (действительное-значение-с-плавающей-тсчкой x, действительное-значение-с-плавающей-точкой у); int islessgreater (действительное-эначение-с-плаваюшей-точкой х, действительное-значение-с-плавающей-точкой у); int isunordered (действительное-значение-с-плаваюшей-точкой х, действительное-значение-с-плаваюшей-точкой у); Два значения с плавающей точкой считаются несравнимыми (unordered), если хотя бы одно из них равно NaN. Выполнение операции сравнения над та- кими числами влечет, как правило, генерирование исключения несоответствия типов значений с плавающей точкой. Исключение не порождается в макросах сравнения общего типа, перечисленных ниже. Эти макросы можно использо- вать для определенного рода «аккуратного» программирования операций над числами с плавающей точкой. Если в какой-либо реализации С указанные ис- ключения не генерируются, это означает, что соответствующие операции обла- дают теми же свойствами, что и указанные макросы. Макрос isunordered возвращает true тогда и только тогда, когда его аргу- менты несравнимы. Макрос isgreater возвращает 0, если его аргументы несравнимы, иначе воз- вращает (х)>(у). Макрос isgreaterequal возвращает 0, если его аргументы несравнимы, ина- че возвращает (х)>=(у). Макрос isless возвращает 0, если его аргументы несравнимы, иначе возвра- щает (х)<(у). Макрос islessequal возвращает 0, если его аргументы несравнимы, иначе возвращает (х)<=(у). Макрос islessgreater возвращает 0, если его аргументы несравнимы, иначе возвращает (х)<(у) 11 (х)>(у) (не оценивая аргументы дважды). Ссылки: NaN 5 2
Глава 18 Функции даты и времени Функции, представленные в этой главе, обеспечивают программисту С воз- можность определения календарных даты и времени, а также процессорного времени — то есть количества времени, в течение которого программа занима- ет процессор. Календарное время можно использовать для записи дат запуска программ или записи файлов, а также для вычисления прошлых и будущих дат. Кален- дарное время представляется в двух форматах: как обычное арифметическое значение, возвращаемое функцией time, и с разбивкой на составляющие, в форме структуры, вычисляемое из указанного арифметического значения функциями gmtime и localtime. Форматирование, в соответствии с географи- ческой установкой, выполняется функцией strftime из Standard С. Процессорное время часто используется для измерения скорости выполне- ния программы или некоторой ее части и выражается арифметическим значе- нием (обычно, целым), возвращаемым функцией clock. 18.1 clock, clock.t, CLOCKS_PER_SEC, times Краткий перечень #include <time.h> typedef ... clock_t; #define CLOCKS_PER_SEC ... clock t clock(void); Функция clock возвращает приближенное время использования процессора текущим процессом. Единица измерения зависит от реализации; чаще всего, это микросекунда. Standard С допускает реализации с любым арифметиче- ским типом clock_t для выражения процессорного времени. Число временных импульсов (тиков) в секунду определяется макросом CLOCKS_PER_SEC. Если процессорное время не определяется, функция возвращает значение -1 (приведенное к типу clock_t). Программистам необходимо учитывать «цикличность» процессорного вре- мени. Если, к примеру, clock_t — 32-битовое значение, а время выражается в микросекундах, значение вернется к исходному примерно через 36 минут.
450 Глава 18 Пример Ниже приведен пример использования функции clock для определения времени вы- полнения программы Standard С. «include <time.h> clock_t start, finish; start = clock(); process (); finish = clock (); printf("process() выполнялся %f секунд\п", ((double) (finish - start)) / CLOCKS_PER_SEC ); Приведение к типу double необходимо на случай, если тип clock_t и CLOCKS_ PER_SEC — значения с плавающей точкой или целые. В традиционном С функция clock возвращает тип long, хотя по существу, это unsigned long; тип long использовался до введения типа unsigned long. Вычисление процессорного времени всегда должно вестись в беззнаковой арифметике. В некоторых реализациях, не соответствующих Standard С, вме- сто clock используется функция times, возвращающая структуру с различны- ми компонентами процессорного времени, исчисляемыми, как правило, в 1/60 секунды. Сигнатуры функций: «include <sys/types.h> «include <sys/times.h> long clock(void); void times(struct tins *); struct tins { ... } ; Пример В реализации, не соответствующей Standard С, можно написать функцию, пример- но эквивалентную функции clock, использовав для этого функцию times: «include <sys/types.h> «include <sys/times.h> «define CLOCKS_PER_SEC 60 long clock(void) ( struct tins tmsbuf; times(Stmsbuf); return (tmsbuf.tms_utime + tmsbuf.tms_stime); ) Использованная выше структура содержит поле типа time_t; эта единица процес- сорного времени, не совпадающая с типом «календарного времени» time_t, опреде- ленным в Standard С. Ссылки: time 18 2; time_t 18 2 18.2 time, time_t Краткий перечень «include <time.h> typedef ... time t; time t time(time t *tptr);
Функции даты и времени 451 Функция time из Standard С возвращает текущее календарное время, коди- рованное в значении типа time_t, который может представлять любой арифме- тический тип. Если параметр tptr — не нулевой, оно также записывается в *tptr. В случае ошибки возвращается значение -1 (приведенное к типу time_t). Как правило, значение, возвращаемое функцией time, передается функции asctime или ctime — для преобразования в удобочитаемую форму, — либо localtime или gmtime — для преобразования в форму, удобную для обработки. Для определения интервала между двумя значениями календарного времени, можно воспользоваться функцией difftime из Standard С; в других реализаци- ях программисту приходится использовать структурирование времени функ- цией gmtime или полагаться на обычное представление времени в секундах, прошедших после некоторой, произвольно установленной, даты (похоже, в этом отношении наиболее популярно 1 января 1970 г.). В традиционных реализациях вместо типа time_t используется тип long, но возвращаемое значение, по смыслу, представляет собой тип unsigned long. В случае ошибки возвращается значение —1L. В UNIX System V, кроме этого, в переменную еггпо записывается значение EFAULT. Ссылки: asctime 18 3; ctime 18 3; difftime 18 5; еггпо 11.2; gmtime 18.4; localtime 18.4 18.3 asctime, ctime Краткий перечень ftinclude ctime.h> char ‘asctime( const struct tm *ts ); char ‘ctime( const time t *timptr ); Функция asctime, как и ctime, возвращает указатель на строку, представ- ляющую дату и время в наглядной форме, пригодной для вывода: "Sat Мау 15 17:30:00 1982\п" Функция asctime вызывается с одним аргументом, представляющим указа- тель на структуру, содержащую календарное время; это структура, возвращае- мая функциями localtime и gmtime после обработки арифметического значе- ния времени, возвращенного функцией time. Параметр функции ctime — это указатель на значение, возвращенное функцией time, а потому выражение ctime(tp) эквивалентно asctime(localtime(tp)). В большинстве реализаций, в том числе многих из тех, что соответствуют Standard С, указанные функции возвращают указатели на статическую об- ласть данных, а потому полученное таким способом значение необходимо вы- вести или копировать (при помощи strcpy) до следующего вызова этой же или другой функции. В традиционном С, вместо типа time_t используется тип long, а функции объявляются в заголовочном файле sys/time.h.
452 Глава 18 Пример Вывод текущих даты и времени необходим во многих программах. Ниже показано, как это можно сделать при помощи функций time и etime: #include <time.h> ^include <stdio.h> time_t now; now = time(NULL); printf("Текущие время и дата: %s",ctime(Snow)); Ссылки: gmtime 18 4; localtime 18.4; strcpy 13.3; struct tm 18 4; time 18.2 *18.4 gmtime, localtime, mktime Краткий перечень ({include <time.h> struct tm ( ... } ; struct tm *gmtime( const time_t *t ) ; struct tm *localtime( const time_t *t ); time t mktime( struct tm *tmptr ); Функции gmtime и localtime преобразуют арифметическое значение кален- дарного времени, возвращаемое функцией time, в значение типа struct tm (с разбивкой на составляющие). Функция gmtime преобразует значение време- ни в «среднее время по Гринвичу» (GMT), localtime — в местное время, с уче- том часового пояса и возможного перехода на летнее время. Обе функции воз- вращают нулевой указатель в случае ошибки и мобильны в пределах систем UNIX и Standard С. Поля структуры struct tm перечислены в таблице 18.1. Все они имеют тип int. Таблица 18.1. Поля структурного типа struct tm Имя Единицы измерения Интервал значений tm_sec секунд с последней минуты 0..611 tm_min минут с последнего часа 0 59 tm_hour часов с полуночи 0..23 tm_mday день месяца 1.31 tmmon месяцев с января 0.11 tm_year лет с 1900 г. tmwday дней с воскресенья 0 6 tm_yday дней с 1 января 0..365 tmisdst флажок летнего времени >0 — летнее время, 0 — зимнее, <0 — неизвестно 1 Допускает «перескакивание» в две секунды (С89), хотя С99 требует только одну.
Функции даты и времени 453 В большинстве реализаций (включая и те, что соответствуют Standard С), функ- ции gmtime и local time возвращают указатель на единую статическую область данных, перезаписываемую при каждом вызове, поэтому возвращенное значение необходимо использовать до следующего вызова любой из этих функций. Функция mktime (Standard С) строит значение типа time_t из структурного значения локального времени, указанного аргументом tmptr. Значения tmptr->tm_wday и tmptr->tm_yday функцией mktime игнорируются. В слу- чае успешного выполнения, mktime возвращает новое значение времени и пе- реустанавливает значения компонентов tm_wday и tm_yday аргумента *tmptr. Если указанное календарное время не может быть представлено как значение типа time_t, mktime возвращает -1 (приведенное к типу time_t). Со- ответствующий пример приведен в разделе 18.5. Ниже приведены сигнатуры функций time в традиционном С: ((include <sys/time.h> struct tm ( ... }; struct tm *gmtime(long *t); struct tm *localtime(long *t); 18.5 difftime Краткий перечень ((include <time.h> double difftime( time t tl, time t tO ); Функция difftime определена только в Standard С. Она вычитает календар- ное время tO из календарного времени tl и возвращает разность в секундах в значении типа double. Программист не может рассчитывать на то, что кален- дарное время кодировано в типе time_t как скалярное значение (например, число микросекунд), поэтому вместо простой операции вычитания приходится использовать функцию difftime. Пример Следующая функция возвращает разность, в секундах, между текущими датой/вре- менем и полночью 15 апреля 1990 г. ^include <time.h> double Secs_Since_Apr_15(void) ( struct tm Apr_15_struct = (0); /* Запись 0 во все поля */ time_t Apr_15_t; Apr_15_struet. tm__year - 90; Apr_15_struct.tm_mon =3; Apr_15_struct.tm mday ~ 15; Apr_15_t = mktime(&Apr_15_struct); if (Apr_15_t == (time_t)-l) return 0.0; /* ошибка */ else return difftime( time(NULL), Apr_15_t); Ссылки: time t 18.2.
454 Глава 18 18.6 strftime, wcsftime Краткий перечень tfinclude <time.h> size_t strftime( char *s, size_t maxsize, const char * format, const struct tm *timeptr); ttinclude <wchar.h> size_t wcsftime( wchar_t *s, size_t maxsize, const wchar_t * format, const struct tm ‘timeptr); Эти функции определены только в Standard С. Подобно функции sprintf (раздел 15.11), strftime записывает символы в массив, на который ссылается параметр s, в многобайтовом строковом формате format. Однако в данном слу- чае форматируется одно значение даты/времени, обозначенное указателем timeptr (раздел 18.4), а коды форматирования format интерпретируются не так, как в случае sprintf. В массив, обозначенный параметром s, записывается не более maxsize символов (с учетом нулевого символа в конце). Функция воз- вращает фактическое число записанных символов (без нулевого символа в конце). Если значение maxsize меньше длины форматированной строки, функция возвращает нуль, а содержимое выходной строки остается неопреде- ленным. Способ форматирования strftime зависит от географической установ- ки — категории LC_TIME (см. setlocale, раздел 20.1). В Дополнении 1 к С89 определена еще одна функция — wcsftime, — форма- тирующая значение даты/времени в виде строки широких символов. Эта функция аналогична swprintf (раздел 15.11). 1S.6.1 Коды форматирования Строка форматирования состоит из произвольного сочетания специфика- ций преобразования и других многобайтовых символов. В процессе формати- рования спецификации заменяются символами, указанными в таблице 18.2, остальные многобайтовые символы выводятся без какого бы то ни было преоб- разования. Спецификация преобразования состоит из знака процентов (%), за которым может следовать заглавная буква модификатора Е или О, а затем символ, определяющий преобразование. Таблица 18.2. Коды форматирования для strftime Буква Чем заменяется Поле timeptr а Сокращенное наименование дня недели. При значении "С" tm_wday географического параметра всегда — первые три буквы от %А Например, "Моп" (Пон.) и т д. А Полное наименование дня недели. При значении "С” tm_wday географического параметра — "Monday" (Понедельник) и т.д. Ь Сокращенное наименование месяца. При значении "С" гсографичс- tm mon скою параметра — первые три буквы от %В: "Feb" (Фев.) и т.д.
Функции даты и времени 455 Буква Чем заменяется Поле timeptr В Полное наименование месяца. При значении "С" географического параметра — "February" (Февраль) и т.д. tmmon С Значение даты/времени, зависящее от географической установки. При значении "С" географического параметра — то же самое, что И %а %b %е %Т %Y любое либо все с С99 — последние две цифры года (00-99) tm_year d День месяца как десятичное целое (01-31). tm_mdoy D Эквивалентна %m/%d/%y tmmon, tmmday, tmyear е День месяца (1-31). Одна цифра дополняется пробелом впереди. tmmday F Формат даты ISO 8601: %Y—%m-%d. tmmon tm mday, tm year 9 Последние две цифры года, определяемого в неделях (00—99)' tm_year, tm_wday, tm_yday G Год, определяемый в неделях (0000-9999). tm_year, tm_wday, tmyday Н То же, что и %Ь. tmmon Н Часы (па 24-часовай школе) в форме десятичного целого (00-23). tm_hour 1 Часы (по 12-часовай шкале) в форме десятичного целого (01-12). tm_hour i День года в форме десятичного числа (001-366). tm_yday m Месяц в форме десятичного числа (01-12). tmmon м Минуты в форме десятичного числа (00-59). tmmin п С99 — заменяется символом новой строки. нет р Эквивалент обозначения АМ/РМ (до/после полудня), соответствующий географической установке. При значении "С" географического параметра — AM или РМ. tm_hour г С99 — 12-часовая шкала времени. При значении "С" географического параметра, эта эквивалентно %I:%M:%S %р. tm_hour, tm_min, tm_sec R С99 - то же, что %Н:%М. tm_hour, tm_min S Секунды как десятичное числа (00—60)2 tmsec t С99 — заменяется символом гооизонтальной табуляции. нет Т С99 — формат времени ISO 8601: %H:%M:%S tm_hour, tm min, tm_sec и С99 — стандарт ISO 8601 дня недели (1-7): 1 соответствует понедельнику, tm_wday и Номер недели в году (00-53)3 tm_year, tm_wday, tm_ydoy V С99 — стандарт ISO 8601 номера недели (01-53) в году, определяемом в неделях tm_year, tm_wday, tm_yday W День недели в форме десятичного числа (0-6, 0 соответствует воскресенью). tm_wday W Номер недепи в году (00~53)4 tm_year, tm_wday, tm yday X Дата в формате, зависящем от географической установки. При значении "С” географического параметра — %m/%d/%y любое либо все
456 Глава 18 Буква Чем заменяется Поле timeptr X Время в формате, зависящем от географической установки. При значении "С” географического параметра — %Т. любое либо все У Две последние цифры года (00-99). tm_year Y Год в форме десятичного числа, вместе со столетием (например, 1952). tm_yeor z С99 — стандарт ISO 8601 поправки для часового пояса относительно UTC (Universal Coordinated Time — Всеобщее скоординированное время) либо ничего -530 означает на 5 часов 30 минут меньше (к западу от) Гринвича. tmisdst z (Сокращенное) наименование часового поясо либо ничего, если чосовой пояс неизвестен При значении ''С" географического параметра — зависит от реализации. tmisdst % Отдельный знок %. нет 1 Год, определяемый в неделях — определение см, ниже 3 Допускает «перескакивание» в одну секунду (60) 3 Неделя номер 1 начинается с воскресенья, предыдущие дни принадлежат неделе 0 4 Неделя номер 1 начинается с понедельника, предыдущие дни принадлежат неделе 0. Модификатор и многие буквы преобразований введены в С99. Модификатор Е применим к преобразованиям с, С, х, X, у и Y; он указывает на необходи- мость использования (не обозначенного) альтернативного представления. Мо- дификатор О применим к преобразованиям d, е, Н, I, М, m, s, u, U, V, w, W и у; он указывает на необходимость использования (не обозначенных) альтер- нативных символов. При значении "С" географического параметра, модифи- каторы игнорируются. Некоторые буквы преобразований в С99 обозначают преобразования соот- ветственно году, определяемому по неделям (стандарт ISO 8601). В этой схеме неделя начинается с понедельника, а 1-й неделей года считается та, на кото- рую приходится 4 января (очевидно, что на нее должны приходиться минимум четыре дня нового года). Таким образом, 1, 2 и 3 января, в этой схеме, могут быть отнесены к предыдущему году, а 29, 30 и 31 декабря — к наступающему. Например, суббота 2 января 1999 г. приходится на 53-ю неделю 1998 г. Чтобы избежать противоречий, можно определить при помощи спецификаций %U и %W, неполную неделю с номером 0. Пример Ниже приведен пример приемлемой реализации функции asctime (раздел 18.3) с ис- пользованием strftime. Поскольку способ форматирования зависит от географиче- ской установки, длину выходной строки (с учетом нулевого символа в конце) пред- сказать трудно (почему и предпочтителен вывод из asctime). #include <time.h> ffdefine TIME_SIZE 80 /* этого, надо полагать, будет достаточно */ char *asctime2( const struct tm *tm ) { static char timebuffer[TIME_SIZE]; size_t len; len = strftime( time_buffer, TIME_SIZE, '%a %b %d %H:%M:%S %Y\n" , tm) ; if (len == 0) return NULL; /* time_buffer недостаточен */ else return time_buffer; )
Глава 19 Управляющие функции Функции, рассматриваемые в этой главе, реализуют дополнительные опе- рации переходов в программах С. Их объявления размещаются в заголовоч- ных файлах assert.h, setjmp.h и signal.h. Часть управляющих функций, рас- сматривавшихся в данной главе предыдущих изданий книги, в этом издании перемещена в главу 16. В числе прочих, это относится к системным функци- ям, а также к тем, которые связаны с выходом из программных блоков. 19.1 assert, NDEBUG Краткий перечень #include <assert.h> ttifndef NDEBUG void assert( int expression ) ; Seise Sdefine assert(x) ((void)O) ttendif Макрос assert имеет единственный аргумент целого типа (в ряде реализа- ций допускается любой скалярный тип). Если значение аргумента равно О, а макрос NDEBUG не определен, assert выводит в стандартный выходной по- ток диагностическое сообщение и завершает выполнение программы, вызвав функцию abort (в Standard С) или exit (в традиционном). Функция assert все- гда реализуется в виде макроса, для ее использования необходимо подключить к исходному файлу заголовочный файл assert.h. Диагностическое сообщение содержит текст аргумента, имя файла (_FILE__) и номер строки (_LINE__). В реализациях С99 может быть указано также имя функции (___func___). Если макрос NDEBUG определить, когда прочитан файл assert.h, выполне- ние assert блокируется (макрос определяется как пустой оператор; диагности- ческое сообщение не выводится, оценки аргумента не происходит). Пример Функция assert используется, как правило, в процессе программирования — для проверки определенных условий выполнения программы. Она обеспечивает хоро- шее документирование программы и существенно упрощает отладку. После завер- шения доводки программы все обращения к assert без труда блокируются и не ока-
458 Глава 19 зывают никакого влияния на ее выполнение. В следующем примере, применение assert позволяет документировать программу более полно, чем это удается сделать при помощи комментариев. #include <assert.h> int f(int x) { /* Значение x должно быть между 1 и 10 */ /* !? */ assert(х>0 && х<10); } Ссылки: abort 19.3; exit 19.3;_func_2.6.1;_LINE_3.3.4. 19.2 system, exec См. раздел 16.7. 19.3 exit, abort См. раздел 16.5. 19.4 setjmp, longjmp, jmp_buf Краткий перечень ttinclude <setjmp.h> typedef ... jmp_buf; int setjmp( jmp_buf env ); void longjmp( jmp buf env, int status ); Функции setjmp и longjmp реализуют примитивную форму нелокальных переходов, которые можно использовать в аварийных или исключительных ситуациях. Эта возможность традиционно считается более мобильной, чем sig- nal (раздел 19.6); тем не менее, последняя также была включена в Standard С. Макрос setjmp записывает параметры среды вызывающей функции в «бу- фер переходов» env (массив, размерность и тип которого зависят от реализа- ции) и возвращает 0. Макросу должен передаваться указатель на этот массив, для чего необходимо объявить тип jmp_buf как массив. Функция longjmp принимает в качестве аргумента указатель на буфер пе- реходов, уже заполненный в результате обращений к функции setjmp, и целое значение status — как правило, ненулевое. Функция longjmp выполняет воз врат из вызванной функции снова в setjmp — на этот раз, с возвратом значе- ния status. В некоторых реализациях, в том числе и Standard С, функция longjmp не может возвращать значение 0 из setjmp; если longjmp вызывается с состоянием 0, она возвращает из setjmp 1. Функции setjmp и longjmp трудны в реализации, поэтому необходимо сво- дить к минимуму число предположений о них. Если setjmp возвращает нену- левое значение, это может свидетельствовать о том, что статические перемен-
Управляющие функции 459 ные содержали в момент вызова longjmp правильные значения. Переменные автоматического класса памяти, локальные по отношению к функции, содер- жащей setjmp, в Standard С гарантированно содержат правильные значения только в том случае, если имеют тип с квалификатором volatile или если они не изменялись с момента исходного обращения к setjmp и до соответствующе- го вызова longjmp. Кроме этого, в Standard С требуется, чтобы вызов представ- лял собой целостное операторное выражение (возможно, приведенное к типу void), правую часть простого выражения присваивания, лйбо использовался в качестве управляющего выражения операторов if, switch, do, while или for следующих видов: (setjmp(...)) (!setjmp(...)) (exp relop setjmp(...)) (setjmp(...) relop exp) Здесь exp — целое константное выражение, relop — оператор отношения либо равенства. Standard С требует, чтобы функция longjmp правильно рабо- тала в невложенных обработчиках сигналов (прерываний), но в некоторых из ранних реализаций вызов setjmp или longjmp во время обработки прерывания или сигнала дает неправильные результаты. Если аргумент функции longjmp, указывающий на буфер переходов, не уста- новлен функцией setjmp, или если выполнение функции, содержащей setjmp, завершается до вызова longjmp, поведение оказывается непредсказуемым. Пример #include <setjmp.h> jmp_buf ErrorEnv; int guard(void) /* При нормальном завершении, возврат 0, иначе - код longjmp. ★/ { int status = setjmp(ErrorEnv); if ( status ’= 0) return status; /* ошибка */ process () ; return 0; ) int process(void) { ... if (проязоша_опбка) longjmp (ErrorEnv, код_ошибки) ; ) Функция longjmp вызывается в случае возникновения ошибки в функции process. Функция guard принимает управление то longjmp. Функция process вызывается непосредственно либо опосредованно (через guard); тем самым предотвращаются вызов longjmp после возврата управления от guard и попытки использования значе- ний локальных переменных функцией process, которая содержит longjmp (консер- вативный подход). Следует обязательно тестировать значение, возвращаемое функ- цией setjmp, чтобы определить, был ли возврат вызван функцией longjmp. 19.5 atexit См. раздел 16.5.
460 Глава 19 19.6 signal, raise, gsignal, ssignal, psignal Краткий перечень #include <signal.h> #define #define #define #define SIG_IGN SIG_DFL SIG_ERR SIGxxx void (‘signal( int sig, void (*func) (int) )) (int); int raise( int sig ) ; typedef ... sig_atomic_t; /* Расширения, не относящиеся к Standard С: */ int kill ( int pid, intsig ); int (‘ssignal( int softsig, int (*func)(int) )) (int); int gsignal( int softsig ); void psignal ( int sig, char ‘prefix ); Сигналами могут быть асинхронные события, требующие специальной об- работки пользовательской программой или библиотечными средствами. Сиг- налы обозначаются целыми значениями, в каждой реализации набор сигналов объявлен в заголовочном файле signal.h; имя каждого имеет префикс SIG. Сигналы формируются (порождаются) механизмами обнаружения ошибок компьютера, пользовательскими программами (посредством функций kill и raise), а также действиями, внешними по отношению к программе. Про- граммные сигналы, используемые функциями ssignal и psignal, определяют- ся пользователем; для них выделены значения от 1 до 15; иначе они действуют как обычные сигналы. Обработчик сигнала sig — это пользовательская функция, вызываемая при «появлении» сигнала sig. Функция-обработчик выполняет некоторые необхо- димые действия, затем возвращает управление — как правило, в точку, из ко- торой была вызвана. В некоторых случаях обработчик может вызвать функ- цию exit или longjmp. Обработчики сигналов — это обычные функции С, вы- зываемые с одним аргументом (порожденным сигналом): void my_handler(int the_signal) { ... } В некоторых реализациях, не относящихся к Standard С, обработчики опреде- ленных сигналов могут иметь дополнительные аргументы. Функция signal сопоставляет обработчик сигналов с сигналами определен- ного типа. В обычной ситуации параметры этой функции — номер сигнала и указатель на обработчик. Если сопоставление с обработчиком оказывается успешным, signal возвращает указатель на предыдущий обработчик, иначе возвращает -1 (в Standard С — SIG_ERR) и записывает код ошибки в перемен- ную errno. Пример void new__handler (int sig) { ... } void (*old_handler)(); /* Установка нового обработчика, сохранение предыдущего */ old_handler = signal( sig, &new_handler );
Управляющие функции 461 if (old_handler==SIG_ERR) printf(”?Не удается установить новый обработчик.\п"); /* Восстановление предыдущего обработчика */ if (signal (sig, old__handler) ==SIG_ERR) printf("?He удается вернуться к прежнему обработчику.\п"); Аргумент signal, указывающий функцию, как и возвращаемое значение, могут быть специальными значениями SIG_IGN и SIG_DFL. Вызов signal(sig, SIG_IGN) означает, что сигнал sig должен быть проигнорирован, вызов signal(sig, SIG_DFL) — что сигнал должен быть подвергнут процедуре обра- ботки «по умолчанию» (это означает игнорирование одних сигналов и завер- шение выполнения программы — по другим). Функция ssignal (входящая в UNIX System V) действует в точности так же, как signal, но применяется только в сочетании с gsignal для обработки сигна- лов, определенных пользователем. Обработчики, указываемые функции ssig- nal, возвращают целые значения, возвращаемые, в свою очередь, функцией gsignal. Функции raise и gsignal порождают выводимый (или программный) сигнал в текущем процессе. Функция kill порождает выводимый сигнал в указанном процессе; она менее мобильна. Если для некоторого сигнала установлен при помощи функции signal или gsignal обработчик, порождение этого сигнала приводит к передаче управле- ния его обработчику. В Standard С (и большей части прочих реализаций) обра- ботчик до передачи ему управления сбрасывается на SIG_DFL либо блокирует сигнал иным образом, предотвращая нежелательную рекурсию (вызов самого себя). Применение этой схемы для сигнала SIGILL зависит от реализации и определяется как историей развития, так и соображениями эффективности. Обработчик может возвратить управление; в этом случае выполнение продол- жается с точки прерывания при следующих условиях: 1. Если сигнал порожден функцией raise или gsignal, происходит возврат в вызывающую функцию. 2. Если сигнал порожден функцией abort, выполнение программы Stan- dard С завершается. В других реализациях может происходить возврат в вызывающую функцию. 3. Если обрабатываемый сигнал — SIGFPE или другой вычислительный сигнал, зависящий от реализации, вариант возврата непредсказуем. При написании обработчиков сигналов следует воздерживаться от вызова из их библиотечных функций (кроме signal), поскольку некоторые из этих функций (опять же, кроме signal), могут также порождать сигналы, не будучи рекурсивными. В Standard С определены макросы, перечисленные в таблице 19.1 и обозна- чающие определенные стандартные сигналы. Эти сигналы характерны для многих реализаций С. Таблица 19.1. Стандартные сигналы Макрос Описание сигнала SIGABRT Аварийное завершение — например, в результате вызово функции obort SIGFPE Ошибко в арифметической операции — нопример, деление на нуль
462 Глава 19 Макрос Описание сигнала SIGILL Ошибка вследствие неправильной команды процессора. SIGINT Предупредительный сигнал — например, в результате нажатия пользователем специальной клавиши. SIGSEGV Попытка доступа в неразрешенную область памяти. SIGTERM Сигнал завершения от пользователя или другой программы. Функция psignal (не в Standard С) выводит стандартное сообщение об ошиб- ке со строковым префиксом prefix (представляющим, обычно, имя програм- мы) и кратким описанием сигнала sig. Эта функция может использоваться в обработчиках перед вызовом exit или abort. Ссылки: exit 19.3; longjmp 19.4. 19.7 sleep, alarm Краткий перечень функций, не относящихся к Standard С void sleep( unsigned seconds ); unsigned alarm( unsigned seconds ); Эти функции не входят в Standard С. Функция alarm устанавливает внут- ренний системный таймер на указанное число секунд и возвращает предыду- щее значение. При достижении таймером указанного значения, программа ге- нерирует сигнал SIGALRM. Если alarm вызывается с аргументом, равным нулю, результатом является отмена всех предыдущих вызовов этой функции. Обычно она применяется для выхода из различных тупиковых ситуаций. Функция sleep приостанавливает выполнение программы на указанное число секунд. Выполнение возобновляется после возврата из функции. Как правило, эта функция использует тот же таймер, что и alarm. Если время, за- данное функции sleep, с самого начала превышает время на таймере, возврат происходит немедленно после обработки сигнала SIGALRM; если оно меньше значения таймера, функция произведет сброс таймера перед возвратом, так что сигнал SIGALRM поступит в положенное время. В большинстве реализаций при обработке любого сигнала происходит воз- врат из функции sleep; в некоторых из них эта функция возвращает время в секундах (тип unsigned), на которое была сокращена пауза в выполнении программы. В некоторых реализациях указанные функции имеют аргумент типа un- signed long. Ссылки: signal 19.6
Глава 20 Географическая установка Standard С предназначался для всего международного сообщества и учиты- вает различные алфавиты и правила обозначения чисел, денежных единиц, дат и времени. Стандарт языка допускает реализации с соответствующей на- стройкой библиотечных функций и, одновременно, сохранением возможности пересечения национальных границ. Набор средств соответствия определенным национальным, культурным или языковым особенностям именуется географической установкой (locale)', все они объявлены в заголовочном файле locale.h. Географическая установка за- трагивает такие особенности, как формат вывода чисел и денежных сумм, ал- фавит (в том числе порядок следования букв — см., например, функции обра- ботки текстовых символов, глава 12), а также обозначения даты и времени. «Текущую географическую установку» можно изменять в процессе выполне- ния программы, выбирая значение географического параметра из определен- ных в данной реализации. В Standard С определен только один географиче- ский параметр "С", определяющий минимальную среду, совместимую с исход- ным определением языка С. 20.1 setlocale Краткий перечень «include <locale.h> «define LC_ALL ... «define LC_COLLATE ... «define LC_CTYPE ... «define LC_MONETARY ... «define LC_NUMERIC ... «define LC_TIME ... char *setlocale( int category, const char ‘locale ); Функция setlocale меняет установки библиотеки функций соответственно географической установке. Ее первый аргумент — category, — содержит код, определяющий необходимое изменение. Допустимые значения этого парамет-
464 Глава 20 ра включают значения макросов, перечисленных в таблице 20.1, возможно, дополненные категориями, характерными для определенных реализаций, в которых имена макросов начинаются с букв LC. Table 20.1. Стандартные категории setlocale Имя Определяемое поведение LC_ALL Любое поведение LC_COLLATE Поведение функций strcoll и strxfrm LC_CTYPE Поведение функций обработки символов (глава 12) LC_MONETARY Поведение денежной информации, возвращаемой функцией locoleconv LC_NUMERIC Поведение десятичных дробей и неденежной информации, возвращаемой функцией localeconv LC_TIME Поведение функции strftime Второй аргумент (locale) — это строка, зависящая от реализации и содержа- щая имя географического параметра, определяющего поведение обозначенной категории. В Standard С для географического параметра достуны только два значения: "С" и пустая строка обозначающая собственную («родную») гео- графическую установку. Любая библиотека функций действует соответственно географическому параметру "С", если явным образом не установлен иной (при помощи функции setlocale). Если аргумент locale функции setlocale равен нулевому указателю, измене- ния географической установки не происходит, а функция возвращает указатель на строку, которая является именем текущего географического параметра для указанной категории. Это имя таково, что если setlocale вызвать, впоследствии, еще раз, указав ту же категорию и возвращенную строку (в качестве значения географического параметра), результатом будет установка такого поведения, какое устанавливается после вызова setlocale при нулевом locale. Например, программист, собирающийся внести изменения соответственно географической установке, может вызвать сначала setlocale с аргументами LC_ALL и NULL, чтобы определить текущую установку и, в случае необходимости, восстановить ее впоследствии. Возвращенную строку нельзя изменять и она может быть пере- записана в последующих обращениях к setlocale. Если аргумент locale функции setlocale не является нулевым указателем, функция заменяет текущий географический параметр и возвращает имя ново- го. Если по какой-то причине функция не может выполнить эту операцию, она возвращает нулевой указатель. Возвращенную строку нельзя изменять и она может быть перезаписана в последующих обращениях к setlocale. Пример Приведенная ниже функция original_locale возвращает строку с описанием теку- щей географической установки, по которому она впоследствии может быть восста- новлена. Длина этой строки не ограничена, поэтому память для нее необходимо рас- пределять динамически. ffinclude <locale.h> ffinclude <string.h> ffinclude <stdlib.h> char *original__locale (void)
Географическая установка 465 char * temp, * copy; temp = setlocale(LC_ALL, NULL); if (temp == NULL) return NULL; /* Аварийное завершение setlocale() */ copy = (char *)malloc(strlen(temp)+1); if (copy == NULL) return NULL, /* Аварийное завершение malloc() */ strcpy(copy,temp); return copy; 1 В следующем фрагменте функция original_locale используется для изменения, а за- тем восстановления географической установки: #include <locale.h> extern char *original_locale(void); char *saved_locale; saved_locale = original_locale(); setlocale (LC_ALL, ""); /* Переход к собственной географической установке */ setlocale(LC_ALL,saved locale);/* Восстановление прежней установки */ Ссылки: malloc 16.1; localeconv 20.2; strcoll 13.10; strcpy 13.3; strftime 186; strlen 13.4; strxfrm 13.10. 20.2 localeconv Краткий перечень ftinclude <locale.h> struct Iconv (...); struct Iconv ‘localeconv(void); Функция localeconv возвращает информацию о правилах форматирования численных значений и денежных сумм в соответствии с текущей географиче- ской установкой. Использование ее позволяет программисту писать собствен- ные функции преобразований и форматирования с некоторой степенью мо- бильности в отношении географических установок. В то же время, наличие этой функции избавляет от необходимости загромождать Standard С множест- вом функций преобразования, соответственно каждой географической уста- новке. Функция localeconv возвращает указатель на объект типа struct Iconv, состоящий, как минимум, из полей, перечисленных в таблице 20.2. Возвра- щаемая структура не должна изменяться программистом и может быть пере- записана в результате последующих вызовов localeconv. Строковые поля struct Iconv, содержащие пустую строку, а также символьные со значением CHARMAX интерпретируются как «неизвестно». Пример В следующем примере функция localeconv выводит число с плавающей десятич- ной точкой, заменяемой символом, соответствующим текущей географической ус- тановке: Hinclude <locale.h> #include <stdio.h> void P(int int-part, int fract_part, int fract_digits)
466 Глава 20 struct Iconv *lconv = localeconv(); char *pt = lconv->decimal_point; /* Если *pt - пустая строка, заменить ее строкой */ if (! *pt) pt = ". " ; printf("%d%s%0*d\n", int_part, pt, fract_digits, fract_part); } Описания остальных полей структуры struct Iconv даны в таблице 20.2. Группирование цифр (разрядов) Поля grouping и mon_grouping структуры struct Iconv — это последова- тельности целых значений типа char — то есть строка использована как спо- соб кодирования последовательности небольших целых значений. Каждое зна- чение в последовательности определяет число цифр в группе. Первое из них соответствует первой группе слева от десятичной точки, второе — второй груп- пе слева от первой и т.д. Значение 0 (нулевой символ в конце строки) означает, что предыдущая группа разрядов должна повторяться, целое значение CHAR_MAX — что следующие разряды не группируются. Обычное группиро- вание «по тысячам» задается строкой ”\3” (повторение группы длиной в три разряда), а строка "\1\2\3\127" группирует разряды числа 1234567890 сле- дующим образом: 1234 567 89 0 (значение 127 соответствует CHAR_MAX). Расположение знака Поля p_sign_posn и n_sign_posn структуры struct Iconv определяют место- положение, соответственно, positive_sign (знака положительного числа) и пе- gative_sign (знака отрицательного числа). Допустимые значения и их смысл приведены в следующей таблице. О Число и currency symbol (знак денежной единицы) заключаются в скобки I Строка знака располагается перед числом и currency symbol 2 Строка знака располагается после числа и currency symbol 3 Строка знака располагается непосредственно перед currency_symbol. 4 Строка знака располагается непосредственно после currency symbol Полные примеры форматирования денежных сумм приведены в таблицах 20.3 и 20.4. Эти примеры взяты из стандарта Standard С. В таблице 20.3 при- ведены форматы денежных сумм, принятые в четырех странах, в таблице 20.4 — значения полей структуры struct Iconv, задающие форматы, приведен- ные в таблице 20.3. Таблица 20.2. Поля структуры Iconv Тип Имя Описание Значение при географическом параметре "С" char * decimal_point Десятичная тачко или символ, ее " . ” заменяющий (кроме денежных сумм) char * thousands_sep Разделитель групп разрядов (кроме " " денежных сумм)
Географическая установка 467 Тип Имя Описание Значение при географическом параметре "С char * grouping Группирование разрядов (кроме денежных сумм) tl It char ★ int_curr_symbol Трехэначный международный валютный знак плюс символ, отделяющий его от денежной суммы .. .1 char ★ currency_symbol Валютный знак, соответствующий текущей географической установке И It char ★ mon decimalpoint Десятичная точка (для денежных сумм) It и char * mon_thousands_sep Разделитель групп разрядов (для денежных сумм) tl Г! char ★ mon__grouping Группирование разрядов (для денежных сумм) tf II char ★ positive_sign Символ(ы) знока (для неотрицательных денежных сумм) tl tl char ★ negative_sign Символ(ы) знака (для отрицательных денежных сумм) 11 tl char int_frac_digits Цифры, располагающиеся справа от десятичной точки (для международных денежных форматов) CHAR_MAX char frac_digits Цифры, располагающиеся справа от десятичной точки (кроме международных денежных форматов) CHAR_MAX char p_cs_precedes 1, если валютный знак располагается перед денежной суммой, 0 — если после CHAR_MAX char p_sep_by_space 1, если валютный знак отделяется от денежной суммы пробелом, иначе — 0 CHAR_MAX char n_cs_precedes То же, что p_cs_precedes для отрицательных денежных сумм CHAR_MAX char n_sep_by_space То же, что p_sep_by_space для отрицательных денежных сумм CHAR_MAX char p__sign_jsosn Расположение positive_sign в неотрицательной денежной сумме (плюс валютный знок) CHAR_MAX char n_sign_jsosn Расположение negative_sign в отрицательной денежной сумме (плюс валютный знак) CHAR_MAX Таблица 20.3. Примеры форматирования денежных сумм Страна Формат Положительная сумма Отрицательная сумма Международный формат Италия 1.1.234 -L.1.234 Ш. 1.234 Нидерланды F 1.234,56 F -1.234,56 NLG 1.234,56 Норвегия krl.234,56 krl.234,56- NOK 1.234,56 Швейцария SFrs. 1,234.56 SFrs. 1,234.56С CHF 1,234.56
468 Глава 20 Таблица 20.4. Примеры значений полей структуры Iconv Поле Италия Нидерланды Норвегия Швейцария int_curr_symbol "ITL." "NLG ” "NOK " "CHF " currency_symbol ”L. " "F" "kr" "SFrs." mon_decimal_point .1If t / P и mon thousands sep ” .. If II / mon grouping "\3" "\3" "\3" "\3" positive sign II и It II 11II n II negative_sign II _ tt "C” int_frac_digits 0 2 2 2 frac_digits 0 2 2 2 p_cs_precedes 1 1 1 1 p_sep_by_space 0 1 0 0 n_cs_precedes 1 1 1 1 n_sep_by_space 0 1 0 0 p__sign_posn 1 1 1 1 n_sign_posn 1 4 2 2
Глава 21 Расширения целых типов В этом разделе мы рассмотрим дополнительные объявления целых типов в С99, обладающих различными характеристиками. Эти объявления располага- ются в заголовочных файлах stdint.h и inttypes.h. Файл stdint.h содержит ос- новные объявления целых типов определенных размеров и необходим как в базовых, так и в автономных реализациях. Заголовочный файл inttypes.h под- ключает к себе stdint.h; в нем объявлены мобильные функции форматирования и преобразований. Это файл необходим только в базовых реализациях. В С принят подход, в котором размеры данных стандартных типов опреде- ляются на уровне реализаций. К сожалению, такая ситуация не способствует написанию мобильных программ. Рассматриваемые в данном разделе средства предполагают мобильность, но число объявлений в указанных заголовочных файлах, похоже, несколько превышает разумные пределы. Ссылки: базовые и автономные реализации 1.4 21.1 Общие правила Упомянутые библиотеки содержат огромное количество объявлений типов, макросов и функций, построенных систематическим образом, и в данном раз- деле мы рассмотрим общие правила из использования. 21.1.1 Разновидности типов Рассматриваемые библиотеки содержат объявления целых типов и макро- сов нескольких «разновидностей»; некоторые из них параметризуются шири- ной типа N. Значение N — десятичное целое без знака и ведущих нулей, пред- ставляющее ширину типа в битах. Пример Целые типы точного размера в 8 битов именуются int8_t и uint8_t (не int08_t и не uint08_t). Самые быстрые целые типы шириной не менее 8 битов именуются int_ fast8_t и uint_fast8_t. Определения «точного размера» и «самый быстрый» отно- сятся к разным типам.
470 Глава 21 21.1.2 Определение всего либо ничего Какой из типов (то есть с каким N) в действительности определен — иногда зависит от реализации. Однако если какой-то разновидности типа соответству- ет определенное значение N, тогда должны быть определены оба его вариан- та — со знаком и без, — а также все макросы для этой разновидности и разме- ра. Если определенные разновидность и размер типа необязательны и оказыва- ются не определены в реализации, остаются не определенными также и все связанные с ними типы и макросы. Пример Если в реализации определен целый тип точного размера 16 битов, тогда должны быть определены также типы intl6_t и uintl6_t и, кроме этого, макросы INT16_MIN, INT16_MAX, UINT16_MAX, PRIdl6, PRH16, PRI0I6, PRI11I6, PRIxl6, PRIX16, SCNdl6, SCNil6, SCN0I6, SCNul6 и SCNxl6. Если целый тип точного размера 16 битов в реализации отсутствует, ни один из перечисленных макросов не будет определен. 21.1.3 Граничные значения MIN и МАХ Макросы ...MIN и ...МАХ определяют интервалы значений объявленных типов, устанавливая представляемые ими минимальное и максимальное зна- чения — совершенно аналогично макросам ...MIN и ...МАХ для стандартных типов из limits.h. Чаще всего, нижние границы интервалов соответствуют оп- ределенным в С99. Пример Типы intl6_t и uintl6_t — это целые типы точного размера 16 битов. Интервалы их значений равны: «define INT16_MIN -32768 «define INT16_MAX 32767 «define UINT16_MAX 65535 Ссылки: limits.h табл 5 2 21.1.4 Макросы форматирования строк PRI... и SCN... Макросы PRIcKW и SCNcKW — это строки форматирования для семейств функций, соответственно, printf и scanf. Здесь с обозначает одну из букв опе- рации преобразования (d, i, о, и, х или X), К — разновидность типа (опускает- ся либо заменяется на LEAST, FAST, PTR или MAX), N — ширина в битах. Полный перечень макросов приведен в таблице 21.1. Расширение макроса PRI... — это строковые литералы, содержащие букву операции преобразования с (d, i, о, и, х или X) для функции printf, следую- щую за (необязательной) спецификацией размера, задаваемой для вывода дан- ных с определенными разновидностью типа и размером. Расширение макроса SCN... — это, аналогично, строковые литералы, содержащие букву операции преобразования с (d, i, о, и или х) для функции scanf, следующую за (необяза- тельной) спецификацией размера, задаваемой для преобразования численных
Расширения целых типов 471 значений при вводе и записи их в объекты, обозначаемые указателями на дан- ные с определенными разновидностью типа и размером. Пример Целые типы минимального размера 64 бита именуются int_least64_t и uint_le- ast64_t (разновидность К равна LEAST). Если эти типы определены, соответственно, как long и unsigned long, в inttypes.h можно обнаружить следующие определения: «define PRIdLEAST64 "Id" «define PRIiLEAST64 "li" «define PRIoLEAST64 ”lo" «define PRIULEAST64 "lu" «define PRIXLEAST64 "lx" «define PRIXLEAST64 "IX" «define SCNdLEAST64 "Id" «define SCNiLEAST64 "li" «define SCNoLEAST64 "lo" «define SCNULEAST64 "lu" «define SCNXLEAST64 "lx" Теперь предположим, что переменная а имеет тип long, переменная b — тип int_least64_t. Следующие два примера служат иллюстрацией двух способов вывода этих значений. Второй способ более мобилен: он действует вне зависимости от того, какой целый тип объявлен как int_least64_t. printf("a=%251d\n", а); /* обычный */ printf("Ь=%25" PRIdLEAST64 "\п", Ь); /★ мобильный */ Ссылки: limits.h табл. 5.2; преобразования printf 15.11.7; преобразования scanf 15.8.2. Таблица 21.1. Макросы строк форматирования для целых типов (N = ширина типа в битах) Разновидность тачного размера Разновидность минимального размера Быстрая разновидность Разновидность типа указателя Максимальная разновидность Форматы printf со знакам PRIdA/ PRIiW PRIdLEASTA/ PRIiLEASTA/ PRIdFASTA/ PRIiFASTA/ PRIdPTR PRIiPTR PRIdMAX PRIiMAX Форматы printf без знака PRIo/V PRIuA/ PRIxW PRIX/V PRIoLEASTA/ PRIuLEASTA/ PRIxLEASTA/ PRIXLEASTA/ PRIoFASTW PRIuFASTW PRIxPASTW PRIXFASTW PRIoPTR PRluPTR PRIxPTR PRIXPTR PRIoMAX PRIuMAX PRIxMAX PRIXMAX Форматы scanf со знаком SCNdW SCNiW SCNdLEASTA/ SCNi LEAST W SCNdFASTW SCNiFASTW SCNdPTR SCNiPTR SCNdMAX SCNiMAX Форматы scanf без знака SCNoW SCNuW SCNxW SCNoLEASTW SCNuLEASTA/ SCNxLEASTW SCNoFASTW SCNuFASTW SCNxFASTW SCNoPTR SCNuPTR SCNxPTR SCNoMAX SCNuMAX SCNxMAX
472 Глава 21 2*1.2 Целые типы точных размеров Краткий перечень (tinclude <stdint.h> // Все - С99 typedef ... intN_t typedef ... uintN_t ((define INTN_MIN -2"-1 ((define INTN_MAX 2"-1-! ((define UINTN_MAX 2“-l ((include <inttypes.h> ((define PRIcN ((define SCNcN " . . . " Эти типы и макросы описывают целые данные, обладающие определенны- ми точными размерами без битов заполнения. Макросы ...MIN и ...МАХ долж- ны возвращать в точности указанные значения. Объявление этих типов в stdint.h обязательно лишь в случае наличия в ис- пользуемой реализации целых типов точных размеров 8, 16, 32 или 64 бита. В любой реализации могут быть определены (либо не определены) дополни- тельные целые типы точных размеров. Пример Следующие определения присутствуют во множестве реализаций С, предназначен- ных для компьютеров с побайтовой адресацией: ((include <limits.h> /* SCHAR_MIN, SCHAR_MAX, UCHAR_MAX */ typedef signed char int8_t; typedef unsigned char uint8_t; typedef short intl6_t; typedef unsigned short uintl6_t; typedef int int32_t; typedef unsigned int uint32_t; typedef long long int int64_t; typedef unsigned long long int uint64_t; ((define INT8_MIN SCHAR_MIN ((define INT8_MAX SCHAR_MAX ((define UINT8_MAX UCHAR_MAX ((define PRId8 "hhd" ((define SCNo64 "llo" // и т.д. В будущем с ростом разрядности компьютеров можно ожидать объявления типа long как int64_t, типа long long int — как int!28_t.
Расширения целых типов 473 21.3 Типы минимальной ширины данных минимальных размеров Краткий перечень #include <stdint.h> // Все - C99 typedef ... int_leastN_t typedef ... uint_leastN_t #define INT_LEASTN_MIN -(2"’1-!) #define INT_LEASTN_MAX 2"*1-! #define UINT_LEASTW_MAX 2N-1 #define INTN_C(константа) ... #define UINTN_C(константа) . . . ftinclude <inttypea.h> #define PRIcLEASTN " . . . " #define SCNcLEASTN ". . . " Эти целые типы и макросы определяют данные минимального размера с не- которой нижней границей. Макросы ...MIN и ...МАХ должны иметь тот же знак, что и показанные значения, и, как минимум, величину. Это должны быть наименьшие типы заданной ширины, откуда следует, что если существует точ- ная ширина (раздел 21.2) для определенного N, тогда тип точного размера дол- жен быть, одновременно, типом минимального размера для того же значения N. Во всех реализациях С99 должны быть определены указанные типы, а так- же макросы для N = 8, 16, 32, 64. Определения для других значений N необя- зательны, но если какое либо иное значение N оказывается определено, для него должны быть определены соответствующие типы и макросы. Пример В реализациях С для компьютеров с адресацией по 32-битовым словам типы char short и int могут быть определены как 32-битовые. В этом случае типы точных раз- меров int8_t и intl6_t (а также их беззнаковые двойники) не будут определены, типы же минимального размера int8_t и intl6_t должны быть определены как один из 32-битовых типов — например, int. Макрос INT7VC принимает в качестве аргумента десятичную, шестнадца- теричную или восьмеричную константу. Расширение этого макроса — некото- рая целая константа со знаком типа int_leastA_t. Расширение макроса UINTW-C — целая константа без знака типа uint_least.ZV_t. Оба макроса до- бавляют к константам соответствующий префикс. * ILL, Пример Если тип int_least64_t определен как long long int, тогда INT64_C(1) будет равен ILL, UINT64C(1) — 1ULL.
474 Глава 21 21.4 Быстрые типы с минимальной шириной Краткий перечень #include <stdint.h> // Все - С99 typedef ... int_fastN_t typedef ... uint_fastN_t «define INT_FASTN_MIN -(2"’1-!) «define INT_FASTN_MAX 2"’1-! «define UINT_FASTW_MAX 2N-1 «include <inttypes.h> «define PRIcFASTN n.,,11 «define SCNcFASTN " . . . " Эти типы и макросы определяют целые данные, максимально быстрые при некоторых минимальных размерах. Макросы ...MIN и ...МАХ должны иметь тот же знак и, как минимум, ту же величину, что показанные значения. Во всех реализациях С99 эти типы и макросы должны быть определены для М = 8, 16, 32 и 64. Определения для других значений N необязательны, но если какое либо иное значение N оказывается определено, для него должны быть определе- ны соответствующие типы и макросы. Определение того, какой из типов самый «быстрый», во многом зависит от точки зрения авторов реализации, реальная скорость — от конкретных условий использования данных соответствующих типов. Например, тип, самый быстрый в операциях скалярной арифметики, мо- жет не оказаться таковым при обращениях к элементам массивов. Пример В компьютере с побайтовой адресацией, оптимизированном для выполнения операций 32-разрядной арифметики, в определенной реализации С могут быть предложены 32-битовые типы, даже если в них нет необходимости. Ниже приведен возможный на- бор определений из заголовочного файла stdint.h. Показаны только типы со знаком. typedef char int8_t; typedef char int_least8_t; typedef int int_fast8_t; typedef short int!6_t; typedef short int_leastl6_t; typedef int int_fast!6_t; typedef int int32_t; typedef int int_least32_t; typedef int int_fast32_t ; 21.5 Целые типы размера указателя и максимального размера Краткий перечень «include <stdint.h> typedef intptr_t; typedef ... uintptr_t; «define INTPTR_MIN -(215-1) «define INTPTR_MAX 215-1 «define UINTPTR_MAX 216-1 // Все - C99
Расширения целых типов 475 Краткий перечень typedef ... intmax_t; typedef ... uintmax_t; «define INTMAX_MIN -(263-l> «define INTMAX_MAX 263-l «define UINTMAX_MAX 264-l «define INTMAX_C (константа) «define UINTMAX_C (константа) «include <inttypes.h> «define PRIcPTR "..." «define SCNcPTR " . . . " «define PRIcMAX "..." «define SCNcMAX ". . . ” Типы intptr_t и uintptr_t — это целые, соответственно, co знаком и без зна- ка, которые могут содержать указатели на данные любых типов. То есть если Р — значение типа void *, тогда его можно преобразовать в intptr_t или uintptr_t, затем — обратно в void *. В результате получим указатель Р в его исходном виде. Макросы ...MIN и ...МАХ должны иметь тот же знак и, как минимум, ту же величину, что показанные значения. Данные типы необяза- тельны, поскольку без них можно обходиться (хотя это и не совсем удобно). Типы intmax_t и uintmax_t — это самые большие целые типы, соответст- венно, со знаком и без, определенные в данной реализации. Эти типы должны быть определены во всех реализациях С. Поскольку в реализациях С99 разре- шены расширения целых типов, intmax_t не должен принадлежать к стан- дартным типам, каковым является, к примеру, тип long long int. Макросы ...MIN и ...МАХ должны иметь тот же знак и, как минимум, ту же величину, что показанные значения. Макрос INTMAX C преобразует десятичную, шестнадцатеричную или вось- меричную константу в целую константу типа intmax_t того же значения. Мак- рос UINTMAX_C выполняет преобразование в тип uintmax_t. Ссылки: limits.h табл 5 2 21.6 Интервалы значений типов ptrdiff_t, size_t, wchar_t, wint_t и sig_atomic_t Кроткий перечень «include <stdint.h> «define PTRDIFFMIN «define PTRDIFF_MAX «define SIZEMAX «define WCHAR_MIN «define WCHAR_MAX «define WINT_MIN «define WINT_MAX ... «define SIG_ATOMIC_MIN ... «define SIG_ATOMIC_MAX . . . // Все - C99 Расширения макросов данного раздела — это константные выражения пре- процессора, представляющие интервалы численных значений различных ти-
476 Глава 21 пов, определенных в заголовочных файлах stddef.h и wchar.h. Они должны быть определены во всех реализациях. PTRDIFF_MIN и PTRDIFF_MAX определяют интервал значений типа ptrdiff_t — со знаком, размером не менее 16 битов. SIZE_MAX — максимальное значение типа size_t. WCHAR_MIN и WCHAR_MAX определяют интервал значений типа wchar_t — со знаком или без, размером не менее 8 битов. WINT_MIN и WINT_MAX определяют интервал значений типа wint_t — со знаком или без, размером не менее 16 битов. SIG_ATOMIC_MIN и SIG_ATOMIC_MAX определяют интервал значений типа sig_atomic_t — со знаком или без, размером не менее 8 битов. Ссылки: ptrdiff_t 11.1, sig_atomic_t 19.6; size_t 1 IJ; wchar_t 24 1; wint_t 24 1. 21.7 imaxabs, imaxdiv, imaxdiv_t Краткий перечень ffinclude <inttypes.h> typedef ... imaxdiv_t; // Все - C99 intmax_t imaxabs( intmax_t x ); imaxdiv t imaxdiv( intmax t n, intmax t d ); Функции этого раздела реализуют основные арифметические операции над значениями целого типа максимального размера, подобно функциям abs и div из stdlib.h. Функция imaxabs вычисляет абсолютное значение аргумента. Если аргумент непредставим в абсолютном значении, результат оказывается неопределенным. Функция imaxdiv вычисляет n / d и п % d в одной операции. Результаты записываются, соответственно, в поля quot и rem структуры imaxdiv_t. Поря- док следования полей этой структуры не указывается. Ссылки: abs 16.9; div 16 9. 21.8 strtoimax, strtoumax Краткий перечень ffinclude <inttypes.h> intmax_t strtoimax( const char * restrict str, char ** restrict ptr, int base); uintmax t strtoumax( const char * restrict str, char ** restrict ptr, int base); Эти функции преобразуют строки в целые значения максимальных разме- ров, подобно функциям strtol и strtoul из stdlib.h. Если происходит перепол- нение, возвращается значение INTMAX_MAX, INTMAX_MIN или U1NT- МАХ_МАХ, а в переменную еггпо записывается значение ERANGE.
Расширения целых типов 477 Ссылки: errno и ERANGE 11.2; strtol и strtoul 16.4. 21.9 wcstoimax, wcstoumax Краткий перечень ftinclude <stddef.h> // wchar_t #include <inttypes.h> intmax_t wcstoimax( const wchar_t * restrict str, wchar_t ** restrict ptr, int base); uintmax_t wcstoumax( const wchar_t * restrict str, wchar_t ** restrict ptr, int base); Эти функции преобразуют строки широких символов в целые значения максимальных размеров, подобно функциям wcstol и wcstoul из wchar.h. Если происходит переполнение, возвращается значение INTMAX_MAX, INT- MAX_MIN или UINTMAX_MAX, а в переменную errno записывается значе- ние ERANGE. Ссылки: errno и ERANGE 11.2; wcstol и wcstoul гл 24

Глава 22 Операции над значениями с плавающей точкой Функции, рассматриваемые в этой главе, введены в С99 и дополняют те, ко- торые объявлены в заголовочном файле float.h. Они обеспечивают доступ к до- полнительным средствам, необходимым программам, выполняющим операции над значениями с плавающей точкой и требующим высокой или управляемой точности выполнения этих операций. Объявления этих функций располагаются в файле fenv.h. Ссылки: float.h табл. 5 3. 22.1 Обзор Программистам, составляющим высокоточные алгоритмы операций над зна- чениями с плавающей точкой, необходимы средства управления различными ас- пектами этих операций; способами округления результатов, упрощения или пре- образования выражений, определением реакции на события наподобие потери значимости (игнорирование или генерирование ошибки). Управление осуществ- ляется установкой режимов выполнения указанных операций. Обратная связь обеспечивается исключениями, порождаемыми в процессе выполнения операций над значениями с плавающей точкой. Исключения, прерывающие нормальное выполнение программы, обозначаются флажками состояния, доступными для чтения программистом. Кроме этого, программист С99 может управлять выпол- нением операций над значениями с плавающей точкой при помощи специализи- рованных математических функций, список которых приведен в главе 17. Операции над значениями с плавающей точкой могут выполняться в разное время. В процессе компиляции программы выполняются константные опера- ции (времени компиляции), после запуска — динамические (времени выпол- нения). Стандарт С99 устанавливает возможность управления только опера- циями времени выполнения. Что же касается операций времени компиляции, то здесь каждая реализация может обладать собственными средствами. Международный стандарт операций над значениями с плавающей точкой, примененный в С99 — IEC 60559:989: Binary floating-point arithmetic for microprocessor systems, second edition (Двоичная арифметика над значениями с плавающей точкой для микропроцессорных систем, второе издание). Прежде этот стандарт обозначался как IEC 559:1989 и ANSI/IEEE 754 1985: IEEE
480 Глава 22 Standard for Binary Floating point Arithmetic (Стандарт IEEE двоичной ариф- метики над значениями с плавающей точкой). Позднее стандарт IEEE 754 был обобщен с целью устранения зависимости от основания системы счисления и длины слова, результатом чего явился новый стандарт ANSI/IEEE 854-1987: IEEE Standard for Radix-Independent Floatingpoint Arithmetic (Стандарт IEEE двоичной арифметики над значениями с плавающей точкой, не зависящей от основания системы счисления). Отображение языка С в IEC 60559, необходи- мое лишь для использования макроса_STDC_IEC_559_, подробно изложено в Приложении F к стандарту. 22.1.1 Соглашения о программировании Средства управления выполнением операций над значениями с плавающей точкой динамичны. Это означает, что если внести в них какие-либо изменения в процессе выполнения программы, эти изменения сохранятся до следующего явного их изменения. Способ выполнения определенной функцией операций над значениями с плавающей точкой зависит от того, какие функции из fenv.h вызывались перед этим; следовательно, этот способ не может быть определен во время компиляции программы С. Если для управления этими операциями используются глобальные регистры, данное условие не вызывает никаких за- труднений; однако его намного труднее реализовать, если все определяется фактическими кодами операций, генерированными компилятором. Стандарт С99 рекомендует обращать внимание на то, что любая вызванная функция должна выполнять операции над значениями с плавающей точкой в соответствии со стандартными процедурами, если в документации не указа- но иное поведение. Аналогично, вызываемые функции не должны изменять условия выполнения указанных операций, если подобное изменение не указа- но в документации. Это означает, что ни одна функция не должна зависеть от каких бы то ни было флажков состояний либо изменять эти флажки. Если не- обходим учет состояния, следует предполагать состояние по умолчанию, и нельзя менять режим, установленный вызывающей функцией. Любая функ- ция может генерировать исключение, связанное с выполнением операции над значениями с плавающей точкой. 22.2 Дополнительные средства выполнения операций над значениями с плавающей точкой Краткий перечень ((include <fenv.h> ((pragma STDC FENV_ACCESS -сбс:: typedef ... feny_t; ((define FE_DEFL_ENV . . . int fegetenv(feny_t *envp); int fesetenv(fenv_t *envp(; int feholdexcept(fenv_t *envp); int feupdateenv(const fenvt ‘envp);
Операции над значениями с плавающей точкой 481 Стандартная директива транслятору FENV_ACCESS устанавливает (либо сбрасывает) разрешение программе С устанавливать режимы управления вы- полнением операций над значениями с плавающей точкой и флажки состоя- ния — даже запускаться в нестандартном режиме управления этими опера- циями. Если директива FENV_ACCESS не разрешает указанные действия, они остаются не определенными. Использование этой директивы позволяет иметь более полные сведения о том, как компилируется программа, и, соответ- ственно, лучше ее оптимизировать. Установка директивы зависит от реализа- ции, поэтому следует исходно предполагать, что она сброшена. FENV_AC- CESS устанавливается в соответствии с обычными правилами установки стан- дартных директив компилятору. Определение типа fenv_t зависит от реализации; значение этого типа содер- жит полностью состояние выполнения операций над значениями с плавающей точкой, включая режимы и флажки исключений. Расширение макроса FE_DEFL_ENV определяет состояние выполнения опе- раций над значениями с плавающей точкой в форме значения типа fenv_t*. В отдельных реализациях С могут быть определены дополнительные макросы состояния, имена которых начинаются с префикса FE_ и следующей за ним за- главной буквы. Эти макросы следует рассматривать как обозначающие объек- ты, предназначенные только для чтения. Функция fegetenv считывает текущее состояние выполнения операций над значениями с плавающей точкой и записывает его в объект, обозначенный ар- гументом envp. В случае успешного выполнения функция возвращает нуль, иначе — ненулевое значение. Функция fesetenv заменяет текущее состояние выполнения операций над значениями с плавающей точкой состоянием, обозначенным аргументом envp. Новое состояние должно быть предварительно установлено при помощи функ- ции fegetenv или feholdexcept, или же это должно быть стандартное состоя- ние наподобие FE_DEFL_ENV. В случае успешного выполнения функция воз- вращает нуль, иначе — ненулевое значение. Функция feholdexcept обычно применяется для отмены, на некоторое вре- мя, исключений, связанных с выполнением операций над значениями с пла- вающей точкой. Она сохраняет текущее состояние в объекте, обозначенном ар- гументом envp, и устанавливает состояние, при котором все указанные исклю- чения игнорируются. В случае успешной установки «непрерываемого» состояния функция возвращает нуль, иначе — ненулевое значение. В некото- рых реализациях игнорирование всех исключений невозможно. Функция feupdateenv сохраняет генерированные на текущий момент ис- ключения в некоторой локальной переменной, устанавливает состояние, обо- значенное аргументом envp, и вновь генерирует сохраненные исключения. В случае успешного выполнения, функция возвращает нуль, иначе — ненуле- вое значение. Ссылки: директивы компилятору и правила их установки 3 7, генерирование исключений в операциях нод значениями с плавающей точкой 22.3
482 Глава 22 22.3 Исключения Краткий перечень ffinclude <fenv.h> macro FE_DIVBYZERO ... macro FE_INEXACT ... macro FE_INVALID ... macro FE_OVERFLOW macro FE_UNDERFLOW ... macro FE_ALL_EXCEPT ... typedef ... fexcept t; int fegetexceptflag(fexcept_t *flagp, int excepts); int fesetexceptflag(const fexcept_t *flagp, int excepts); int fetestexcept(int excepts); int feraiseexcept(int excepts); int feclearexcept(int excepts); Исключения — побочный эффект выполнения некоторых операций над значениями с плавающей точкой. При генерировании каждого исключения, устанавливается соответствующий флажок. В зависимости от установки режи- ма выполнения операций над значениями с плавающей точкой, исключение может прерывать либо не прерывать нормальное выполнение программы. Определение типа fexcept_t зависит от реализации; значение этого типа со- держит все флажки состояния выполнения операций над значениями с пла- вающей точкой, поддерживаемые в данной реализации. Часто это целый тип, отдельные биты которого соответствуют различным исключениям, но может использоваться и более сложная конструкция. Например, значение типа fexcept_t может содержать сведения о том, где было генерировано то или иное исключение. Набор исключений, генерируемых в операциях над значениями с плаваю- щей точкой, в общем случае зависит от реализации С. Для каждого исключе- ния должен быть определен макрос наподобие FE_DIVBYZERO, FE_IN- ЕХАСТ, FE INVALID, FEOVERFLOW и FE_UNDERFLOW. Исключения, которые не поддерживаются, должны оставаться неопределенными (не просто определенными как нулевые). Расширение каждого из определенных макро- сов — целое константное выражение; должна быть обеспечена возможность объединения этих значений в побитовой операции ИЛИ для представления любого подмножества исключений. Как правило, расширения каждого макро са представляет некоторую степень двух. Расширение макроса FE_ALL_EX- СЕРТ — это объединение в побитовой операции ИЛИ всех поддерживаемых исключений. Сигнатуры функций, рассматриваемых в этом разделе, указыва- ют на то, что число исключений не может превышать число битов в типе int — минимум 16. Функция fegetexceptflag записывает текущую установку флажков состоя- ния выполнения операций над значениями с плавающей точкой в объект, обо- значенный аргументом flagp. Впрочем, в переменную flagp* записываются не все флажки, а только те, для которых установлены биты в аргументе excepts;
Операции над значениями с плавающей точкой 483 биты переменной flagp*, соответствующие исключениям, которые не записы- ваются, остаются в прежнем состоянии. Аргумент excepts действует как маска «интересных» исключений. В случае успешного выполнения, функция возвра- щает нуль, иначе — ненулевое значение. Функция fesetexceptflag устанавливает флажки состояния выполнения операций над значениями с плавающей точкой соответственно значению, за- писанному в объекте, обозначенном аргументом flagp. Устанавливаются не все флажки, а только те, которые установлены в аргументе excepts; остальные не изменяются. Аргумент excepts действует как маска «интересных» исключе- ний. Если функции удается установить все указанные флажки, она возвраща- ет нуль, иначе — ненулевое значение. Функция fetestexcept возвращает объединение в побитовой операции ИЛИ значений макросов, соответствующих текущей установке состояния выполне- ния операций над значениями с плавающей точкой и, одновременно, установ- ленных в значении аргумента excepts. Таким образом, fetestexcept возвраща- ет подмножество установленных исключений множества excepts. Функция feraiseexcept генерирует исключения, указанные в аргументе excepts. Порядок их генерирования не определяется, в процессе выполнения функции возможно генерирование других исключений: к примеру, с другими исключениями часто сочетается FE_INEXACT. Функция feclearexcept сбрасывает все флажки исключений в текущем со- стоянии, установленные в аргументе excepts. В случае успешного сбрасыва- ния всех флажков, функция возвращает нуль, иначе — ненулевое значение. 22.4 Режимы округления Краткий перечень ftinclude <fenv.h> macro FE_DOWNWARD ... macro FE_UPWARD ... macro FE_TONEAREST .. macro FE_TOWARDZERO int fegetround(void); int fetestrour.d (int rounds); В реализации C99 для каждого направления округления, которое может быть установлено и использовано функциями этой главы, должны быть опре- делены макросы наподобие FE_DOWNWARD, FE_UPWARD, FE_TONE- AREST и FE_TOWARDZERO. Расширения макросов представляют целые не- отрицательные константные выражения, представимые в типе int. Направле- ния округления, которые не поддерживаются, не имеют соответствующих макросов. Функция fegetround возвращает текущее направление округления, пред- ставленное в виде значения одного из указанных макросов. Аналогично, функ- ция fesetround устанавливает направление округления и возвращает, в случае успешного выполнения, нуль. В случае, если текущее направление округле- ния не удается определить или установить, каждая из функций возвращает от- рицательное значение.

Глава 23 Комплексная арифметика Функции этой главы предназначены для поддержки комплексной арифме- тики и объявлены в заголовочном файле complex.h С99. 23.1 Соглашения комплексной библиотеки Все угловые величины выражаются в радианах. Комплексное число z иначе может быть записано в виде x+yi, где х и у — действительные числа. Анало- гично, w = u+vi и с = а+Ы. Если функция имеет разрез на комплексной плоскости, нарушающий ее не- прерывность, действует одно из следующих соглашений (в зависимости от реа- лизации). Если нуль, определенный в реализации, может иметь знак, стороны разреза различаются по знаку нуля; иначе, разрез должен трактоваться таким образом, что функция остается непрерывной, если приближается к разрезу против часовой стрелки вокруг конечной точки разреза. Ссылки: комплексные типы 5.2.1. 23.2 complex, _Complex_l, imaginary, imaginary l, I Краткий перечень #include <complex.h> // Все - C99 #define complex _Complex tfdefine imaginary —Imaginary #define Complex I #define Imaginary_I ... ftdefine I ... В случае поддержки комплексных типов, в качестве синонима ключевого слова -Complex должен быть определен макрос complex. В случае поддержки мнимых типов, в качестве синонима ключевого слова —Imaginary должен быть определен макрос imaginary. В случае поддержки соответствующих констант- ных типов, должны быть определены макросы _Complex_I и _Imaginary—I, как константные выражения, соответственно, типов const float -Complex
486 Глава 23 и const float -Imaginary. Значения обоих макросов должны быть равны мни- мой единице — то есть V-1 или i. Если поддерживаются комплексные типы, расширение макроса I равно _Complex_I. Если определен мнимый тип, расширение I может быть равно _Imaginary_I Поскольку идентификаторы complex, imaginary и I могут использоваться в программах, написанных до появления С99, определения этих макросов можно отменить (#undefine) либо, возможно, переопределить их. Ссылки: комплексные типы 5.2.1. 23.3 CX_LIIVHTED_RANGE Краткий перечень #include <complex.h> // Все - С99 ftpragma STDC CX LIMITED RANGE установка-сброс Стандартная директива компилятору CX_LIMITED_RANGE, если установ- лена, разрешает использовать «очевидные» реализации операций умножения, деления и вычисления абсолютного значения над комплексными числами. По умолчанию, эта директива сброшена. Директива CX_LIMITED_RANGE оформляется согласно правилам для стандартных директив компилятору. «Очевидными» считаются следующие реализации: умножение: z*w = (x+iy)(u+iv) = (xu-yv)+i(yu+xv) деление: г/w = (x+n/)/(u+it>) = ((xu+yi>)+i(</u-xt>))/(u2+t>2) абсолютное значение: |z| = |х+дг/| = i/(x2 + у2) Эти реализации не вполне удобны, поскольку подвержены неоправданным переполнению и потере значимости, а также из-за недостаточно эффективной обработки значений бесконечности. Однако если есть уверенность, что в опре- деленной программе эти недостатки не проявятся, можно предпочесть именно эти реализации ввиду высокой скорости выполнения. Ссылки: стандартные директивы компилятору, псрсключотепь установка-сброс, правила размещения 3.7. 23.4 cacos, easin, catan, ccos, csin, ctan Краткий перечень #include Ccomplex.h> // Все - C99 double complex cacos (double complex z), float complex cacosf(float complex z); long double complex cacosl(long double complex z); double complex easin (double complex z); float complex casinf(float complex z) ; long double complex casinl(long double complex z); double complex catan (double complex z); float complex catanf(float complex z); long double complex catanl(long double complex z);
Комплексная арифметика 487 Краткий перечень double complex ccos (double complex z); float complex ccosf(float complex z); long double complex ccosl(long double complex z); double complex csin (double complex z); float complex csinf(float complex z); long double complex csinl(long double complex z); double complex ctan (double complex z); float complex ctanf(float complex z); long double complex ctanl(long double complex z); Области определения и значений указанных функций перечислены в табли- це 23.1 в предположении (a+bi)=f(x+yi). Таблица 23.1. Области определения и области значений комплексных тригонометрических функций Имя Функция Разрезы Область значений cacos комплексный арккосинус у = 0, х > +1; у = 0, х < - I 0 < о < л easin комплексный арксинус у= 0, х> +1; у = 0, х< -1 -л/2 < а < + я/2 catan комплексный арктангенс х = 0, у> + I; х = 0. у < -1 -т.1'1 < а < +л/2 CCOS комплексный косинус csin комплексный синус ctan комплексный тангенс 23.5 cacosh, casinh, catanh, ccosh, csinh, ctanh Краткий перечень ((include <complex.h> // Все - C99 double complex cacosh (double complex z); float complex cacoshf(float complex z); long double complex cacoshl(long double complex z); double complex casinh (double complex z); float complex casinhf(float complex z); long double complex casinhl(long double complex z); double complex catanh (double complex z); float complex catanhf(float complex z); long double complex catanhl(long double complex z); double complex ccosh (double complex z); float complex ccoshf(float complex z); long double complex ccoshl(long double complex z); double complex csinh (double complex z); float complex csinhf(float complex z); long double complex csinhl(long double complex z); double complex ctanh (double complex z); float complex ctanhf(float complex z); long double complex ctanhl(long double complex z) ;
488 Глава 23 Области определения и значений указанных функций перечислены в табли- це 23.2 в предположении (a+bi)=f(x+yi). Таблица 23.2. Области определения и области значений комплексных гиперболических функций Имя Функция Разрезы Область значений cacosh комплексный гиперболический арккосинус у - 0, х < +1 0 < а, -я < Ь < +я casinh комплексный гиперболический арксинус х = 0, у > +1; х - 0, у < -1 -я/2 < Ь < +я/2 catanh комплексный гиперболический арктангенс у = 0, х > +1; у = 0, х < -1 -я/2 < Ь < +я/2 ccosh комплексный гиперболический косинус csinh комплексный гиперболический синус ctanh комплексный гиперболический тангенс 23.6 сехр, dog, cabs, cpow, csqrt Краткий перечень ((include <complex.h> // Все - C99 double float long double complex complex complex cexp (double complex z); cexpf(float complex cexpl(long double complex z) Z) double float long double complex complex complex clog (double complex z); clogf(float complex clogl(long double complex z) Z) double float long double cabs (double complex z); cabsf(float complex cabsl(long double complex z); z) double double double float complex complex complex complex cpow ( Z f U) ; cpowf( float complex z, float complex u); long double complex cpowl( long double complex z, long double complex u); double complex csqrt (double complex z); float complex csqrtf(float complex z); long double complex csqrtl(long double complex z); Области определения и значений указанных функций перечислены в табли- це 23.3 в предположении (a+bi)=f(z)^f(x+yi) или (a+bi)=f(z, iv)=f(x+yi, u+vi). Таблица 23.3. Области определения и области значений комплексных экспоненциальных и степенных функций Имя Функция Разрезы Область значений сехр & clog In z у=0, х^О -я<6<+я
Комплексная арифметика 489 Имя Функция Разрезы Область значений cabs абсолютное значение о = sqrt(№ + у2) cpow 2м у=0,х<0 csqrt квадратный корень у = 0, х< 0 23.7 carg, ci mag, creal, conj, cproj Краткий перечень ttinclude <complex.h> // Все - C99 double carg (double complex z); float cargf(float complex z) ; long double cargl(long double complex z) ; double cimag (double complex z); float cimagf(float complex z) ; long double cimagi(long double complex z) ; double creal (double complex z); float crealf(float complex z) ; long double creall(long double complex z) ; double complex conj (double complex z); float complex conjf(float complex z); long double complex conjl(long double complex z); double complex cproj (double complex z); float complex cprojf(float complex z); long double complex cprojl(long double complex z); Области определения и значений указанных функций перечислены в табли- це 23.4 в предположении (a+bi)=f(x+yi) или (a+bi)=f(x+yi, u+vi). Таблица 23.4. Области определения и значений различных комплексных функций Имя Функция Разрезы Область значений carg аргумент (иначе — фазовый угол) у=0, х<0 [~л, +л] cimag creal мнимая часть z у действительная часть z. х conj cproj о = х, Ь = -у проекция на сферу Римана Значение функции carg(z) равно углу на комплексной плоскости между по- ложительным направлением действительной оси и отрезком, проведенным в точку z из начала координат. Значение функции cproj(z) равно z, если значение z конечно; при бесконеч- ном z, cproj(z) равно положительной действительной бесконечности в форме комплексного числа. Если реализация поддерживает нули со знаком, тогда при бесконечном z (нулевая) мнимая часть функции cproj (z) будет иметь тот же знак, что и мнимая часть z. Чтобы значение z было бесконечным, достаточ- но чтобы была бесконечной его действительная либо мнимая часть, даже если другая часть равна NaN.

Глава 24 Функции обработки широких и многобайтовых символов Функции этой главы предназначены для обработки широких и многобайто- вых символов, а также строк этих символов. Функции классификации и ото- бражения символов объявлены в заголовочном файле wctype.h, остальные символьные и строковые функции — в файле wchar.h. По большей части, дан- ные функции дублируют соответствующие функции обработки традиционных символов и соответствующих строк, объявленные в файлах ctype.h, string.h и stdio.h, ио с соответствующим изменением типов аргументов и возвращае- мых значений. 24.1 Основные типы и макросы Краткий перечень ftinclude <wchar.h> typedef ... wchar_t; typedef ... wint_t; typedef ... mbstate_t; typedef ... size_t; #define WEOF ... ftdefine WCHAR_MIN ... ftdefine WCHAR MAX ... Тип wchar_t (wide-character type — широкосимвольный тип) — это целый тип, в котором можно представить все отдельные значения любого расширен- ного набора символов времени выполнения в поддерживаемых географиче- ских установках. Этот тип может быть беззнаковым либо со знаком, и он так- же объявлен в stddef.h. Макросы WCHAR_MIN и WCHAR_MAX устанавли- вают пределы допустимых численных значений типа wchar_t; они могут не соответствовать значениям расширенных символов.
492 Глава 24 Тип wint_t также является целым типом, который может содержать все значения типа wchar_t и, кроме этого, минимум одно значение, не принадле- жащее расширенному символьному набору. Это константное значение опреде- ляется макросом WEOF и используется для обозначения «конца ввода» и дру- гих исключительных условий. Аргументы типа wint_t не подвергаются «обычным преобразованиям аргументов». Тип mbstate_t описывает объекты, не относящиеся к массивам; они могут задавать состояние преобразования из последовательностей многобайтовых символов в строки широких и обратно. Тип size_t — аналогичный тип, объявленный в stddef.h. Ссылки: size_t 11.1; wchar_t 11.1, широкие символы 2.7.3. 24.2 Преобразование из широких символов в многобайтовые и обратно Краткий перечень ftinclude <wchar.h> size_t mbrlen(const char *s, size_t n, mbstate_t *ps); wint_t btowc(int c); size_t mbrtowc (wchar_t *pwc, const char *s, size_t n, mbstate_t *ps); int wctob(wint_t c); size_t wcrtomb(char *s, wchar_t wc, mbstate_t *ps); int mbsinit(const mbstate t *ps); Функции преобразований этого раздела представляют расширения базовых функций, объявленных в stdlib.h: mblen, mbtowc и wctomb (раздел 16.10). Эти функции, введенные в Дополнении 1 к С89, более гибки, их поведение бо- лее определенно. Функция mbrlen просматривает до п байт строки, обозначенной аргумен- том s, проверяя, составляют или они правильный многобайтовый символ в со- ответствии с состоянием преобразования, указанным аргументом ps. Если ps — нулевой указатель, функция использует собственный объект состояния, инициализированный в исходное состояйие при запуске программы. Если s — нулевой указатель, просматриваемая строка трактуется как пустая, а п прини- мается равным 1. Если s указывает на нулевой широкий символ, функция воз- вращает 0 (вне зависимости от того, сколько байт составляют этот многобайто- вый символ). Если s — любой другой многобайтовый символ, функция возвра- щается число байт, его составляющих (то есть число от 1 до п). Если s — неполный многобайтовый символ, функция возвращает -2. Если s — непра- вильный многобайтовый символ, функция возвращает -1, а в переменную errno записывается значение EILSEQ. Если возвращаемое значение неотрица- тельно, происходит изменение состояния преобразования; если оно равно -1, состояние преобразования неопределенно; если -2 — остается прежним. Функция btowc возвращает широкий символ, соответствующий байту с, рассматриваемому в исходном состоянии преобразования как многобайтовый символ длиной в 1 байт. Если с (приведенный к типу unsigned char) не соот- ветствует правильному многобайтовому символу или равен EOF, btowc возвра- щает WEOF.
Функции обработки широких и многобайтовых символов 493 ' Функция mbrtowc преобразует многобайтовый символ s в широкий символ, соответственно состоянию преобразования ps. Если ps — нулевой указатель, функция использует собственный объект состояния, инициализированный в исходное состояние при запуске программы. Если pwc — не нулевой указа- тель, результат записывается в объект, на который указывает этот аргумент. Если s — нулевой указатель, тогда вызов mbrtowc эквивалентен вызову mbrtowc(NULL, 1, ps) — то есть s трактуется как пустая строка, а значе- ния pwc и п игнорируются. Если s преобразуема в нулевой широкий символ, функция возвращает 0 (даже если этот символ представлен только частью байтов, составляющих s). Если же s — правильный многобайтовый символ, функция возвращает число байт, его составляющих. Если s — неполный мно- гобайтовый символ, функция возвращает -2. Наконец, если s — неправиль- ный многобайтовый символ, функция возвращает -1. Если преобразование проходит успешно, происходит изменение состояния преобразования, обозна- ченного аргументом ps (или внутреннего состояния преобразования, если ps — нулевой указатель). Если s — неполный многобайтовый символ, состояние преобразования остается прежним, если неправильный — неопределенным. Функция wctob (Дополнение 1 к С89) возвращает многобайтовый символ размером в 1 байт, соответствующий широкому символу с в исходном состоя- нии преобразования. Если соответствующий байт не существует, функция воз- вращает EOF. Функция wcrtomb преобразует широкий символ wc в многобайтовый сим- вол, соответствующий состоянию преобразования, обозначенному аргументом ps. Если ps — нулевой указатель, используется внутренний объект состояния преобразования. Многобайтовый символ записывается в массив, на первый элемент которого ссылается s и длина которого должна составлять не менее MB_CUR_MAX символов. Состояние преобразования изменяется. Если wc — нулевой широкий символ, в массив записывается — вслед за регистровой по- следовательностью, необходимой для восстановления исходного состояния преобразования, — нулевой широкий символ. Функция возвращает число символов, записанных в s. Если s — нулевой указатель, wc игнорируется, функция же просто восстанавливает исходное состояние преобразования и воз- вращает 1 (как после преобразования L'\0' в скрытый буфер). Если wc не яв- ляется правильным широким символом, в переменную еггпо записывается значение EILSEQ, а функция возвращает -1. Функция mbsinit возвращает ненулевое значение, если ps является нуле- вым указателем либо ссылается на объект, представляющий исходное состоя- ние преобразования; в остальных случаях, функция возвращает нуль. Ссылки: EILSEQ 11.2, еггпо 11.2, многобайтовые символы 2.1 5, mbstate_t 1] 1, size_t 11.1; wchar_t 11.1; wint_t 111. 24.3 Преобразование из строк широких символов в строки многобайтовых и обратно Кроткий перечень #include <wchar.h> size_t mbsrtowcs (wchar_t *pwcs, const char **src, size_t n, mbstate_t *ps); size t wcsrtombs(char *s, const wchar t **src, size t n, mbstate_t *ps);
494 Глава 24 Функции этого раздела представляют собой «рестартуемые» версии функ- ций mbstowcs и wcstombs, объявленных в stdlib.h (см. раздел 16.11). Они вве- дены в Дополнении 1 к С89. Функция mbsrtowcs преобразует последовательность многобайтовых сим- волов в строке с нуль-окончанием s в соответствующую последовательность широких символов, записываемых в массив, обозначенный аргументом pwcs. Исходное состояние преобразования указывается аргументом ps, входная по- следовательность многобайтовых символов — (опосредованно) аргументом src. При обычном выполнении этой операции каждый многобайтовый символ, включая нулевой символ в конце, преобразуется как в функции mbrtowc, за- писывающей выходные широкие символы в массив, обозначенный аргумен- том pwcs. После преобразования указателю, обозначенному аргументом src, присваивается нулевое значение, что свидетельствует о преобразовании всей строки, а функция возвращает число символов, записанных в pwcs (без учета нулевого символа в конце). Состояние преобразования заменяется исходным состоянием регистра — следствие преобразования нулевого символа в конце входной строки многобайтовых символов. Выходной указатель pwcs может быть нулевым; в этом случае mbsrtowcs просто вычисляет длину выходной строки широких символов, необходимой для преобразования. Преобразование входной многобайтовой строки может прерваться из-за ошибки преобразования. В этом случае, указатель, обозначенный аргументом src, устанавливается на многобайтовый символ, попытка преобразования ко- торого привела к ошибке. Функция возвращает -1, в переменную еггпо запи- сывается значение EILSEQ, состояние преобразования оказывается неопреде- ленным. Функция wcsrtombs преобразует последовательность широких символов, начинающуюся со значения, обозначенного аргументом pwcs, в последова- тельность многобайтовых символов, записывая результат в символьный мас- сив, обозначенный аргументом s. Исходное состояние преобразования указы- вается аргументом ps, входная строка широких символов — (опосредованно) аргументом src. При обычном выполнении этой операции каждый широкий символ, включая нулевой символ в конце, преобразуется как в функции wcrtomb, записывающей выходные многобайтовые символы в массив, обозна- ченный аргументом s. После преобразования указателю, обозначенному аргу- ментом src, присваивается нулевое значение, что свидетельствует о преобразо- вании всей строки, а функция возвращает число символов, записанных в s (без учета нулевого символа в конце). Состояние преобразования заменяется исход- ным состоянием регистра — следствие преобразования нулевого символа в конце входной строки широких символов. Выходной указатель s может быть нулевым; в этом случае wcsrtombs просто вычисляет длину выходной сим- вольной строки, необходимой для преобразования. Преобразование входной строки широких символов прерывается до преоб- разования нулевого широкого символа в конце, если число байт, записанных в s, достигает п (при этом s не является нулевым указателем). В этом случае указатель, обозначенный аргументом src, устанавливается на широкий сим- вол, следующий непосредственно за символом, который был преобразован по- следним. Состояние преобразования изменяется (оно не должно обязательно быть равно исходному состоянию), а функция возвращает п. Преобразование входной строки широких символов может прерваться также в случае ошибки преобразования. В этом случае указатель, обозначенный аргу- ментом src, переводится на широкий символ, попытка преобразования которого
Функции обработки широких и многобайтовых символов 495 привела к ошибке. Функция возвращает -1, в переменную errno записывается значение EILSEQ, состояние преобразования остается неопределенным. Ссылки: состояние преобразовония 2.1.5; многобайтовый символ 2.1.5; широкий сим- вол 2.1.5. 24.4 Преобразование в арифметические типы Краткий перечень ttinclude <wchar.h> double wcstod( const wchar_t * restrict str, wchar_t ** restrict ptr ); float wcstof ( const wchar_t * restrict str, wchar_t ** restrict ptr ); long double wcstold( const wchar_t * restrict str, wchar_t ** restrict ptr ) ; long wcstol ( const wchar_t * restrict str, wchar_t ** restrict ptr, int base ); long long wcstoll( const wchar_t * restrict str, wchar_t ** restrict ptr, int base ); unsigned long wcstoul( const char * restrict str, wchar_t ** restrict ptr, int base ); unsigned long strtoull( const char * restrict str, wchar t ** restrict ptr, int base ); Функции wcsto..., рассматриваемые в этом разделе, аналогичны своим двойникам strto... из раздела 16.4, за исключением типов аргументов, исполь- зования функции iswspace для обнаружения пробельных символов и исполь- зования широкого символа десятичной точки вместо обычной точки. Данные функции преобразования строк широких символов могут принимать в качест- ве аргументов не только строки, принимаемые функциями strto..., но также и дополнительные строковые типы, определяемые реализациями. Функции wcstod, wcstol и wcstoul были введены в Дополнении 1 к С89, ос- тальные — в С99. 24.5 . Функции ввода и вывода Функции, предназначенные для ввода и вывода (строк) широких символов, перечислены в таблице 24.1, вместе с их однобайтовыми двойниками и разде- лами книги, в которых рассматриваются те и другие.
496 Глава 24 Таблица 24.1. Функции ввода/вывода (строк) широких символов Широкая функция Раздел Однобайтовая функция fgetwc 15.6 fgetc fgetws 15.7 fgets fputwc 159 fputc fputws 15.10 fputs fwide 15.2 fwprintf 15.11 fprintf fwscanf 15.8 fscanf getwc 15.6 getc getwchar 15.6 getchar putwc 15.9 putc putwchar 15.9 putchar swprintf 15.11 sprintf swscanf 15.8 sscanf ungetwc 15.6 ungetc vfwprintf 15.12 vfprintf vfwscanf 15.12 vfscanf vswprintf 15.12 vsprintf vswscanf 15.12 vsscanf vwprintf 15.12 vprintf vwscanf 15.8 vscanf wprintf 15.11 printf wscanf 15.8 scanf 24.6 . Строковые функции В таблице 24.2 перечислены функции обработки (строк) широких симво- лов, наряду с их однобайтовыми двойниками и разделами книги, в которых рассматриваются те и другие. Таблица 24.2. Функции обработки широких символов Широкая функция Роздел Однобайтовая функция wcscat 13 1 strcat wcschr 13.5 strchr wcscmp 13.2 strcmp wcscoll 13 10 strcoll wcscpy 13 3 strcpy wcscspn 13.6 strcspn wcslen 13.4 strlen wcsncat 13.1 strncat wcsncmp 13.2 strncmp
Функции обработки широких и многобайтовых символов 497 Широкая функция Раздел Однобайтовая функция wcsncpy 13.3 strnepy wcspbrk 13.6 strpbrk wcsrchr 13.5 strrehr wcsspn 13.6 strspn wcsstr 13.7 strstr west ok 13.7 strtok wcsxfrm 13.10 strxfrm wmemchr 14.1 memchr wmemcmp 14.1 memcmp wmemepy 14.3 memepy wmemmove 14.3 memmove wmemset 14.4 memset 24.7 Преобразование даты и времени Широкосимвольная функция wcsftime соответствует однобайтовой strftime. Ссылки: strftime 18.6. 24.8 Функции классификации и отображения широких символов В таблице 24.3 перечислены функции классификации и отображения ши- роких символов, наряду с их однобайтовыми двойниками и разделами книги, в которых они рассматриваются. Таблица 24.3. Функции обработки широких символов Широкая функция Раздел Однобайтовая функция iswalnum 12.1 isalnum iswalpha 12.1 isalpha iswblank 12. isblank iswcntrl 12.1 iscntrl iswetype 12. isetype iswdigit 12.3 isdigit iswgraph 12.4 isgraph iswlower 12.5 islower iswprint 12.4 isprint iswpunct 12.4 ispunct iswspace 12.6 isspace iswupper 12.5 isupper
498 Глава 24 Широкая функция Раздел Однобайтовая функция iswxdigit 12 3 isxdigit towlower 12.9 tolower towupper 129 toupper wctrans 12.11 ctrans Широкосимвольная функция towctrans не имеет однобайтового двойника. Ее сигнатура: ftinclude <wctype.h> wint_t towctrans( wint_t wc, wctrans_t desc ); Функция towctrans отображает широкий символ wc в новое значение, кото- рое и возвращает. Способ отображения указывается значением типа wctrans_t, возвращаемым функцией wctrans (раздел 12.11). Категория LC_CTYPE геогра- фической установки при вызове функции towctrans должна быть той же, что и при вызове функции wctrans_t для определения значения desc.
Приложение А Символьный набор ASCH Шести. Восьм. 0 0 0x20 040 0x40 0100 0x60 0140 Дес. Симе. Наим. Дес. Симе. Дес. Симе. Дес. Симв. 0 0 0 NUL 32 SP 64 @ 96 1 1 1 ~А ЗОН 33 1 65 A 97 a 2 2 2 ЛВ STX 34 If 66 В 98 b 3 3 3 "С ЕТХ 35 # 67 c 99 c 4 4 4 EOT 36 $ 68 D 100 d 5 5 5 ЛЕ ENQ 37 % 69 E 101 e 6 6 6 "F АСК 38 & 70 F 102 f 7 7 7 “G BEL, \а 39 t 71 G 103 g 8 010 В ~Н BS, \Ь 40 ( 72 H 104 h 9 on 9 Л1 TAB, \t 41 ) 73 I 105 i ОхА 012 10 П LF, \n 42 1c 74 J 106 j ОхВ 013 11 VT, \v 43 + 75 К 107 k ОхС 014 12 ч FF, \f 44 r 76 L 108 1 OxD 015 13 Лм CR, \r 45 - 77 M 109 m ОхЕ 016 14 SO 46 78 N 110 n OxF 017 15 'О SI 47 / 79 0 111 о 0x10 020 16 ~р DLE 48 0 80 p 112 p 0x11 021 17 "О DC1 49 1 81 Q 113 4 0x12 022 18 'R DC2 50 2 82 R 114 r 0x13 023 19 "S DC3 51 3 83 S 115 s 0x14 024 20 ~Т DC4 52 4 84 T 116 t 0x15 025 21 "U NAK 53 5 85 и 117 u 0x16 026 22 'V SYN 54 6 86 V 118 V 0x17 027 23 'W ETB 55 7 87 w 119 w 0x18 030 24 ЛХ CAN 56 8 88 X 120 X 0x19 031 25 "Y EM 57 9 89 Y 121 У OxlA 032 26 "Z SUB 58 90 Z 122 z
500 Приложение А Шести Засьм. 0 0 0x20 040 0x40 0100 0x60 0140 Дес. Симв. Наим, Дес. Симв. Дес. Симв. Дес. Симв. 0x1 В 033 27 ч ESC 59 91 [ 123 ( 0х1С 034 28 Л FS 60 < 92 \ 124 1 OxlD 035 29 "] GS 61 = 93 ] 125 } Ох1Е 036 30 RS 62 > 94 А 126 OxlF 037 31 US 63 9 95 127 DEL
Приложение В Синтаксис comma-выражение: выражение-присваивания comma-выражение , выражение-присваивания h-char-последователъностъ: любая последовательность символов, кроме > и символа конца строки q-char-последователъностъ: любая последовательность символов, кроме " и символа конца строки абстрактный-объявителъ: указатель указателъопц прямой-абстрактный-объявителъ аддитивное-выражение: мультипликативное-выражение аддитивное-выражение аддитивный-знак мультипликативное-выражение аддитивный-знак: один из + - адресное-выражение: & выражение-приведения бинарная-экспонента: р знаковая-частьопц последовательность-цифр р знаковая-частьопц последовательность-цифр битовое-поле: описательопц : ширина воеьмери иная-констан та: О восьмеричная-константа восьмеричная-цифра восьмеричная-цифра: одна из 01234567 восьмеричный-управляющий-код: восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра восьмеричная-цифра
502 Приложение В вызов-функции: постфиксное-выражение ( список-выраженияОГщ ) выражение: сотта-выражение выражение-sizeof: sizeof ( имя-типа ) sizeof унарное-выражение выражение-в-скобках: ( выражение ) выражение-выбора-компонента: непосредственный-выбор-компонента опосредованный-выбор-компонента выражение-опосредования: * выражение-приведения выражение-отношения: выражение-сдвига выражение-отношения знак-отношения выражение-сдвига выражение-побитового-И: выражение-равенства выражение-побитового-И & выражение-равенства выражение-побитового-ИЛИ: выражение-побитового-исключающего- ИЛ И выражение-побитового-ИЛИ | выражение-побитового-исключающего-ИЛ И выражение-побитового-исключающего-ИЛИ: выражение-побитового-И выражение-побитового-исключающего-ИЛИ * выражение-побитового-И выражение-побитового-НЕ: ~ выражение-приведения выражение-приведения: унарное-выражение ( имя-типа ) выражение-приведения выражение-присваивания: условное-выражение унарное-выражение знак-присваивания выражение-присваивания выражение-равенства: выражение-отношения выражение-равенства знак-равенства выражение-отношения выражение-размера-массива: выражение-присваивания * выражение-сдвига: аддитивное-выражение выражение-сдвига знак-сдвига аддитивное-выражение
Синтаксис 503 выражения-for: ( начальное-условиеО!Щ ; выражениеопц ; выражениеопц ) десятичная-константа: ненулевая-цифра десятичная-константа цифра десятичная-константа-сплавающейточкой: последовательность-цифр экспонента суффикс-плавающегоопц цифры-с-точкой экспонентаопц суффикс-плавающегоопц единица-трансляции: объявление-верхнего-уровня единица-трансляции объявление-верхнего-уровня знаковая-частъ: один из + - знак-отношения: один из <<=>>= знак-присваивания: один из = += -= *= /= %= «= »= &= А= | = знак-равенства: один из == ! = знак-сдвига: один из « » знак-умножения: один из * / % идентификатор: идентификатор-не-цифра идентификатор идентификатор-не-цифра цифра идентификатор идентификатор-не-цифра: не-цифра универсальное-символъное-имя другие символы, в зависимости от реализации именованная-инициализация: список-именованных-инициализаторов = именованная-метка: идентификатор именованный-инициализатор: [ константное-выражение ] . идентификатор имя-typedef: идентификатор имя-типа: спецификаторы-объявления абстрактный-объявительопц индексное-выражение: постфиксное-выражение [ выражение ]
504 Приложение В инициализатор: выражение-присваивания { список-инициализаторов , огщ } инициализированный-описателъ: описатель описатель = инициализатор инициализированных-список-описателей: инициализированный-описателъ инициализированных-список-описателей инициализированный-описателъ исходный-з-символ: любой исходный символ, за исключением двойных кавычек (”), обратной косой (\) и символа новой строки управляющий-символ универсальное-символьное-имя ( С99 ) исходный-символ: любой исходный символ, кроме апострофа ('), обратной косой (\) и символа новой строки управляющий-символ универсальное-символьное-имя ( С99 ) квалификатор-массива: static restrict const volatile квалификатор-типа: const volatile restrict (C99) константа: целая-константа константа-с-плавающей-точкой символьная-константа строковая-константа константа-перечислимого-типа: идентификатор константа-с-плавающей-точкой: десятичная-константа-с-плавающей-точкой шестнадцатеричная-константа-с-плавающей-точкой (С99) константное-выражение: условное-выражение лексемы-пре процессора: любая последовательность лексем С (или непробельные символы, которые не могут интерпретироваться как лексемы), не начинающаяся с < или
Синтаксис 505 логическое-выражение-И: выражение-побитового-ИЛИ логическое-выражение-И && выражение-побитового-ИЛИ логическое-выражение-ИЛИ: логическое-выражение-И логическое-выражение-ИЛИ | | логическое-выражение-И логическое-выражение-НЕ: ! выражение-приведения метка: именованная-метка метка-case метка-default метка-case: case константное-выражение метка-default: default мультипликативное-выражение: выражение-приведения мультипликативное-выражение знак-умножения выражение-приве- дения начальное-условие: выражение объявление (С99) ненулевая-цифра: одна из 123456789 непосредственный-выбор-компонента: постфиксное-выражение . идентификатор не-цифра: один из ABCDEFQHIJKLM NOPQRSTUVWXYZ abcdefghijkim nopqrstuvwxyz объявление: спецификаторы-объявления объявление-верхнего-уровня: объявление определение-функции объявление-или-оператор: объявление оператор объявление-компонента: спецификатор-типа список-описателей-компонентов ; инициализированный-список-описателей ;
506 Приложение В объявление-параметров; спецификаторы-объявления описатель спецификаторы-объявления абстрактный-описателъ0ПЦ оператор: операторное-выражение оператор-с-меткой составной-оператор условный-оператор оператор-цикла оператор-switch оператор-выхода-из-цикла onepamop-continue оператор-возврата onepamop-goto пустой-опера тор onepamop-continue: continue; onepamop-do: do оператор while ( выражение ) ; onepamop-for: for выражения-for оператор onepamop-goto: goto именованная-метка ; onepamop-if: if ( выражение ) оператор onepamop-if-else: if ( выражение ) оператор else оператор оператор-switch: switch ( выражение ) оператор onepamop-while: while ( выражение ) оператор оператор-возврата: return выражениеогщ ; оператор-выхода-из-цикла: break; опера торное-выражение: выражение ; операторе-меткой: метка : оператор оператор-цикла: onepamop-while onepamop-do onepamop-for
Синтаксис 507 описатель: описателъ-указателя прямойописателъ описатель-компонента: простой-компонент битовое-поле описатель-массива: пр ямой-описатель [ константное-выражениеопц ] (до С99) прямой описателъ [ список-квалификаторов-массиваОГ1Ц выражение-размера-массива0гщ ] (С99) прямой описателъ [ список-квалификаторов-массиваопц *] (С99) описателъ-указателя: указатель прямойописателъ описатель-фу нкции: прямой описателъ ( список-типов-параметров ) (С89) прямой описателъ ( список-идентификаторов0ПЦ ) опосредованный-выбор-компонента: постфиксное-выражение -> идентификатор определение-константы-перечислимого-типа: константа-перечислимого-типа константа-перечислимого-типа = выражение определение-перечислимого-типа: enum тег-перечисленияОПц { список-определений-перечисления } enum тег-перечисленияогщ { список-определений-перечисления ,}(С99) определение-структурного-типа: struct тег-структурыопц { список-полей } определение-типа-объединение: union тег-объединение { список-полей } определение-функции: спецификатор-опреде ленияфункции составной-оператор основное-выражение: идентификатор константа выражение-в-скобках переключателъ-on-off (установка-сброс): ON OFF DEFAULT последовательное тъ-исходных-s-символов: исходный-s символ последователъностъ-исходных-з-символов исходный-з-символ последовательность-исходных-символов: исходный-символ последовательность-исходных-символов исходный-символ
508 Приложение В последовательность-цифр: цифра последовательность-цифр цифра последовательностъ-шестнадцатеричных-цифр: шестнадцатеричная-цифра последователъностъ-шестнадцатеричных-цифр шестнадцатеричная-цифра постдекрементное-выражение: постфиксное-выражение постинкрементное-выражение: постфиксное-выражение ++ постфиксное-выражение: основное-выражение индексное-выражение выражение-выбора-компонента вызов-функции постинкрементное-выражение постдекрементное-выражение составной-литерал (С99) предекрементное-выражение: — унарное-выражение преинкрементное-выражение: ++ унарное-выражение простой-компонент: описатель простой-описатель: идентификатор прямой-абстрактный-объявителъ: ( абстрактный-объявителъ ) прямой-абстрактный-объявитель0,щ [ константное-выражениеогщ ] прямой-абстрактный-объявителъопц [ выражение ] (С99) прямой-абстрактный-объявительопц [ * ] (С99) прямой-абстрактный-объявительопц ( список-типов-параметровопц ) прямой-описатель: простой-описатель ( описатель ) описатель-функции описатель-массива пустой-оператор: символьная-константа: ' последовательность-исходных-символов ' L' последовательность-исходных-символов ' (С89) символьный-управляющий-код: один из n t Ь г f
Синтаксис 509 V \ ' " а ? (С89) составной-литерал: ( имя-типа ) { список-инициализаторов , огщ } (С99) составной-оператор: { список-объявления-или-оператораогщ } спецификатор-беззнакового-типа: unsigned short intoni< unsigned inton(+ unsigned long intonl< unsigned long long inton(< (C99) спецификатор-знакового-типа: short или short int или signed short или signed short int int или signed int или signed long или long int или signed long или signed long int long long или long long int или signed long long или signed long long int спецификатор-класса-памяти: один из auto extern register static typedef спецификатор-комплексного-типа: ( C99 ) float _Complex double _Complex long double —Complex спецификатор-определения-функции: спецификаторы-объявленияогщ описатель список-объявленийогщ спецификатор-символьного-типа: char signed char unsigned char спецификатор-типа: спецификатор-типа-перечисления специфика тор-типа-с-плавающей-точкой спецификатор-це логотипа спецификатор-типа-структура имя-typedef спецификатор-типа-объединение спецификатор-типа-void спецификатор-типа-void: void спецификатор-типа-объединение: определение-типа-объединение ссылка-на-тип-объединение спецификатор типа-перечисления: определениеперечислимого-типа ссылочный-тип-перечисления
510 Приложение В спецификатор-типа-с-плавающей-точкой: float double long double специфика mop-комплексного- типа спецификатор-типа-структура: определение-структурного-типа ссылка-на-структурный-тип (С89) (С99) спецификатор-функции: inline (С99) спецификатор-целого-типа: спецификатор-знакового-типа спецификатор-беззнакового-типа спецификатор-символьного-типа спецификатор-бу лева-типа спецификаторы-объявления: спецификатор-класса-памяти спецификаторы-объявленияогщ спецификатор-типа спецификаторы-объявленияогщ квалификатор-типа спецификаторы-объявленияопц спецификатор-функции спецификаторы-объявленияопц список-выражения: выражение-присваивания список-выражения , выражение-присваивания (С99) (С99) список-идентификаторов: идентификатор список-параметров , идентификатор список именованных-инициализаторов: именованный-инициализатор список-именованных-инициализаторов именованный-инициализатор список-инициализаторов: инициализатор список-инициализаторов , инициализатор именованная-инициализация инициализатор список-инициализаторов , именованная-инициализация инициализатор список-квалификаторов-массива: квалификатор-массива список-квалификаторов-массива квалификатор-массива (С99) (С99) список-квалификаторов-типа: квалификатор типа список-квалификаторов-типа квалификатор-типа (С89) список-объявления: объявление список-объявления объявление
Синтаксис 511 списокобъявленияилиоператора: объявлениеилиоператор списокобъявленияилиоператора объявлениеилиоператор список-описателейкомпонентов: описатель-компонента список-описателейкомпонентов , описатель-компонента список-определений-перечисления: определение-константы-перечислимого-типа список-определений-перечисления , определениеконстанты-перечис- лимого-типа список-параметров: объявление-параметра список-параметров , объявление-параметра список-полей: объявление-компонента список-полей объявление-компонента список-типов-параметров: список-параметров список-параметров , ... ссылка-на-структурный-тип: struct тег-структуры ссылка-на-тип-объединение: union тег-объединение ссылочный-тип-перечисления: enum тег-перечисления строковая-константа: " последователъность-исходных-з-символовОГщ " L" последователъность-исходных-8-символовогщ " (С89) суффикс-беззнакового-типа: один из u U суффикс-длинного: один из 1 L суффикс-длинного-длинного: один из (С99) 11 LL суффикс-плавающего: один из f F 1 L суффикс-целого: суффикс-длинного суффикс беззнакового(,„ц суффикс-длинного-длинного суффикс-беззнаковогоогщ (С99) суффикс-беззнакового суффикс-длинногоопц суффикс-беззнакового суффикс-длинного-длинногоОПЦ (С99) тег-объединения: идентификатор
512 Приложение В тег-перечисления: идентификатор тег-структуры: идентификатор указатель: * список-квалификаторов-типовопц ★ список-квалификаторов-типовопц указатель унарное-выражение: постфиксное-выражение выражение-sizeof унарное-выражение-«минус» унарное-выражение-«плюс» логическое-выражение-Н Е выражение-побитового-Н Е адресное-выражение выражение-опосредования преинкрементное-выражение предекрементное-выражение унарное-выражение-«минус»: - выражение-приведения унарное-выражение-«плюс»: ( С89 ) + выражение-приведения универсалъное-символъное-имя: \и шестнадцатеричная-четверка \U шестнадцатеричная-четверка шестнадцатеричная-четверка управляющий-код: символъный-управляющий-код восъмеричный-управляющий-код шестнадцатеричный-управляющий-код ( С89 ) управляющий-символ: \ управляющий-код универсальное-символьное-имя ( С99 ) условное-выражение: логическое-выражение-ИЛИ логическое-выражение-ИЛИ ? выражение : условное-выражение условный-оператор: onepamop-if onepamop-if-else целая-константа: десятичная-константа суффикс-це логоопц восьмеричная-константа суффикс-целогоопц шестнадцатеричная-константа суффикс-целогоопц цифра: одна из 0123456789 цифры-с-точкой: последователъностъ-цифр
Синтаксис 513 последователъностъ-цифр . последователъностъ-цифр последователъностъ-цифр шестнадцатеричная-константа: Ох шестнадцатеричная-цифра ОХ шестнадцатеричная-цифра шестнадцатеричная-константа шестнадцатеричная-цифра шестнадцатеричная-константа-с-плавающей-точкой: ( С99 ) шестнадцатеричный-префикс шестнадцатеричные-цифры-с-точкой бинарная-экспонента суффикс-плавающегоопц шестнадцатеричный-префикс последователъностъ-шестнадцатеричных-цифр бинарная-экспонента суффикс-плавающегоопц шестнадцатеричная-цифра: одна из 01234567 ABCDEFab 8 9 с d е f шестнадцатеричная-четверка: шестнадцатеричная-цифра щестнадцатеричная-цифра шестнадцатеричная-цифра шестнадцатеричная-цифра шестнадцатеричные-цифры-с-точкой: последователъностъ-шестнадцатеричных-цифр последователъностъ-шестнадцатеричных-цифр последователъностъ-шестнадцатеричных-цифр последователъностъ-шестнадцатеричных-цифр шестнадцатеричный-префикс: Ох ОХ шестнадцатеричный-управляющий-код: х шестнадцатеричная-цифра шестнадцатеричный-управляющий-код шестнадцатеричная-цифра (С89) ширина: константное-выражение экспонента: е знаковая-частъопц последователъностъ-цифр Е знаковая-частъогщ последователъностъ-цифр

Приложение С Ответы к упражнениям В данном приложении даются ответы к упражнениям, приведенным в гла- вах 2-9. Ответы к главе 2 1. Зарезервированные слова, шестнадцатеричные константы, константы строк широких символов и скобки — это лексемы. Комментарии и про- бельные символы служат лишь в качестве разделителей лексем. Три- графы удаляются перед распознаванием лексем. 2. Число лексем в каждой строке: (а) 3 лексемы. (Ь) 2 лексемы, — это знак операции, не принадлежащий константе. (с) 1 лексема. (d) 3 лексемы, вторая — "F00". (е) 1 лексема. (f) 4 лексемы, ** не является отдельным знаком операции. (g) Не лексема, как и "Х\" — строковая константа без символа окончания. (h) Не лексема — идентификаторы не могут содержать $. (i) 3 лексемы, *= — знак операции. (j) Ни одной либо 3. ## не является лексемой, но оказывается лексе- мой препроцессора. 3. Результат равен ***/, комментарии обозначены скобками во второй строке, кавычки внутри комментария могут быть непарными. /**/*/*'*/*/*'//*//**/*/ ( —) ( —) (-----) (—) 4. Порядок должен быть следующим: 1. преобразование триграфов; 2. обработка продолжений строк; 3. удаление комментариев; 4. объединение символов в лексемы. 5. Возможные затруднения: (а) отдельные слова, составляющие идентификатор, неразличимы; следует выделять их заглавными буквами либо отделять символами подчеркивания;
516 Приложение С (b) идентификатор подобен зарезервированному слову; (с) строчную букву 1 и заглавную О можно принять за цифры 1 (едини- ца) и 0 (нуль); (d) можно принять за численный литерал (первый символ — буква О); (е) если компилятор примет этот идентификатор, он определит его как расширение. 6. (а) Например: х = а //‘поделить на*/ Ь; (Ь) если в некоторой реализации Standard С идентификаторы различа- ются только по первым 31 символу, то прочтение программой Standard С этого идентификатора, после 31 символа иным образом будет помече- но в C++ как ошибка; (с) например, объявление: int class = 0; (d) выражение sizeof (' a' }==sizeof (char) будет иметь разный смысл в С и C++, поскольку sizeof (char) !=sizeof (int) . Ответы к главе 3 1. (а) Пробел перед левой скобкой не разрешен ни в традиционном, ни в Standard С. Макрос ident будет определен как не имеющий парамет- ров, его расширение будет равно " (х) х". (Ь) Символы = и ; необязательны и, вероятно, неправильны. В некото- рых компиляторах традиционного С могут возникать проблемы из-за пробела после символа #. (с) Данное определение правильно. (d) Данное определение правильно; имена макросов могут совпадать с зарезервированными словами. 2. Standard С Традиционный С (а) Ь+а Ь+а (Ь) х 4 (две лексемы) х4 (одна лексема) (с) "a book” # a book (d) p?free(р):NULL р?р?р? ...: NULL : NULL : NULL (до бесконечности) 3. Результат обработки (при игнорировании пробельных символов) сле- дующие три строки: int blue = 0; int blue = 0; int red = 0; 4. Поскольку ни аргументы, ни тело макроса не заключены в скобки, в сложных выражениях его расширение может быть неправильно ин- терпретировано. Ниже приведен более надежный вариант: #define DBL(а) ( (а) + (а)) 5. Расширение этого макроса происходит в следующей последовательности: М(М) (А,В) ММ (А, В) А = "В"
Ответы к упражнениям 517 6. Данное решение зависит от наличия элементов defined и #еггог: #if !defined(SIZE) || (SIZEC1) || (SIZE>10) #error "параметр SIZE определен неправильно " #endif 7. В препроцессорной команде #include </a/file.h>, последователь- ность /а/file.h считается лексемой (имя отдельного файла); для ком- пилятора это не лексема. 8. Очевидно, программист пытается задать вывод сообщения об ошибке в случае х==0. Однако проверка условия х==0 должна происходить во время выполнения программы, тогда как #еггог — директива компи- лятору. При компиляции этой программы всегда будет возникать ошибка (при любом значении х). Ответы к главе 4 1. При каждом вызове функция будет возвращать значение аргумента. Толь- ко в случае объявления i внутри функции Р с классом памяти static, она будет возвращать новое значение при каждом следующем вызове. 2. Объявления f, одновременно, как функции, целой переменной, типа и константы перечислимого типа противоречат друг другу. Следует ос- тавить не более одного из этих объявлений. Использование f в качестве тега, одновременно, структуры и объединения также несовместимо. Следует удалить объявление объединения, указанного также в качестве поля структуры. Наличие метки f противоречит остальным объявлени- ям лишь в нескольких устарелых реализациях С. Код int i; long i; float i; 1 int i ; (объявлена) 2 void f(i) (объявлена) 3 long i; (объявлена) 4 { 5 long 1 = i ; (используется) 6 ( 7 float i; (объявлена) 8 i = 3.4; (используется) 9 } 10 1 = i+2; (используется) 11 } 12 int *p = fii; (используется) 4. (a) extern void Р (void) ; (b) register int i; (c) typedef char *LT; (d) extern void Q(int i, const char *cp); (e) extern int R( double *(*p) (long i) ) ; (f) static char STR[11] ; (С учетом нулевого символа.) (g) const char STR2 [] = INIT_STR2; скобки вокруг INIT_STR2 необя- зательны. Возможно также: const char *STR2=INIT_STR2; (без скобок). (h) int *IP = &i ; 5. int m[3] [3] = ({1,2,3},{1,2,3},{1,2,3}} ;
518 Приложение С Ответы к главе 5 1. Следует заметить, что ни один из указанных типов не должен включать тип int, поскольку размер int не может превышать размера short. (a) long или unsigned long (unsigned short может оказаться недос- таточным для значения 99999). (Ь) Структура с двумя полями: типа short (для кода города) и типа long (для местного номера). Можно использовать также беззнаковые варианты указанных типов. (с) char (любой вариант). (d) В Standard С — signed char, в прежних реализациях — short (char со знаком может отсутствовать). (е) В Standard С — signed char, в прежних реализациях — short (char со знаком может отсутствовать). (f) Подойдет double, но с точки зрения экономии памяти, предпочти- тельнее будет тип long с записью баланса в центах. 2. UP_ARROW_KEY имеет тип int и значение 0x86 (134). Если компьютер работает с типом signed char, символы расширенной части набора оказываются отрицательными. Если аргументу функции is_up_arrow присвоить значение 0x86, операция сравнения в операторе return бу- дет иметь вид -122==134, и вместо значения true будет возвращено false. Чтобы функция работала правильно, следует привести код симво- ла к типу char либо аргумент — к типу unsigned char. Для этого не- обходимо заменить оператор return одним из следующих: return с == (char) UP_ARROW_KEY; return (unsigned char) c == UP_ARROW_KEY; Первый вариант несколько предпочтительнее, поскольку оставляет больше свободы в определении значения UP_ARROW_KEY. 3. (а) Правильно. (Ь) Правильно. (с) Неправильно; невозможно обращение к значению, на которое ссы- лается указатель void *. (d) Неправильно; невозможно обращение к значению, на которое ссы- лается указатель void *. 4. (е) * (iv + i) (f) *(*(im+i)+j) 5. 13. Приведение типа в Standard С необязательно, но делает исходный текст более наглядным и может понадобиться в некоторых более ран- них компиляторах. 6. x.i = 0; х . F . s = 0 ; (допустимы только значения 0 и 1). x.F.e = 0; x.F.m = 0; х . U. d = 0 . 0 ; (или х .U . р = NULL;, но не х. U. а [0] = ' \0 ' ;, что ос- тавляет некоторые элементы а неопределенными).
Ответы к упражнениям 519 7. Схемы показаны ниже. Указано число битов, занимаемых каждым по- лем, вертикальными чертами внизу помечены границы слов. Обратите внимание на порядок следования битовых полей. «Остроконечный» вариант, упаковка битов справа налево 8. typedef int *fpi(); /* определение типа */ fpi *х; /* объявление переменной */ int *fpi() /* функция; typedef в заголовке не допускается */ { return (fpi *)0; } Ответы к главе 6 1. Все приведения типов допустимы как в традиционном, так и в Standard С, за исключением (с) и (е), которые в Standard С недопустимы. 2. В данном решении предполагается, что компилятор традиционного С до- пускает несоответствие ссылочных типов в операторах присваивания, в остальном же действуют правила Standard С. Впрочем, для некоторых традиционных компиляторов решения будут теми же, что и в п. 1. (а) Допустимо в традиционном и Standard С. (Ь) Допустимо в традиционном, недопустимо в Standard С. (с) Допустимо в традиционном, недопустимо в Standard С. (d) Недопустимо в традиционном и Standard С. (е) Допустимо в традиционном, недопустимо в Standard С. (f) Допустимо в традиционном и Standard С. 3. (a) unsigned (b) В традиционном С — unsigned long, в Standard С — long или unsigned long.
520 Приложение С (с) double. (d) В Standard С — long double. (е) int * (к int [] применяются обычные унарные преобразования). (f) short (*) (), поскольку сначала применяются обычные унарные преобразования. Ниже приведен пример ситуации, в которой это может произойти: extern short fl(), f2(), (*pf) () ; extern int i ; pf = (i>0 ? fl f2); /* бинарное преобразование fl и f2 */ 4. Допускается (расточительное) 32-битовое представление типа char. При любом представлении, функция sizeof (char) возвращает 1. Ин- тервал значений типа int не может быть меньше интервала значений типа char — только равным ему или больше. 5. Никаких обязательных соотношений. Значения могут быть равны либо не равны. 6. Значение 128 можно представить как 32-битовое шестнадцатеричное число 0000008016. Поскольку компьютер А относится к «тупоконечно- му» типу, байты этого числа записываются в следующем порядке: 00i6, 0016, 0016, 8016- В «остроконечном» компьютере порядок следования байт обратный, поэтому получаем 8000000016 или -2,147,483,648. Этот же результат был бы в случае «остроконечного» А и «тупоконечного» В. Ответы к главе 7 1. (a) char * (b) float (в традиционном С — double) (с) float (d) int (е) float (в традиционном С — double) (f) int (g) int (h) int (i) неправильно (j) float 2. (a) pl+=l; p2+=l; *pl=*p2; (b) *pl=*p2; pl-=l; p2-=l; 3. (a) #define low_zeroes (n) (-l«n) (если n не превышает размера типа int) (b) #define low_ones(n) (~low_zeroes(n)) (c) #define mid_zeroes(width,offset) \ (low_zeroes(width+offset) | low_ones(offset)) (| можно заменить знаком +). (c) #define mid_ones(width,offset (~mid_zeroes(width,offset))
Ответы к упражнениям 521 4. Выражение j++==++j допустимо, но его результат в Standard С оказы- вается неопределенным, поскольку значение j меняется дважды в од- ном выражении. В зависимости от того, какой из операндов операции == изменяется первым, результат будет равен 0 или 1, конечное же зна- чение j будет, по всей вероятности, равно 2. Что касается выражения j++&&++j, то оно допустимо и определенно, его результат равен 0, а ко- нечное значение j равно 1. 5. (а) Допустимо ввиду совместимости типов. (Ь) Недопустимо (типу, на который ссылается указатель в левой части оператора присваивания, необходимы дополнительные квалификаторы), (с) Допустимо, поскольку размер определяется только одним из типов, (d) Допустимо, поскольку уточнение типа несущественно, если выра- жение в правой части не является 1-значением. (е) Недопустимо исключительно ввиду несовместимости типа float с преобразуемым типом (double). (f) Допустимо ввиду совместимости типов, на которые ссылаются ука- затели в обеих частях оператора присваивания. 6. Нет. Операция присваивания недопустима, поскольку определение структуры создает новый тип. Если определения располагаются в от- дельных исходных файлах, типы оказываются совместимыми. Распре- деление программы по отдельным файлам позволяет добиваться нуж- ного поведения. Ответы к главе 8 1. (а) п = А; L1: if (n>=B) goto L2; sum+=n; n++; goto Ll; L2 : ; (b) L: if (a<b) { a++; goto L; } (C) L: sum += *p; if (++p < q) goto L; 2. Значение j равно 3. Утверждение «j неопределенно» — неверно. Не- смотря на переход внутрь блока, класс памяти static переменной i обеспечивает правильную ее инициализацию при запуске программы. 3. Значение sum равно 3. Переменная i в каждом цикле принимает значе- ния 0, 1, ... При значениях i, равных 0, 1 и 3, происходит приращение sum, а оператор continue инициирует новый цикл. Если i равно 2, из- менения sum не происходит, но оператор continue срабатывает. Если
522 Приложение С же i равно 4, срабатывает оператор break, в результате чего происхо- дит выход из цикла. Таким образом, оператор, помеченный меткой case 5: никогда не будет выполнен. Ответы к главе 9 1. (а) Правильный прототип. (Ь) Допускается как объявление, но не прототип — недостает списка типов параметров в скобках. (с) Неправильное объявление. Троеточию должно предшествовать объ- явление минимум одного параметра. (d) Неправильное объявление — каждому параметру должен предше- ствовать минимум один спецификатор типа или класса памяти либо квалификатор типа. (е) Правильный прототип. Имя параметра можно не указывать. (f) Допускается как объявление, но не прототип — недостает списка типов параметров в скобках. 2. (а) Не совместимы — тип параметра в прототипе несовместим с обыч- ным преобразованием аргументов, обязательным в случае определения не в форме прототипа. (Ь) Не совместимы, невзирая на наличие прототипа в определении. (с) Совместимы — имена параметров не обязаны совпадать. (d) Не совместимы — несовпадение многоточий в прототипах. (е) Совместимы. Не прототип, поэтому функция принимает преобразо- ванные типы параметров. (f) Совместимы. 3. (а) Недопустим — аргумент short * нельзя преобразовать в int *. (b) Допустим, s преобразуется в тип int, Id остается в прежнем виде. (с) Допустим. Id преобразуется в тип short. (d) Допустим. Первый параметр остается неизменным, второй преоб- разуется в тип int, третий также остается неизменным. (е) Допустим. Параметр преобразуется в тип int перед вызовом, затем (в начале выполнения функции) — обратно в тип short. (f) Допустим, но применять его не следует. Параметр не преобразуется, но интерпретируется вызывающей функцией как имеющий тип int. 4. Способ вызова определяется прототипом, указанным в первой строке. Следующее объявление не отменяет предыдущее, поскольку Р помечена как внешняя функция. 5. (а) Допустим. Значение будет преобразовано в тип short перед возвра- том. (Ь) Допустим. Значение будет преобразовано в тип short перед возвратом. (с) Недопустим — выражение не может быть преобразовано в тип воз- вращаемого значения. (d) Недопустим — выражение не может быть преобразовано по прави- лам преобразования в операции присваивания.