Главная страница
Навигация по странице:

  • А 10.2. Внешние объявления

  • А 11. Область видимости и связи

  • А 11.1. Лексическая область видимости

  • А 11.2. Связи

  • А 12. Препроцессирование

  • А 12.1. Трехзнаковые последовательности

  • А 12.2. Склеивание строк

  • А 12.3. Макроопределение и макрорасширение

  • Язык программирования Си Брайан Керниган, Деннис Ритчи 3е издание Версия 1 Table of Contents


    Скачать 2.33 Mb.
    НазваниеЯзык программирования Си Брайан Керниган, Деннис Ритчи 3е издание Версия 1 Table of Contents
    Дата18.09.2022
    Размер2.33 Mb.
    Формат файлаpdf
    Имя файлаBrian_Kernighan_Dennis_Ritchie-The_C_Programming_Language-RU.pdf
    ТипДокументы
    #683263
    страница27 из 31
    1   ...   23   24   25   26   27   28   29   30   31

    А 10.1. Определение функции
    Определение функции имеет следующий вид:
    определение-функции:
    спецификаторы-объявления
    необ
    объявитель список-объявлений
    необ
    составная-инструкция
    Из спецификаторов класса памяти в спецификаторах-объявлениях возможны только extern и static; различия между последними рассматриваются в А11.2.
    Типом возвращаемого функцией значения может быть арифметический тип, структура, объединение, указатель и void
    , но не "функция" и не "массив". Объявитель в объявлении функции должен явно указывать на то, что описываемый им идентификатор имеет тип "функция", т. е. он должен иметь одну из следующих двух форм (А8.6.3):
    собственно-объявитель ( список-типов-параметров )
    собственно-объявитель ( список-идентификаторов
    необ
    ) где собственно-объявитель есть идентификатор или идентификатор, заключенный в скобки. Заметим, что тип "функция" посредством typedef получить нельзя.

    Первая форма соответствует определению функции новым способом, для которого характерно объявление параметров в списке-типов-параметров вместе с их типами; за объявителем не должно быть списка-
    объявлений. Если список-типов-параметров не состоит из одного-единственного слова void
    , показывающего, что параметров у функции нет, то в каждом объявителе в списке-типов-параметров обязан присутствовать идентификатор. Если список-типов-параметров заканчивается знаками"
    , ...
    ", то вызов функции может иметь аргументов больше, чем параметров; в таком случае, чтобы обращаться к дополнительным аргументам, следует пользоваться механизмом макроса va_arg из заголовочного файла

    , описанного в приложении В. Функции с переменным числом аргументов должны иметь по крайней мере один именованный параметр.
    Вторая форма — определение функции старым способом. Список-идентификаторов содержит имена параметров, а список-объявлений приписывает им типы. В списке-объявлений разрешено объявлять только именованные параметры, инициализация запрещается, и из спецификаторов класса памяти возможен только register
    И в том и другом способе определения функции мыслится, что все параметры как бы объявлены в самом начале составной инструкции, образующей тело функции, и совпадающие с ними имена здесь объявляться не должны (хотя, как и любые идентификаторы, их можно переобъявить в более внутренних блоках).
    Объявление параметра "
    массив из типа " можно трактовать как "
    указатель на тип "; аналогично объявлению параметра объявление "
    функция, возвращающая тип " можно трактовать как "
    указатель на функцию, возвращающую тип ". В момент вызова функции ее аргументы соответствующим образом преобразуются и присваиваются параметрам (см. А7.3.2).
    Новый способ определения функций введен ANSI-стандартом. Есть также небольшие изменения в операции повышения типа; в первой версии языка параметры типа float следовало читать как double
    . Различие между float и double становилось заметным, лишь когда внутри функции генерировался указатель на параметр.
    Ниже приведен пример определения функции новым способом: int max(int a, int b, int с)
    { int m; m = (a > b) ? a : b; return (m > с) ? m : с;
    }
    Здесь int
    спецификаторы-объявления; max(int a, int b, int с)
    объявитель функции, а , . . . -
    — блок, задающий ее код. Определение старым способом той же функции выглядит следующим образом: int max(a, b, с) int a, b, с;
    {
    /* ... */
    } где max(а, b, с)
    объявитель, a int a, b, с
    список-объявлений для параметров.
    А 10.2. Внешние объявления
    Внешние объявления специфицируют характеристики объектов, функций и других идентификаторов. Термин "внешний" здесь используется, чтобы подчеркнуть тот факт, что объявления расположены вне функций; впрямую с ключевым словом extern
    ("внешний") он не связан. Класс памяти для объекта с внешним объявлением либо вообще не указывается, либо специфицируется как extern или static

    В одной единице трансляции для одного идентификатора может содержаться несколько внешних объявлений, если они согласуются друг с другом по типу и способу связи и если для этого идентификатора существует не более одного определения.
    Два объявления объекта или функции считаются согласованными по типу в соответствии с правилами, рассмотренными в А8.10. Кроме того, если объявления отличаются лишь тем, что в одном из них тип структуры, объединения или перечисления незавершен (А8.3), а в другом соответствующий ему тип с тем же тегом завершен, то такие типы считаются согласованными. Если два типа массива (8.6.2) отличаются лишь тем, что один завершенный, а другой незавершенный, то такие типы также считаются согласованными.
    Наконец, если один тип специфицирует функцию старым способом, а другой — ту же функцию новым способом (с объявлениями параметров), то такие типы также считаются согласованными.
    Если первое внешнее объявление функции или объекта помечено спецификатором static
    , то объявленный идентификатор имеет внутреннюю связь; в противном случае — внешнюю связь. Способы связей обсуждаются в А11.2.
    Внешнее объявление объекта считается определением, если оно имеет инициализатор. Внешнее объявление, в котором нет инициализатора и нет спецификатора extern
    , считается пробным определением. Если в единице трансляции появится определение объекта, то все его пробные определения просто станут избыточными объявлениями. Если никакого определения для этого объекта в единице трансляции не обнаружится, то все его пробные определения будут трактоваться как одно определение с инициализатором
    0.
    Каждый объект должен иметь ровно одно определение. Для объекта с внутренней связью это правило относится к каждой отдельной единице трансляции, поскольку объекты с внутренними связями в каждой единице уникальны. В случае объектов с внешними связями указанное правило действует в отношении всей программы в целом.
    Хотя правило одного определения формулируется несколько иначе, чем в первой версии языка, по существу оно совпадает с прежним. Некоторые реализации его ослабляют, более широко трактуя понятие пробного определения. В другом варианте указанного правила, который распространен в системах UNIX и признан как общепринятое расширение стандарта, все пробные определения объектов с внешними связями из всех транслируемых единиц программы рассматриваются вместе, а не отдельно в каждой единице. Если где-то в программе обнаруживается определение, то пробные определения становятся просто объявлениями, но, если никакого определения не встретилось, то все пробные определения становятся однимединственным определением с инициализатором 0.
    А 11. Область видимости и связи
    Каждый раз компилировать всю программу целиком нет необходимости. Исходный текст можно хранить в нескольких файлах, представляющих собой единицы трансляции. Ранее скомпилированные программы могут загружаться из библиотек. Связи между функциями программы могут осуществляться через вызовы и внешние данные.
    Следовательно, существуют два вида областей видимости: первая — это лексическая область идентификатора: т. е. область в тексте программы, где имеют смысл все его характеристики; вторая область — это область, ассоциируемая с объектами и функциями, имеющими внешние связи, устанавливаемые между идентификаторами из раздельно компилируемых единиц трансляции.
    А 11.1. Лексическая область видимости
    Каждый идентификатор попадает в одно из нескольких пространств имен. Эти пространства никак не связаны друг с другом. Один и тот же идентификатор может использоваться в разных смыслах даже в одной области видимости, если он принадлежит разным пространствам имен. Ниже через точку с запятой перечислены
    классы объектов, имена которых представляют собой отдельные независимые пространства: объекты, функции, typedef
    -имена и enum
    -константы; метки инструкций; теги структур, объединений и перечислений; элементы каждой отдельной структуры или объединения.
    Сформулированные правила несколько отличаются от прежних, описанных в первом издании. Метки инструкций не имели раньше собственного пространства; теги структур и теги объединений (а в некоторых реализациях и теги перечислений) имели отдельные пространства. Размещение тегов структур, объединений и перечислений в одном общем пространстве — это дополнительное ограничение, которого раньше не было. Наиболее существенное отклонение от первой редакции в том, что каждая отдельная структура (или объединение) создает свое собственное пространство имен для своих элементов. Таким образом, одно и то же имя может использоваться в нескольких различных структурах. Это правило широко применяется уже несколько лет.
    Лексическая область видимости идентификатора объекта (или функции), объявленного во внешнем объявлении, начинается с места, где заканчивается его объявитель, и простирается до конца единицы трансляции, в которой он объявлен. Область видимости параметра в определении функции начинается с начала блока, представляющего собой тело функции, и распространяется на всю функцию; область видимости параметра в описании функции заканчивается в конце этого описания. Область видимости идентификатора, объявленного в начале блока, начинается от места, где заканчивается его объявитель, и продолжается до конца этого блока. Областью видимости метки является вся функция, где эта метка встречается. Область видимости тега структуры, объединения или перечисления начинается от его появления в спецификаторе типа и продолжается до конца единицы трансляции для объявления внешнего уровня и до конца блока для объявления внутри функции.
    Если идентификатор явно объявлен в начале блока (в том числе тела функции), то любое объявление того же идентификатора, находящееся снаружи этого блока, временно перестает действовать вплоть до конца блока.
    А 11.2. Связи
    Если встречается несколько объявлений, имеющих одинаковый идентификатор и описывающих объект (или функцию), то все эти объявления в случае внешней связи относятся к одному объекту (функции) — уникальному для всей программы; если же связь внутренняя, то свойство уникальности распространяется только на единицу трансляции.
    Как говорилось в А10.2, если первое внешнее объявление имеет спецификатор static
    , то оно описывает идентификатор с внутренней связью, если такого спецификатора нет, то — с внешней связью. Если объявление находится внутри блока и не содержит extern
    , то соответствующий идентификатор ни с чем не связан и уникален для данной функции. Если объявление содержит extern и блок находится в области видимости внешнего объявления этого идентификатора, то последний имеет ту же связь и относится к тому же объекту (функции). Однако если ни одного внешнего объявления для этого идентификатора нет, то он имеет внешнюю связь.
    А 12. Препроцессирование
    Препроцессор выполняет макроподстановку, условную компиляцию, включение именованных файлов.
    Строки, начинающиеся со знака
    #
    (перед которым возможны символы-разделители), устанавливают связь с препроцессором. Их синтаксис не зависит от остальной части языка; они могут появляться где угодно и оказывать влияние (независимо от области видимости) вплоть до конца транслируемой единицы. Границы строкпринимаются во внимание; каждая строка анализируется отдельно (однако есть возможность "склеивать" строки, см. А12.2). Лексемами для препроцессора являются все лексемы языка и последовательности символов, задающие имена файлов, как, например, в директиве
    #include
    (А12.4).
    Кроме того, любой символ, не определенный каким-либо другим способом, воспринимается как лексема.

    Влияние символов-разделителей, отличающихся от пробелов и горизонтальных табуляций, внутри строк препроцессора не определено.
    Само препроцессирование проистекает в нескольких логически последовательных фазах. В отдельных реализациях некоторые фазы объединены.
    1. Трехзнаковые последовательности, описанные в А12.1, заменяются их эквивалентами. Между строками вставляются символы новой строки, если того требует операционная система.
    2. Выбрасываются пары символов, состоящие из обратной наклонной черты с последующим символом новой строки; тем самым осуществляется "склеивание" строк (А12.2).
    3. Программа разбивается на лексемы, разделенные символами-разделителями. Комментарии заменяются единичными пробелами.
    Затем выполняются директивы препроцессора и макроподстановки (А12.3-А12.10).
    4. Эскейп-последовательности в символьных константах и строковых литералах (А2.5.2, А2.6) заменяются на символы, которые они обозначают. Соседние строковые литералы конкатенируются.
    5. Результат транслируется. Затем устанавливаются связи с другими программами и библиотеками посредством сбора необходимых программ и данных и соединения ссылок на внешние функции и объекты с их определениями.
    А 12.1. Трехзнаковые последовательности
    Множество символов, из которых набираются исходные Си-программы, основано на семибитовом ASCII-коде.
    Однако он шире, чем инвариантный код символов ISO 646-1983 (ISO 646-1983 Invariant Code Set). Чтобы дать возможность пользоваться сокращенным набором символов, все указанные ниже трехзнаковые последовательности заменяются на соответствующие им единичные символы. Замена осуществляется до любой иной обработки.
    ??= #
    ??( [
    ??< {
    ??/ \
    ??) ]
    ??> }
    ??' ^
    ??! ;
    ??-


    Никакие другие замены, кроме указанных, не делаются.
    Трехзнаковые последовательности введены ANSI-стандартом.
    А 12.2. Склеивание строк
    Строка, заканчивающаяся обратной наклонной чертой, соединяется со следующей, поскольку символ
    \
    и следующий за ним символ новой строки выбрасываются. Это делается перед "разбиением" текста на лексемы.
    А 12.3. Макроопределение и макрорасширение
    Управляющая строка вида
    # define идентификатор последовательность-лексем заставляет препроцессор заменять идентификатор на последовательность-лексем; символы-разделители в начале и в конце последовательности-лексем выбрасываются. Повторная строка
    #define с тем же идентификатором считается ошибкой, если последовательности лексем неидентичны (несовпадения в символах-разделителях при сравнении во внимание не принимаются). Строка вида
    # define идентификатор( список-идентификаторов ) последовательность-лексем где между первым идентификатором и знаком
    (
    не должно быть ни одного символа-разделителя, представляет собой макроопределение с параметрами, задаваемыми списком идентификаторов. Как и в первом варианте, символы-разделители в начале и в конце последовательности лексем выбрасываются, и
    макрос может быть повторно определен только с тем же списком параметров и с той же последовательностью лексем. Управляющая строка вида
    # undef идентификатор предписывает препроцессору "забыть" определение, данное идентификатору. Применение
    #undef к неизвестному идентификатору ошибкой не считается.
    Если макроопределение было задано вторым способом, то текстовая последовательность, состоящая из его идентификатора, возможно, со следующими за ним символами-разделителями, знака
    (
    , списка лексем, разделенных запятыми, и знака
    )
    , представляет собой вызов макроса. Аргументами вызова макроса являются лексемы, разделенные запятыми (запятые, "закрытые" кавычками или вложенными скобками, в разделении аргументов не участвуют). Аргументы при их выделении макрорасширениям не подвергаются. Количество аргументов в вызове макроса должно соответствовать количеству параметров макроопределения. После выделения аргументов окружающие их символы-разделители выбрасываются. Затем в замещающей последовательности лексем макроса идентификаторы-параметры (если они не закавычены) заменяются на соответствующие им аргументы. Если в замещающей последовательности перед параметром не стоит знак
    #
    и ни перед ним, ни после него нет знака
    ##
    , то лексемы аргумента проверяются: не содержат ли они в себе макровызова, и если содержат, то прежде чем аргумент будет подставлен, производится соответствующее ему макрорасширение.
    На процесс подстановки влияют два специальных оператора. Первый — это оператор
    #
    , который ставится перед параметром. Он требует, чтобы подставляемый вместо параметра и знака
    #
    (перед ним) текст был заключен в двойные кавычки. При этом в строковых литералах и символьных константах аргумента перед каждой двойной кавычкой "
    (включая и обрамляющие строки), а также перед каждой обратной наклонной чертой
    \
    вставляется
    \
    Второй оператор записывается как
    ##
    . Если последовательность лексем в любого вида макроопределении содержит оператор
    ##
    , то сразу после подстановки параметров он вместе с окружающими его символами- разделителями выбрасывается, благодаря чему "склеиваются" соседние лексемы, образуя тем самым новую лексему. Результат не определен при получении неправильных лексем или когда генерируемый текст зависит от порядка применения операторов
    ##
    . Кроме того,
    ##
    не может стоять ни в начале, ни в конце замещающей последовательности лексем.
    В макросах обоих видов замещающая последовательность лексем повторно просматривается на предмет обнаружения там новых define
    -имен. Однако, если некоторый идентификатор уже был заменен в данном расширении, повторное появление такого идентификатора не вызовет его замены.
    Если полученное расширение начинается со знака
    #
    , оно не будет воспринято как директива препроцессора.
    В ANSI-стандарте процесс макрорасширения описан более точно, чем в первом издании книги.
    Наиболее важные изменения касаются введения операторов
    #
    и
    ##
    , которые предоставляют возможность осуществлять расширения внутри строк и конкатенацию лексем. Некоторые из новых правил, особенно касающиеся конкатенации, могут показаться несколько странными. (См. приведенные ниже примеры.)
    Описанные возможности можно использовать для показа смысловой сущности констант, как, например, в
    #define TABSIZE 100 int table[TABSIZE];
    Определение
    #define ABSDIFF(a, b) ((a)>(b) ? (a)-(b) : (b)-(a))
    задает макрос, возвращающий абсолютное значение разности его аргументов. В отличие от функции, делающей то же самое, аргументы и возвращаемое значение здесь могут иметь любой арифметический тип и даже быть указателями. Кроме того, аргументы, каждый из которых может иметь побочный эффект, вычисляются дважды: один раз — при проверке, другой раз — при вычислении результата.
    Если имеется определение
    #define tempfile(dir) #dir "/%s" то макровызов tempfile(/usr/tmp)
    даст в результате "/usr/tmp" "/%s"
    Далее эти две строки превратятся в одну строку. По макросу
    #define cat(x, y) x ## y вызов cat(var, 123)
    сгенерирует var123
    . Однако cat (cat (1, 2), 3)
    не даст желаемого, так как оператор
    ##
    воспрепятствует получению правильных аргументов для внешнего вызова cat
    . В результате будет выдана следующая цепочка лексем: cat ( 1 , 2 )3 где
    )3
    (результат "склеивания" последней лексемы первого аргумента с первой лексемой второго аргумента) не является правильной лексемой.
    Если второй уровень макроопределения задан в виде
    #define xcat(x.y) cat(x.y) то никаких коллизий здесь не возникает; xcat(xcat(1, 2), 3)
    выдаст
    123
    , поскольку сам xcat не использует оператора
    #
    Аналогично сработает и
    ABSDIFF(ABSDIFF(a, b), с)
    , и мы получим правильный результат.
    1   ...   23   24   25   26   27   28   29   30   31


    написать администратору сайта