Языка программирования набор символов разрешенный к использованию

В этой главе описываются основные характеристики и элементы языка программирования Си.

C-это универсальный процедурный язык программирования. Деннис Ричи впервые разработал C в 1970-х годах в AT&T Bell Laboratories в Мюррей-Хилл, Нью-Джерси. С целью внедрения операционной системы Unix и утилит с максимально возможной степенью независимости от конкретных аппаратных платформ. Ключевыми характеристиками языка Си являются качества. Которые сделали его пригодным для этой цели:

  • Переносимость исходного кода

  • Возможность работы “рядом с машиной”

  • Эффективность

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

Предками C являются безтипные языки программирования BCPL (базовый комбинированный язык программирования). Разработанный Мартином Ричардсом; и B. Потомок BCPL. Разработанный Кеном Томпсоном. Новой особенностью языка Си стало его разнообразие типов данных : символов. Числовых типов, массивов. Структур и так далее. Брайан Керниган и Деннис Ричи опубликовали официальное описание языка программирования C в 1978 году. В качестве первого стандарта де-факто их описание обычно называют просто “K&R.”

[] Своей высокой степенью переносимости Си обязан компактному базовому языку. Который содержит мало аппаратно-зависимых элементов. Например, собственно язык Си не имеет операторов доступа к файлам или динамического управления памятью . На самом деле. Нет даже никаких инструкций для консольного ввода и вывода. Вместо этого обширная стандартная библиотека языка Си предоставляет функции для всех этих целей.

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

В результате компиляторы языка Си доступны практически для каждой компьютерной системы.

Поскольку язык Си был специально разработан для системного программирования. Неудивительно. Что одно из его основных применений сегодня-программирование встроенных систем. В то же время многие разработчики используют Си как портативный. Структурированный язык высокого уровня для написания таких программ. Как мощный текстовый процессор. База данных и графические приложения.

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

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

Каждая программа на языке Си должна определить хотя бы одну собственную функцию со специальным именем main(): это первая функция. Вызываемая при запуске программы. main()Функция является высшим уровнем управления программы и может вызывать другие функции в качестве подпрограмм.

Пример 1-1 показывает структуру простой. Полной программы на языке Си.

Мы обсудим детали объявлений. Вызовов функций. Выходных потоков и многое другое в других разделах этой книги. На данный момент нас интересует только общая структура исходного кода языка Си. Программа в примере 1-1 определяет две функции, main()и circularArea(). main()Функция вызывает circularArea()для получения площади окружности с заданным радиусом. А затем вызывает стандартную библиотечную функцию printf()для вывода результатов в форматированных строках на консоль.

Пример 1-1. Простая программа на языке Си

// circle.c: Вычисление и печать областей окружностей double circularArea( двойной r ); // Объявление функции (форма прототипа) int main() // Определение main() начинается { двойной радиус = 1,0, площадь = 0,0; printf( area = circularArea( radius ); printf( радиус = 5,0; area = circularArea( radius ); printf( // Функция circularArea() вычисляет площадь окружности // Параметр: Радиус окружности // Возвращаемое значение: Площадь окружности double circularArea( double r ) // Начинается определение circularArea() { 

Выход:

 Области окружностей Площадь Радиуса ------------------------- 1.0 3.14 5.0 78.54

Обратите внимание. Что компилятор требует предварительного объявление каждой вызываемой функции. Прототип circularArea()в третьей строке примера 1-1 предоставляет информацию. Необходимую для компиляции оператора. Вызывающего эту функцию. Прототипы стандартных библиотечных функций находятся в стандартных заголовочных файлах. Поскольку файл заголовка stdio.h содержит прототип printf()функции. Директива препроцессора #include объявляет функцию косвенно. Направляя препроцессор компилятора для вставки содержимого этого файла. (См. также раздел Как работает компилятор Си” в конце этой главы.)

Вы можете расположить функции. Определенные в программе. В любом порядке. В примере 1-1мы могли бы с таким же успехом поместить функцию circularArea()перед функцией main(). Если бы мы это сделали. То объявление прототипа circularArea()было бы излишним. Потому что определение функции также является объявлением.

Определения функций не могут быть вложены друг в друга: вы можете определить локальную переменную внутри функционального блока. Но не локальную функцию.

Определения функций. Глобальные объявления и директивы предварительной обработки составляют исходный код программы на языке Си. Для небольших программ исходный код записывается в один исходный файл. Более крупные программы на языке Си состоят из нескольких исходных файлов . Поскольку определения функций обычно зависят от директив препроцессора и глобальных деклараций. Исходные файлы обычно имеют следующую внутреннюю структуру:

  1. Директивы препроцессора

  2. Глобальные декларации

  3. Определения функций

C поддерживает модульное программирование. Позволяя организовать программу в любом количестве исходных и заголовочных файлов. А также редактировать и компилировать их отдельно. Каждый исходный файл обычно содержит логически связанные функции. Такие как функции пользовательского интерфейса программы. Обычно исходные файлы C помечаются суффиксом имени файла .c .

Примеры 1-2 и 1-3 показывают ту же программу , что и пример 1-1, но разделенную на два исходных файла.

Пример 1-2. Первый исходный файл. Содержащий функцию main()

// circle.c: Печатает области окружностей. // Использует circulararea.c для математики int main() { /* ... Как впримере 1-1

Пример 1-3. Второй исходный файл. Содержащий функцию circularArea()

// circulararea.c: Вычисляет площади окружностей. // Вызывается main() в circle.c двойная циркулярная зона( double r ) { /* ... Как впримере 1-1

Когда программа состоит из нескольких исходных файлов. Во многих файлах необходимо объявить одни и те же функции и глобальные переменные. А также определить одни и те же макросы и константы. Эти объявления и определения. Таким образом. Образуют своего рода заголовок файла. Который более или менее постоянен во всей программе. Для простоты и согласованности вы можете записать эту информацию только один раз в отдельный заголовочный файл, а затем ссылаться на него с помощью #include директива в каждом файле исходного кода. Заголовочные файлы обычно идентифицируются суффиксом имени файла .h . Заголовочный файл. Явно включенный в исходный файл языка Си, может. В свою очередь. Включать в себя и другие файлы.

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

Любое количество пробелов может возникать между двумя последовательными токенами. Что дает вам большую свободу в форматировании исходного кода. Нет никаких правил для разрывов строк или отступов. И вы можете свободно использовать пробелы. Вкладки и пустые строки для форматирования “читаемого человеком” исходного кода. Директивы препроцессора немного менее гибки: директива препроцессора всегда должна появляться в строке сама по себе. И никакие символы. Кроме пробелов или вкладок. Не могут предшествовать хэш-метке (#), которая начинает строку.

Существует множество различных соглашений и “стилей дома” для форматирования исходного кода. Большинство из них включают в себя следующие общие правила:

  • Начните новую строку для каждого нового объявления и утверждения.

  • Используйте отступ. Чтобы отразить вложенную структуру блочных операторов.

Вы должны щедро использовать комментарии в исходном коде для документирования ваших программ на языке Си. Есть два способа вставить комментарий в C: блочные комментарии начинаются /*и заканчиваются */, а строчные комментарии начинаются //и заканчиваются следующим новым символом строки.

/*Разделители и можно использовать */для начала и окончания комментариев внутри строки. А также для включения комментариев из нескольких строк. Например. В следующем прототипе функции многоточие (...) означает. Что open()функция имеет третий необязательный параметр. В комментарии объясняется использование необязательного параметра:

 int open( const char *name, int mode. ... /* int разрешения */ );

Вы можете использовать //его для вставки комментариев. Заполняющих всю строку. Или для написания исходного кода в формате двух столбцов. С программным кодом слева и комментариями справа :

 const double pi = 3.1415926536; // Pi-постоянная величина

Эти комментарии строк были официально добавлены в язык C стандартом C99, но большинство компиляторов уже поддерживали их еще до C99. Их иногда называют комментариями в стиле C++. Хотя они возникли в предшественнике C, BCPL.

Внутри кавычек. Разделяющих символьную константу или строковый литерал. Символы /*так и //не начинаются с комментария. Например, следующее утверждение не содержит комментариев:

 printf( 

Единственное. Что ищет препроцессор при изучении символов в комментарии. — это конец комментария; таким образом. Вложить блочные комментарии невозможно. Однако вы можете вставить /*и */закомментировать часть программы. Содержащую комментарии строк:

 /* Временное удаление двух строк: const double pi = 3.1415926536; // Pi - постоянная площадь = pi * r * r // Вычислить площадь Временно удален до сих пор */

Если вы хотите закомментировать часть программы. Содержащую комментарии блоков. Вы можете использовать условную директиву препроцессора (описанную в главе 14):

 #если 0 const double pi = 3.1415926536; /* Pi является постоянным */ площадь = pi * r * r /* Вычислить площадь */ #эндиф

Препроцессор заменяет каждый комментарий пробелом. Последовательность символовmin/*max*/Value таким образом. Становятся два знака min Value.

Язык Си проводит различие между средой. В которой компилятор переводит исходные файлы программы,—средой перевода, —и средой. В которой выполняется скомпилированная программа, — средой выполнения. Соответственно. C определяет два набора символов : исходный набор символов-это набор символов. Которые могут быть использованы в исходном коде C, и набор символов выполнения это набор символов. Которые могут быть интерпретированы запущенной программой. Во многих реализациях C эти два набора символов идентичны. Если это не так. То компилятор преобразует символы в символьных константах и строковых литералах в исходном коде в соответствующие элементы набора символов выполнения.

Каждый из двух наборов символов включает в себя как базовый набор символов, так и расширенные символы . Язык C не определяет расширенные символы. Которые обычно зависят от местного языка. Расширенные символы вместе с базовым набором символов составляют расширенный набор символов .

Базовые исходные и исполнительные наборы символов содержат следующие типы символов:

Буквы латинского алфавита

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

a b c d e f g h i j k l m n o p q r s t u v w x y z

Десятичные цифры

0 1 2 3 4 5 6 7 8 9

Следующие 29 знаков препинания

! " # % & ' () * + . − . / : ; ? [ \ ] ^ _ { | } ~

Пять пробелов

Пробел, горизонтальная вкладка. Вертикальная вкладка. Новая строка и подача формы

Базовый набор символов выполнения также включает четыре непечатаемых символа : нулевой символ. Который действует как знак завершения в символьной строке; предупреждение; backspace; и возврат каретки. Чтобы представить эти символы в символьных и строковых литералах. Введите соответствующие escape-последовательности, начинающиеся с обратной косой черты: \0для нулевого символа, \aдля предупреждения, \bдля backspace и \rдля возврата каретки. Более подробную информацию см. в главе 3.

Фактические числовые значения символов—коды символов —могут варьироваться от одной реализации языка Си к другой. Сам язык налагает только следующие условия:

  • Каждый символ в базовом наборе символов должен быть представлен одним байтом.

  • Нулевой символ-это байт. В котором все биты равны 0.

  • Значение каждой десятичной цифры после 0 больше на единицу. Чем значение предыдущей цифры.

Широкие символы и Многобайтовые символы

C изначально был разработан в англоязычной среде. Где доминирующим набором символов был 7-битный код ASCII. С тех пор 8-битный байт стал самой распространенной единицей кодирования символов. Но программное обеспечение для международного использования. Как правило. Должно быть способно представлять больше различных символов. Чем может быть закодировано в одном байте. И на международном уровне в течение десятилетий использовались различные многобайтовые схемы кодирования символов для представления нелатинских алфавитов и неалфавитных китайских. Японских и корейских алфавитов. системы письма. В 1994 году. С принятием “нормативные добавлении 1,” стандартизированный ISO для C двух способов представления больших наборов символов: широкий символов , в которой один и тот же разрядность используется для каждого символа в наборе символов, и многобайтовых символов , в которой данный символ может быть представлен один или несколько байт. И символьное значение данного байта последовательности может зависеть от ее контекста в строку или поток.

Совет

Хотя C теперь предоставляет абстрактные механизмы для манипулирования и преобразования различных типов схем кодирования. Сам язык не определяет и не определяет ни одну схему кодирования или любой набор символов. Кроме базовых исходных и исполнительных наборов символов. Описанных в предыдущем разделе. Другими словами. Это зависит от отдельных реализаций. Чтобы определить. Как кодировать широкие символы и какие многобайтовые схемы кодирования поддерживать.

Начиная с добавления 1994 года. C предоставил не только тип char, но и wchar_tширокий тип символов. Этот тип, определенный в заголовочном файле stddef.h, достаточно велик. Чтобы представлять любой элемент расширенных наборов символов данной реализации.

Хотя стандарт C не требует поддержки наборов символов Unicode. Многие реализации используют форматы преобразования Unicode UTF-16 и UTF-32 (см. http://www.unicode.org) для широких символов. Стандарт Unicode в значительной степени идентичен стандарту ISO/IEC 10646 и представляет собой надмножество многих широко существующих наборов символов . Включая 7-битный код ASCII. Когда стандарт Unicode реализован. Тип wchar_tимеет ширину не менее 16 или 32 бит и значение типа wchar_t представляет один символ Юникода. Например, следующее определение инициализирует переменную wcгреческой буквой α.

 wchar_t wc = '\x3b1';

Escape — последовательность. Начинающаяся с\x, указывает код символа в шестнадцатеричной системе счисления. Который должен храниться в переменной—в данном случае код для строчной буквы альфа.

В многобайтовых наборах символов каждый символ кодируется как последовательность из одного или нескольких байтов. Исходные и исполняемые наборы символов могут содержать многобайтовые символы Если это так. То каждый символ в базовом наборе символов занимает только один байт. И ни один многобайтовый символ. Кроме нулевого. Не может содержать байт. В котором все биты равны 0 . Многобайтовые символы могут использоваться в символьных константах. Строковых литералах. Идентификаторах. Комментариях и именах заголовочных файлов. Многие многобайтовые наборы символов предназначены для поддержки определенного языка. Такого как японский промышленный стандартный набор символов (JIS) . Многобайтовый набор символов UTF-8 . Определенный Консорциумом Unicode. Способен представлять все символы Unicode. UTF-8 использует от одного до четырех байтов для представления символа.

Ключевое различие между многобайтовыми символами и широкими символами (то есть символами типа wchar_t) заключается в том. Что все широкие символы имеют одинаковый размер. А многобайтовые символы представлены различным числом байтов. Это представление делает многобайтовые строки более сложными для обработки. Чем строки с широкими символами. Например, даже если персонаж 'A' может быть представлен в одном байте. Поиск его в многобайтовой строке требует большего. Чем простое сравнение байт за байтом. Потому что одно и то же значение байта в определенных местах может быть частью другого символа. Однако многобайтовые символы хорошо подходят для сохранения текста в файлах (см. главу 13).

C предоставляет стандартные функции для получения wchar_tзначения любого многобайтового символа и преобразования любого широкого символа в его многобайтовое представление. Например, если компилятор C использует стандарты Unicode UTF-16 и UTF-8, то следующий вызов функции wctomb()(читай: “wide character to multibyte”) получает многобайтовое представление символа α:

 wchar_t wc = L'\x3B1'; // Греческая строчная альфа,α char mbStr[10] = nBytes = wctomb( mbStr, wc );

После вызова функции массив mbStrсодержит многобайтовый символ. Который в данном примере является последовательностью "\xCE\xB1". wctomb()Возвращаемое значение функции. Присвоенное здесь переменнойnBytes, — это количество байтов. Необходимых для представления многобайтового символа. А именно 2.

Универсальные Имена символов

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

 \uXXXX

или:

 \UXXXXXXXX

где XXXXили XXXXXXXX-кодовая точка Юникода в шестнадцатеричной нотации. Используйте префикс нижнего регистраu, за которым следуют четыре шестнадцатеричные цифры. Или верхний регистрU далее следуют ровно восемь шестнадцатеричных цифр. Если первые четыре шестнадцатеричные цифры равны нулю. То одно и то же универсальное имя символа может быть записано либо как\uXXXX, либо как \U0000XXXX.

Универсальные имена символов допустимы в идентификаторах. Символьных константах и строковых литералах. Однако они не должны использоваться для представления символов в базовом наборе символов.

Когда вы указываете символ по его универсальному символьному имени. Компилятор сохраняет его в наборе символов. Используемом реализацией. Например, если набор символов выполнения в локализованной программе-ISO 8859-7 (8-битный греческий) . То следующее определение инициализирует переменную alphaкодом\xE1:

 char alpha = '\u03B1';

Однако если набор символов выполнения-UTF-16, то вам нужно определить переменную как широкий символ:

 wchar_t alpha = '\u03B1';

В этом случае значение кода символа, присвоенное alphaшестнадцатеричному 3B1, совпадает с универсальным именем символа.

Совет

Не все компиляторы поддерживают универсальные имена символов .

Орграфы и триграфы

Язык Си предоставляет альтернативные представления для ряда знаков препинания. Которые доступны не на всех клавиатурах . Шесть из них являются орграфами , или двухсимвольными знаками. Которые представляют символы. Показанные в таблице 1-1.

Таблица 1-1. Орграфы

Орграф

Эквивалент

<:>

[

:>

]

{

%>

}

%:

#

%:%:

##

Эти последовательности не интерпретируются как орграфы. Если они встречаются в символьных константах или строковых литералах. Во всех остальных позициях они ведут себя точно так же. Как односимвольные символы. Которые они представляют. Например, следующие фрагменты кода полностью эквивалентны и дают один и тот же результат. С орграфами:


Без орграфов:


Выход:


C также предоставляет триграфы , трехсимвольные представления. Все они начинаются с двух вопросительных знаков. Третий символ определяет. Какой знак препинания представляет собой триграф. Как показано в таблице 1-2.

Таблица 1-2. Триграфы

Триграф

Эквивалент

??(

[

??)

]

??

{

??>

}

??=

#

??/

\

??!

|

??’

^

??-

~

Триграфы позволяют писать любую программу на языке Си. Используя только символы. Определенные в стандарте ISO/IEC 646 1991 года. Соответствующем 7-битному ASCII Препроцессор компилятора заменяет триграфы их односимвольными эквивалентами на первом этапе компиляции. Это означает. Что триграфы. В отличие от орграфов. Переводятся в их односимвольные эквиваленты независимо от того. Где они встречаются. Даже в символьных константах. Строковых литералах. Комментариях и директивах предварительной обработки. Например, препроцессор интерпретирует второй и третий вопросительные знаки оператора ниже как начало триграфа:

 printf(

Таким образом. Строка выдает следующий вывод препроцессора:

 printf(

Если вам нужно использовать одну из этих трехсимвольных последовательностей и вы не хотите. Чтобы она интерпретировалась как триграф. Вы можете записать вопросительные знаки как escape-последовательности:

 printf(

Если символ , следующий за любыми двумя вопросительными знаками, не является одним из тех, которые показаны в таблице 1-2, то последовательность не является триграфом и остается неизменной.

Совет

В качестве еще одной замены знаков препинания в дополнение к орграфам и триграфам заголовочный файл iso646.h содержит макросы. Определяющие альтернативные представления логических операторов C и побитовых операторов. Таких как andfor &&и xorfor ^. Подробнее см. главу 15.

Термин идентификатор относится к именам переменных. Функций, макросов. Структур и других объектов. Определенных в программе на языке Си. Идентификаторы могут содержать следующие символы:

  • Буквы в базовом наборе символовazи AZ. Идентификаторы чувствительны к регистру.

  • Символ подчеркивания, _.

  • Десятичные цифры 09, хотя первый символ идентификатора не должен быть цифрой.

  • Универсальные имена символов. Представляющие буквы и цифры других языков.

Допустимые универсальные символы определены в Приложении D к стандарту C и соответствуют символам, определенным в стандарте ISO/IEC TR 10176, за вычетом базового набора символов.

Многобайтовые символы также могут быть допустимы в идентификаторах . Однако именно данная реализация C определяет. Какие многобайтовые символы разрешены и каким универсальным именам символов они соответствуют.

Следующие 37 ключевых слов зарезервированы в in C, каждое из которых имеет определенное значение для компилятора. И не должны использоваться в качестве идентификаторов:

auto

enum

restrict

unsigned

break

extern

return

void

case

float

short

volatile

char

for

signed

while

const

goto

sizeof

_Bool

continue

if

static

_Complex

default

inline

struct

_Imaginary

do

int

switch

double

long

typedef

else

register

union

Следующие примеры являются допустимыми идентификаторами:

x dollar Break error_handler scale64

Следующие идентификаторы не являются допустимыми:

1st_rank switch y/n x-ray

Если компилятор поддерживает универсальные имена символов, то α также является примером допустимого идентификатора. И вы можете определить переменную по этому имени:

 двойнойα = 0,5;

Ваш редактор исходного кода может сохранить символ α в исходном файле как универсальный символ \u03B1.

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

Компилятор C предоставляет предопределенный идентификатор _ _func_ _, который можно использовать в любой функции для доступа к строковой константе. Содержащей имя функции. Это полезно для ведения журнала или для отладки вывода; например:

{ if( s == NULL) { fprintf( stderr, 

В этом примере передача нулевого указателя функции test_func()приводит к появлению следующего сообщения об ошибке:

 test_func: полученный аргумент нулевого указателя

Нет никаких ограничений на длину идентификаторов. Однако большинство компиляторов считают значимым только ограниченное число символов в идентификаторах. Другими словами. Компилятор может не различать два идентификатора. Которые начинаются с длинной идентичной последовательности символов. Чтобы соответствовать стандарту C. Компилятор должен рассматривать по крайней мере первые 31 символ как значимые в именах функций и глобальных переменных (то есть идентификаторов с внешней связью) и по крайней мере первые 63 символа во всех остальных идентификаторы.

Пробелы имен идентификаторов

Все идентификаторы попадают точно в одну из следующих четырех категорий. Которые составляют отдельные пространства имен:

  • Названия этикеток.

  • Теги, которые идентифицируют типы структуры. Объединения и перечисления.

  • Имена членов структуры или профсоюза. Каждая структура или объединение представляет собой отдельное пространство имен для своих членов.

  • Все остальные идентификаторы. Которые называются обычными идентификаторами.

Идентификаторы. Принадлежащие разным пространствам имен. Могут быть одинаковыми. Не вызывая конфликтов. Другими словами. Вы можете использовать одно и то же имя для обозначения различных объектов. Если они разного рода. Например, компилятор способен различать переменную и метку с тем же именем. Аналогично, вы можете дать одно и то же имя типу структуры. Элементу в структуре и переменной. Как показано в следующем примере:

_Bool check_pin( struct pin *pin ) 

Первая строка примера определяет тип структуры . Идентифицируемый тегомpin, содержащий массив символовpin, названный одним из его членов. Во второй строке-параметр функцииpin это указатель на структуру только что определенного типа. Выражение pin->pin в четвертой строке указывается элемент структуры. На который указывает параметр функции . Контекст, в котором появляется идентификатор. Всегда определяет его пространство имен без какой-либо двусмысленности. Тем не менее. Как правило. Это хорошая идея. Чтобы сделать все идентификаторы в программе отчетливой. Чтобы избавить читателей-людей от ненужной путаницы.

Область действия идентификатора

Область действия идентификатора относится к той части единицы перевода. В которой идентификатор имеет смысл. Или, другими словами. Область действия идентификатора-это та часть программы. Которая может “видеть” этот идентификатор. Тип области всегда определяется местоположением. В котором вы объявляете идентификатор (за исключением меток. Которые всегда имеют область действия функции ). Возможны четыре вида области применения:

Область действия файла

Если вы объявляете идентификатор вне всех блоков и списков параметров. То он имеет область действия файла . Затем вы можете использовать идентификатор в любом месте после объявления и до конца единицы перевода.

Объем блока

За исключением меток. Идентификаторы. Объявленные внутри блока. Имеют область действия блока Вы можете использовать такой идентификатор только от его объявления до конца самого маленького блока. Содержащего это объявление . Наименьший содержащий блок часто. Но не обязательно. Является телом определения функции. В C99 объявления не должны помещаться перед всеми операторами в функциональном блоке. Имена параметров в заголовке определения функции также имеют область действия блока и допустимы в соответствующем функциональном блоке.

Область применения прототипа функции

Имена параметров в прототипе функции имеют область действия прототипа функции . Поскольку эти имена параметров не значимы вне самого прототипа. Они значимы только как комментарии и также могут быть опущены. Дополнительную информацию см. в главе 7.

Область применения функции

Область действия метки всегда является функциональным блоком. В котором она находится. Даже если она помещена во вложенные блоки. Другими словами. Вы можете использовать gotoоператор для перехода к метке из любой точки внутри той же функции. Которая содержит метку. (Однако прыгать во вложенные блоки не очень хорошая идея: подробности см. в главе 6.)

Область действия идентификатора обычно начинается после его объявления. Однако имена типов или теги типов структуры. Объединения и перечисления. А также имена констант перечисления являются исключением из этого правила: их область действия начинается сразу после их появления в объявлении. Так что на них можно снова ссылаться в самом объявлении. (Структуры и объединения подробно рассматриваются в главе 10; типы перечислений описаны в главе 2.) Например. В следующем объявлении типа структуры последний член структуры,next, является указателем на тот самый тип структуры . Который объявляется:

 struct Node { /* ... int printList( const struct Node *first ) // Начало определения функции { struct Node *ptr = first; while( ptr != NULL ) { PrintNode( ptr ); 

В этом фрагменте кода идентификаторыNode,next,printNode, и printListвсе они имеют область действия файла . Параметр ptrNodeимеет область прототипа функции . А переменныеfirstptrобласть блока .

Можно снова использовать идентификатор в новом объявлении. Вложенном в его существующую область видимости. Даже если новый идентификатор не имеет другого пространства имен. Если вы это сделаете. То новое объявление должно иметь область видимости прототипа блока или функции. А прототип блока или функции должен быть истинным подмножеством внешней области видимости. В таких случаях новое объявление того же идентификатора скрывает внешнее объявление. Так что переменная или функция. Объявленные во внешнем блоке, не видны во внутреннем объеме. Например, допустимы следующие заявления:

 double x; // Объявить переменную x с областью действия файла long calc( double x ); // Объявить новую переменную x с областью действия прототипа функции int main() { long x = calc( 2.5 ); // Объявите длинную переменную x с областью действия блока если( x { float x = 0.0 F; // Объявите новую переменную float x с областью действия блока 

В этом примереlong переменная xdelcared в main()функции скрывает глобальную переменную xс типом double. Таким образом. Нет прямого способа получить доступ к doubleпеременной x изнутри main(). Кроме того, в условном блоке. Который зависит от if оператора, xссылается на вновь объявленную floatпеременную, которая. В свою очередь. Скрывает longпеременную x.

После того как вы написали исходный файл с помощью текстового редактора. Вы можете вызвать компилятор C. Чтобы перевести его в машинный код. Компилятор работает с единицей перевода, состоящей из исходного файла и всех заголовочных файлов. На которые ссылаются #includeдирективы. Если компилятор не находит ошибок в блоке перевода. Он генерирует объектный файл, содержащий соответствующий машинный код. Объектные файлы обычно идентифицируются суффиксом имени файла .o или .obj . Кроме того, компилятор может также генерировать список ассемблеров (см. Часть III).

Объектные файлы также называются модулями. Библиотека, такая как стандартная библиотека C. Содержит скомпилированные. Быстро доступные модули стандартных функций.

Компилятор переводит каждую единицу перевода программы на языке Си—то есть каждый исходный файл с любыми включенными в него заголовочными файлами—в отдельный объектный файл. Затем компилятор вызывает компоновщик, который объединяет объектные файлы и любые используемые библиотечные функции в исполняемый файл. На рис. 1-1 показан процесс компиляции и связывания программы из нескольких исходных файлов и библиотек. Исполняемый файл также содержит любую информацию. Необходимую целевой операционной системе для загрузки и запуска.

Рис. 1-1. От исходного кода к исполняемому файлу

Этапы перевода компилятора C

Процесс компиляции состоит из восьми логических шагов. Данный компилятор может объединить несколько из этих шагов. Пока результаты не будут затронуты. Шаги таковы:

  1. Символы считываются из исходного файла и при необходимости преобразуются в символы исходного набора символов. Индикаторы конца строки в исходном файле. Если они отличаются от символа новой строки. Заменяются. Аналогично, любые последовательности триграфов заменяются одиночными символами. Которые они представляют. (Орграфы, однако. Оставляют в покое; они не преобразуются в их односимвольные эквиваленты.)

  2. Всякий раз, когда за обратной косой чертой сразу следует символ новой строки. Препроцессор удаляет оба. Поскольку символ конца строки завершает директиву препроцессора. Этот шаг обработки позволяет поместить обратную косую черту в конце строки. Чтобы продолжить директиву. Такую как определение макроса. На следующей строке.

    Совет

    Каждый исходный файл. Если он не полностью пуст. Должен заканчиваться символом новой строки.

  3. Исходный файл разбивается на токены препроцессора (см. следующий раздел Токены“) и последовательности пробельных символов. Каждый комментарий обрабатывается как один пробел.

  4. Выполняются директивы препроцессора и расширяются вызовы макросов.

    Совет

    Шаги с 1 по 4 также применяются ко всем файлам. Вставленным #includeдирективами. После того как компилятор выполнил директивы препроцессора. Он удаляет их из своей рабочей копии исходного кода.

  5. Символы и escape-последовательности в символьных константах и строковых литералах преобразуются в соответствующие символы в наборе символов выполнения.

  6. Соседние строковые литералы объединяются в одну строку.

  7. Происходит собственно компиляция: компилятор анализирует последовательность токенов и генерирует соответствующий машинный код.

  8. Компоновщик разрешает ссылки на внешние объекты и функции и генерирует исполняемый файл. Если модуль ссылается на внешние объекты или функции. Которые не определены ни в одной из единиц перевода. Компоновщик берет их из стандартной библиотеки или другой указанной библиотеки. Внешние объекты и функции не должны быть определены в программе более одного раза.

Для большинства компиляторов либо препроцессор является отдельной программой. Либо компилятор предоставляет опции для выполнения только предварительной обработки (шаги 1-4 в предыдущем списке). Эта настройка позволяет вам проверить. Что ваши директивы препроцессора имеют предполагаемые эффекты. Более практичный взгляд на процесс компиляции см. в главе 18.

Жетоны

Токен-это ключевое слово. Идентификатор, константа. Строковый литерал или символ. Символы в языке Си состоят из одного или нескольких знаков препинания и функционируют как операторы или орграфы. Или имеют синтаксическое значение. Как точка с запятой. Которая завершает простой оператор. Или фигурные { }скобки. Которые заключают блочный оператор. Например, следующий оператор C состоит из пяти токенов:

 printf(

Отдельные токены:

 printf ( - Привет, мир.

Токены, интерпретируемые препроцессором. Анализируются на третьем этапе трансляции. Они лишь немного отличаются от токенов. Которые компилятор интерпретирует на седьмом этапе перевода:

  • В #includeдирективе препроцессор распознает дополнительные токены filename>и "filename".

  • На этапе предварительной обработки символьные константы и строковые литералы еще не были преобразованы из исходного набора символов в набор символов выполнения.

  • В отличие от собственно компилятора. Препроцессор не делает различий между целочисленными константами и константами с плавающей запятой.

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

 a+++b

Поскольку первый +не может быть частью идентификатора или ключевого слова . Начинающегося сa, он начинает новый токен. Второй+, добавленный к первому. Образует действительный токен—оператор инкремента. А третий+-нет. Следовательно. Выражение должно быть проанализировано как:

 a ++ + b

Дополнительные сведения о компиляции программ на языке Си см. в главе 18.