Приложение A. Справочное руководство
A1. Введение
A2. Соглашения о лексике
A2.1. Лексемы (tokens)
A2.2. Комментарий
A2.3. Идентификаторы
A2.4. Ключевые слова
A2.5. Константы
A2.5.1. Целые константы
A2.5.2. Символьные константы
А2.5.3. Константы с плавающей точкой
A2.5.4. Константы-перечисления
A2.6. Строковые литералы
A3. Нотация синтаксиса
A4. Что обозначают идентификаторы
A4.1. Класс памяти
A4.2. Базовые типы
A4.3. Производные типы
A4.4. Квалификаторы типов
A5. Объекты и Lvalues
A6. Преобразования
A6.1. Целочисленное повышение
A6.2. Целочисленные преобразования
A6.3. Целые и числа с плавающей точкой
A6.4. Типы с плавающей точкой
А6.5. Арифметические преобразования
A6.6. Указатели и целые
A6.7. Тип void
А6.8. Указатели на void
A7. Выражения
A7.1. Генерация указателя
A7.2. Первичные выражения
A7.3. Постфиксные выражения
A7.3.1. Обращение к элементам массива
A7.3.2. Вызов функции
A7.3.3. Обращение к структурам
A7.3.4. Постфиксные операторы инкремента и декремента
А7.4. Унарные операторы
А7.4.1. Префиксные операторы инкремента и декремента
A7.4.2. Оператор получения адреса
A7.4.3. Оператор косвенного доступа
A7.4.4. Оператор унарный плюс
A7.4.5. Оператор унарный минус
A7.4.6. Оператор побитового отрицания
A7.4.7. Оператор логического отрицания
A7.4.8. Оператор определения размера sizeof
A7.5. Оператор приведения типа
A7.6. Мультипликативные операторы
A7.7. Аддитивные операторы
A7.8. Операторы сдвига
A7.9. Операторы отношения
A7.10. Операторы равенства
A7.11. Оператор побитового И
A7.12. Оператор побитового исключающего ИЛИ
A7.13. Оператор побитового ИЛИ
A7.14. Оператор логического И
A7.15. Оператор логического ИЛИ
А7.16. Условный оператор
A7.17. Выражения присваивания
A7.18. Оператор запятая
A7.19. Константные выражения
A8. Объявления
A8.1. Спецификаторы класса памяти
А8.2. Спецификаторы типа
A8.3. Объявления структур и объединений
A8.4. Перечисления
А8.5. Объявители
A8.6. Что означают объявители
A8.6.1. Объявители указателей
А8.6.2. Объявители массивов
А8.6.3. Объявители функций
A8.7. Инициализация
A8.8. Имена типов
А8.9. Объявление typedef
A8.10. Эквивалентность типов
A9. Инструкции
A9.1. Помеченные инструкции
A9.2. Инструкция-выражение
A9.3. Составная инструкция
A9.4. Инструкции выбора
A9.5. Циклические инструкции
A9.6. Инструкции перехода
А10. Внешние объявления
A10.1. Определение функции
A10.2. Внешние объявления
A11. Область видимости и связи
A11.1. Лексическая область видимости
A11.2. Связи
A12. Препроцессирование
A12.1. Трехзнаковые последовательности
A12.2. Склеивание строк
А12.3. Макроопределение и макрорасширение
A12.4. Включение файла
A12.5. Условная компиляция
A12.6. Нумерация строк
A12.7. Генерация сообщения об ошибке
A12.8. Прагма
A12.9. Пустая директива
A12.10. Заранее определенные имена
A13. Грамматика
Данное руководство описывает язык программирования Си, определенный 31
октября 1989 г. в соответствии с проектом, утвержденным в ANSI в качестве
Американского национального стандарта для информационных систем: Язык
программирования Си, X3.159-1989 ("American National Standard for
Information Systems - Programming Language C, X3.159-1989"). Это описание
- лишь один из вариантов предлагаемого стандарта, а не сам стандарт, однако
мы специально заботились о том, чтобы сделать его надежным руководством по
языку.
Настоящий документ в основном следует общей схеме описания, принятой в
стандарте (публикация которого в свою очередь основывалась на первом издании
этой книги), однако в организационном плане есть различия. Если не считать
отклонений в названиях нескольких продуктов и отсутствия формальных
определений лексем и препроцессора, грамматика языка здесь и грамматика в
стандарте эквивалентны.
Далее примечания (как и это) набираются с отступом от левого края страницы. В
основном эти примечания касаются отличий стандарта от версии языка, описанной
в первом издании этой книги, и от последующих нововведений в различных
компиляторах.
Программа состоит из одной или нескольких единиц трансляции, хранящихся в
виде файлов. Каждая такая единица проходит несколько фаз трансляции,
описанных в A12. Начальные фазы осуществляют лексические преобразования
нижнего уровня, выполняют директивы, заданные в программе строками,
начинающимися со знака #, обрабатывают макроопределения и производят
макрорасширения. По завершении работы препроцессора (A12) программа
представляется к виде последовательности лексем.
Существуют шесть классов лексем (или токенов): идентификаторы, ключевые
слона, константы, строковые литералы, операторы и прочие разделители.
Пробелы, горизонтальные и вертикальные табуляции, новые строки, переводы
страницы и комментарии (имеющие общее название символы-разделители)
рассматриваются компилятором только как разделители лексем и в остальном на
результат трансляции влияние не оказывают. Любой из символов-разделителей
годится, чтобы отделить друг от друга соседние идентификаторы, ключевые слова
и константы.
Если входной поток уже до некоторого символа разбит на лексемы, то
следующей лексемой будет самая длинная строка, которая может быть лексемой.
Символы /* открывают комментарий, а символы */ закрывают его. Комментарии
нельзя вкладывать друг в друга, их нельзя помещать внутрь строк или текстовых
литералов.
Идентификатор — это последовательность букв и цифр. Первым символом должна
быть буква; знак подчеркивания _ считается буквой. Буквы нижнего и верхнего
регистров различаются. Идентификаторы могут иметь любую длину; для внутренних
идентификаторов значимыми являются первые 31 символ; в некоторых реализациях
принято большее число значимых символов. К внутренним идентификаторам
относятся имена макросов и все другие имена, не имеющие внешних связей
(A11.2). На идентификаторы с внешними связями могут накладываться большие
ограничения: иногда воспринимаются не более шести первых символов и могут не
различаться буквы верхнего и нижнего регистров.
Следующие идентификаторы зарезервированы в качестве ключевых слов и в другом
смысле использоваться не могут:
auto
break
char
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
В некоторых реализациях резервируются также слова fortran и asm.
Ключевые слова const, signed и volatile впервые появились в стандарте ANSI;
enum и void - новые по отношению к первому изданию книги, но уже
использовались; ранее зарезервированное entry нигде не использовалось и
поэтому более не резервируется.
Существует несколько видов констант. Каждая имеет свой тип данных; базовые
типы рассматриваются в A4.2.
константа:
целая–константа
символьная-константа
константа-с-плавающей-точкой
константа-перечисление
Целая константа, состоящая из последовательности цифр, воспринимается как
восьмеричная, если она начинается с 0 (цифры нуль), и как десятичная в
противном случае. Восьмеричная константа не содержит цифр 8 и 9.
Последовательность цифр, перед которой стоят 0x или 0X, рассматривается как
шестнадцатеричное целое. В шестнадцатеричные цифры включены буквы от a (или
A) до f (или F) co значениями от 10 до 15.
Целая константа может быть записана с буквой-суффиксом u (или U) для
спецификации ее как беззнаковой константы. Она также может быть с буквой-
суффиксом l (или L) для указания, что она имеет тип long.
Тип целой константы зависит от ее вида, значения и суффикса (о типах см. A4).
Если константа - десятичная и не имеет суффикса, то она принимает первый из
следующих типов, который годится для представления ее значения: int, long
int, unsigned long int. Восьмеричная или шестнадцатеричная константа без
суффикса принимает первый возможный из типов: int, unsigned int, long int,
unsigned long int. Если константа имеет суффикс u или U, то она принимает
первый возможный из типов: unsigned int, unsigned long int. Если константа
имеет суффикс l или L, то она принимает первый возможный из типов: long int,
unsigned long int. Если константа имеет суффикс ul или UL, то она принимает
тип unsigned long int.
Типы целых констант получили существенное развитие в сравнении с первой
редакцией языка, в которой большие целые имели просто тип long. Суффиксы U
и u введены впервые.
Символьная константа - это последовательность из одной или нескольких
символов, заключенная в одиночные кавычки (например 'x'). Если внутри
одиночных кавычек расположен один символ, значением константы является
числовое значение этого символа в кодировке, принятой на данной машине.
Значение константы с несколькими символами зависит от реализации.
Символьная константа не может содержать в себе одиночную кавычку ' или символ
новой строки; чтобы изобразить их и некоторые другие символы, могут быть
использованы эскейп-последовательности:
новая строка (newline, linefeed) |
NL (LF) |
\n |
горизонтальная табуляция (horizontal tab) |
HT |
\t |
вертикальная табуляция (vertical tab) |
VT |
\v |
возврат на шаг (backspace) |
BS |
\b |
возврат каретки (carriage return) |
CR |
\r |
перевод страницы (formfeed) |
FF |
\f |
сигнал звонок (audible alert, bell) |
BEL |
\a |
обратная наклонная черта (backslash) |
\ |
\\ |
знак вопроса (question mark) |
? |
\? |
одиночная кавычка (single quote) |
' |
\' |
двойная кавычка (double quote) |
" |
\" |
восьмеричный код (octal number) |
ooo |
\ooo |
шестнадцатеричный код (hex number) |
hh |
\xhh |
Эскейп-последовательность \ooo состоит из обратной наклонной черты, за
которой следуют одна, две или три восьмеричные цифры, специфицирующие
значение желаемого символа. Наиболее частым примером такой конструкции
является \0 (за которой не следует цифра); она специфицирует NULL-символ.
Эскейп-последовательность \xhh состоит из обратной наклонной черты с буквой
x, за которыми следуют шестнадцатеричные цифры, специфицирующие значение
желаемого символа. На количество цифр нет ограничений, но результат будет не
определен, если значение полученного символа превысит значение самого
"большого" из допустимых символов. Если в данной реализации тип char
трактуется как число со знаком, то значение и в восьмеричной, и в
шестнадцатеричной эскейп-последовательности получается с помощью
"распространения знака", как если бы выполнялась операция приведения к типу
char. Если за \ не следует ни один из перечисленных выше символов, результат
не определен.
В некоторых реализациях имеется расширенный набор символов, который не может
быть охвачен типом char. Константа для такого набора пишется с буквой L
впереди (например L'x') и называется расширенной символьной константой. Такая
константа имеет тип wchar_t (целочисленный тип, определенный в стандартном
заголовочном файле <stddef.h>). Как и в случае обычных символьных
констант, здесь также возможны восьмеричные и шестнадцатеричные эскейп-
последовательности; если специфицированное значение превысит тип wchar_t,
результат будет не определен.
Некоторые из приведенных эскейп-последовательностей новые (шестнадцатеричные
в частности). Новым является и расширенный тип для символов. Наборам
символов, обычно используемым в Америке и Западной Европе, подходит тип char,
а тип wchar_t был добавлен главным образом для азиатских языков.
Константа с плавающей точкой состоит из целой части, десятичной точки,
дробной части, e или E и целого (возможно, со знаком), представляющего
порядок, и, возможно, суффикса типа, задаваемого одной из букв: f, F, l или
L. И целая, и дробная часть представляют собой последовательность цифр. Либо
целая часть, либо дробная часть (но не обе вместе) могут отсутствовать; также
могут отсутствовать десятичная точка или E с порядком (но не обе
одновременно). Тип определяется суффиксом: F или f определяют тип float, L
или l - тип long double; при отсутствии суффикса подразумевается тип double.
Суффиксы для констант с плавающей точкой являются нововведением.
Идентификаторы, объявленные как элементы перечисления (A8.4), являются
константами типа int.
Строковый литерал, который также называют строковой константой, - это
последовательность символов, заключенная в двойные кавычки (Например, "...").
Строка имеет тип "массив символов" и память класса static (A4), которая
инициализируется заданными символами. Представляются ли одинаковые строковые
литералы одной копией или несколькими, зависит от реализации. Поведение
программы, пытающейся изменить строковый литерал, не определено.
Написанные рядам строковые литералы объединяются (конкатенируются) в одну
строку. После любой конкатенации к строке добавляется NULL-байт (\0), что
позволяет программе, просматривающей строку, найти ее конец. Строковые
литералы не могут содержать в себе символ новой строки или двойную кавычку; в
них нужно использовать те же эскейп-последовательности, что и в символьных
константах.
Как и в случае с символьными константами, строковый литерал с символами из
расширенного набора должен начинаться с буквы L (например L"..."). Строковый
литерал из расширенного набора имеет тип "массив из wchar_t". Конкатенация
друг с другом обычных и "расширенных" строковых литералов не определена.
То, что строковые литералы не обязательно представляются разными копиями,
запрет на их модификацию, а также конкатенация соседних строковых литералов
- нововведения ANSI-стандарта. "Расширенные" строковые литералы также
объявлены впервые.
В нотации синтаксиса, используемой в этом руководстве, синтаксические понятия
набираются курсивом, а слова и символы, воспринимаемые буквально, обычным
шрифтом. Альтернативные конструкции обычно перечисляются в столбик (каждая
альтернатива на отдельной строке); в редких случаях длинные списки небольших
по размеру альтернатив располагаются в одной строке, помеченной словами "один
из". Необязательное слово-термин или не термин снабжается индексом "необ.".
Так, запись
{ выражениенеоб }
обозначает выражение, заключенное в фигурные скобки, которое в общем случае
может отсутствовать. Полный перечень синтаксических конструкций приведен в
A13.
В отличие от грамматики, данной в первом издании этой книги, приведенная
здесь грамматика старшинство и порядок выполнения операций в выражениях
описывает явно.
Идентификаторы, или имена, ссылаются на разные объекты (в оригинале - things.
- Примеч. ред.): функции; теги структур, объединений и перечислений; элементы
структур или объединений; typedef-имена; метки и объекты. Объектом
(называемым иногда переменной) является часть памяти, интерпретация которой
зависит от двух главных характеристик: класса памяти н ее типа. Класс памяти
сообщает о времени жизни памяти, связанной с идентифицируемым объектом, тип
определяет, какого рода значения находятся в объекте. С любым именем
ассоциируются своя область видимости (т. е. тот участок программы, где это
имя известно) и атрибут связи, определяющий, обозначает ли это имя в другом
файле тот же самый объект или функцию. Область видимости и атрибут связи
обсуждаются в A11.
Существуют два класса памяти: автоматический и статический. Несколько
ключевых слов в совокупности с контекстом объявлений объектов специфицируют
класс памяти для этих объектов.
Автоматические объекты локальны в блоке (A9.3), при выходе из него они
"исчезают". Объявление, заданное внутри блока, если в нем отсутствует
спецификация класса памяти или указан спецификатор auto, создаст
автоматический объект. Объект, помеченный в объявлении словом register,
является автоматическим и размещается по возможности в регистре машины.
Статические объекты могут быть локальными в блоке или располагаться вне
блоков, но в обоих случаях их значения сохраняются после выхода из блока (или
функции) до повторного в него входа. Внутри блока (в том числе и в блоке,
образующем тело функции) статические объекты в объявлениях помечаются словом
static. Объекты, объявляемые вне всех блоков на одном уровне с определениями
функций, всегда статические. С помощью ключевого слова static их можно
сделать локальными в пределах транслируемой единицы (в этом случае они
получают атрибут внутренней связи), и они становятся глобальными для всей
программы, если опустить явное указание класса памяти или использовать
ключевое слово extern (в этом случае они получают атрибут внешней связи).
Существует несколько базовых типов. Стандартный заголовочный файл
<limits.h>, описанный в приложении B, определяет самое большое и самое
малое значения для каждого типа в данной конкретной реализации. В приложении
B приведены минимально возможные величины.
Размер объектов, объявляемых как символы, позволяет хранить любой символ из
набора символов, принятого в машине. Если объект типа char действительно
хранит символ из данного набора, то его значением является код этого символа,
т. е. некоторое неотрицательное целое. Переменные типа char могут хранить и
другие значения, но тогда диапазон их значений и особенно вопрос о том,
знаковые эти значения или беззнаковые, зависит от реализации.
Беззнаковые символы, объявленные с помощью слов unsigned char, имеют ту же
разрядность, что и обычные символы, но представляют неотрицательные значения;
с помощью слов signed char можно явно объявить символы со знаком, которые
занимают столько же места, как и обычные символы.
Тип unsigned char не упоминался в первой редакции языка, но всеми
использовался. Тип signed char - новый.
Помимо char среди целочисленных типов могут быть целые трех размеров: short
int, int и long int. Обычные объекты типа int имеют естественный размер,
принятый в архитектуре данной машины, другие размеры предназначены для
специальных нужд. Более длинные целые по крайней мере покрывают все значения
более коротких целых, однако в некоторых реализациях обычные целые могут быть
эквивалентны коротким (short) или длинным (long) целым. Все типы int
представляют значения со знаком, если не оговорено противное.
Для беззнаковых целых в объявлениях используется ключевое слово unsigned.
Такие целые подчиняются арифметике по модулю 2 в степени n, где n - число
битов в представлении числа, и, следовательно, в арифметике с беззнаковыми
целыми никогда не бывает переполнения. Множество неотрицательных значений,
которые могут храниться в объектах со знаком, является подмножеством
значений, которые могут храниться в соответствующих объектах без знака;
знаковое и беззнаковое представления каждого такого значения совпадают.
Любые два из типов с плавающей точкой: с одинарной точностью (float), с
двойной точностью (double) и с повышенной точностью (long double) могут быть
синонимами, но каждый следующий тип этого списка должен по крайней мере
обеспечивать точность предыдущего.
long double - новый тип. В первой редакции языка синонимом для double был
long float, теперь последний изъят из обращения.
Перечисления - единственные в своем роде типы, которым дается полный перечень
значений; с каждым перечислением связывается множество именованных констант
(A8.4). Перечисления ведут себя наподобие целых, но компилятор обычно выдает
предупреждающее сообщение, если объекту некоторого перечислимого типа
присваивается нечто, отличное от его константы, или выражение не из этого
перечисления.
Поскольку объекты перечислений можно рассматривать как числа, перечисление
относится к арифметическому типу. Типы char и int всех размеров, каждый из
которых может быть со знаком или без знака, а также перечисления называют
целочисленными (integral) типами. Типы float, double и long double называются
типами с плавающей точкой.
Тип void специфицирует пустое множество значений. Он используется как "тип
возвращаемого функцией значения" в том случае, когда она не генерирует
никакого результирующего значения.
Помимо базовых типов существует практически бесконечный класс производных
типов, которые формируются из уже существующих и описывают следующие
конструкции:
массивы объектов заданного типа;
функции, возвращающие объекты заданного типа;
указатели на объекты заданного типа;
структуры, содержащие последовательность объектов, возможно, различных
заданных типов;
объединения, каждое из которых может содержать любой из нескольких объектов
различных заданных типов.
В общем случае приведенные методы конструирования объектов могут применяться
рекурсивно.
Тип объекта может снабжаться квалификатором. Объявление объекта с
квалификатором const указывает на то, что его значение далее не будет
изменяться; объявляя объект как volatile (изменчивый, непостоянный (англ.)),
мы указываем на его особые свойства для выполняемой компилятором оптимизации.
Ни один из квалификаторов на диапазоны значений и арифметические свойства
объектов не влияет. Квалификаторы обсуждаются в A8.2.
Объект - это некоторая именованная область памяти; lvalue - это выражение,
обозначающее объект. Очевидным примером lvalue является идентификатор с
соответствующим типом и классом памяти. Существуют операции, порождающие
lvalue. Например, если E - выражение типа указатель, то *E есть выражение для
lvalue, обозначающего объект, на который указывает E. Термин "lvalue"
произошел от записи присваивания E1 = E2, в которой левый (left - левый
(англ.), отсюда буква l, value - значение) операнд E1 должен быть выражением
lvalue. Описывая каждый оператор, мы сообщаем, ожидает ли он lvalue в
качестве операндов и выдает ли lvalue в качестве результата.
Некоторые операторы в зависимости от своих операндов мoгут вызывать
преобразование их значений из одного типа в другой. В этом параграфе
объясняется, что следует ожидать от таких преобразований. В A6.5
формулируются правила, по которым выполняются преобразования для большинства
обычных операторов. При рассмотрении каждого отдельного оператора эти правила
могут уточняться.
Объект типа перечисление, символ, короткое целое, целое в битовом поле - все
они со знаком или без могут использоваться в выражении там, где возможно
применение целого. Если тип int позволяет "охватить" все значения исходного
типа операнда, то операнд приводится к int, в противном случае он приводится
к unsigned int. Эта процедура называется целочисленным повышением (Integral promotion
– целочисленное повышение – иногда также переводят как
"интегральное продвижение” – Примеч. ред.).
Любое целое приводится к некоторому заданному беззнаковому типу путем поиска
конгруэнтного (т. е. имеющего то же двоичное представление) наименьшего
неотрицательного значения и получения остатка от деления его на nmax + 1, где
nmax - наибольшее число в этом беззнаковом типе. Для двоичного представления
в дополнительном коде это означает либо выбрасывание лишних старших разрядов,
если беззнаковый тип "уже" исходного типа, либо заполнение недостающих
старших разрядов нулями (для значения без знака) или значением знака (для
значения со знаком), если беззнаковый тип "шире" исходного.
В результате приведения любого целого к знаковому типу преобразуемое значение
не меняется, если оно представимо в этом новом типе, и противном случае
результат зависит от реализации.
При преобразовании из типа с плавающей точкой в целочисленный дробная часть
значения отбрасывается; если полученное при этом значение нельзя представить
в заданном целочисленном типе, то результат не определен. В частности, не
определен результат преобразования отрицательных значений с плавающей точкой
в беззнаковые целые.
Если значение преобразуется из целого в величину с плавающей точкой и она
находится в допустимом диапазоне, но представляется в новом типе неточно, то
результатом будет одно из двух значений нового типа, ближайших к исходному.
Если результат выходит за границы диапазона допустимых значений, поведение
программы не определено.
При преобразовании из типа с плавающей точкой меньшей точности в тип с
плавающей точкой большей точности значение не изменяется. Если, наоборот,
переход осуществляется от большей точности к меньшей и значение остается в
допустимых пределах нового типа, то результатом будет одно из двух ближайших
значений нового типа. Если результат выходит за границы диапазона допустимых
значений, поведение программы не определено.
Во многих операциях преобразование типов операндов и определение типа
результата осуществляются по одним и тем же правилам. Они состоят в том, что
операнды приводятся к некоторому общему типу, который также является и типом
результата. Эти правила называются обычными арифметическими преобразованиями.
Если какой-либо из операндов имеет тип long double, то другой приводится к
long double.
В противном случае, если какой-либо из операндов имеет тип double, то
другой приводится к double.
В противном случае, если какой-либо из операндов имеет тип float,
то другой приводится к float.
В противном случае для обоих операндов осуществляется целочисленное
повышение: затем, если один из операндов имеет тип unsigned long int, то и
другой преобразуется в unsigned long int.
В противном случае, если один из операндов принадлежит типу long int, а
другой - unsigned int, то результат зависит от того, покрывает ли long int
все значения unsigned int, и если это так, то unsigned int приводится к long
int; если нет, то оба операнда преобразуются в unsigned long int.
В противном случае, если один из операндов имеет тип long int, то другой
приводится к long int.
В противном случае, если один из операндов - unsigned int, то другой
приводится к unsigned int.
В противном случае оба операнда имеют тип int.
Здесь есть два изменения. Во-первых, арифметика с операндами с плавающей
точкой теперь может производиться с одинарной точностью, а не только с
двойной; в первой редакции языка вся арифметика с плавающей точкой
производилась с двойной точностью. Во-вторых, более короткий беззнаковый
тип в комбинации с более длинным знаковым типом не распространяет свойство
беззнаковости на тип результата; в первой редакции беззнаковый тип всегда
доминировал. Новые правила немного сложнее, но до некоторой степени
уменьшают вероятность появления неожиданных эффектов в комбинациях знаковых
и беззнаковых величин. При сравнении беззнакового выражения со знаковым
того же размера все же может возникнуть неожиданный результат.
К указателю можно прибавлять (и вычитать из него) выражение целочисленного
типа; последнее в этом случае подвергается преобразованию, описанному в A7.7
при рассмотрении оператора сложения.
К двум указателям на объекты одного типа, принадлежащие одному массиву, может
применяться операция вычитания; результат приводится к целому посредством
преобразования, описанного в A7.7 при рассмотрении оператора вычитания.
Целочисленное константное выражение со значением 0 или оно же, но приведенное
к типу void *, может быть преобразовано в указатель любого типа операторами
приведения, присваивания и сравнения. Результатом будет NULL-указатель,
который равен любому другому NULL-указателю того же типа, но не равен
никакому указателю на реальный объект или функцию.
Для указателей допускаются и другие преобразования, но в связи с ними
возникает проблема зависимости результата от реализации. Эти преобразования
должны быть специфицированы явным оператором преобразования типа или
оператором приведения (A7.5 и A8.8).
Указатель можно привести к целочисленному типу, достаточно большому для его
хранения; требуемый размер зависит от реализации. Функция преобразования
также зависит от реализации.
Объект целочисленного типа можно явно преобразовать в указатель. Если целое
получено из указателя и имеет достаточно большой размер, это преобразование
даст тот же указатель; в противном случае результат зависит от реализации.
Указатель на один тип можно преобразовать в указатель на другой тип. Если
исходный указатель ссылается на объект, должным образом не выровненный по
границам слов памяти, то в результате может произойти ошибка адресации. Если
требования на выравнивание у нового типа меньше или совпадают с требованиями
на выравнивание первоначального типа, то гарантируется, что преобразование
указателя в другой тип и обратно его не изменит; понятие "выравнивание"
зависит от реализации, однако в любой реализации объекты типа char
предъявляют минимальные требования на выравнивание. Как описано в A6.8,
указатель может также преобразовываться в void * и обратно, значение
указателя при этом не изменяется.
Указатель может быть преобразован в другой указатель того же типа с
добавлением или удалением квалификаторов (A4.4, A8.2) того типа объекта, на
который этот указатель показывает. Новый указатель, полученный добавлением
квалификатора, имеет то же значение, но с дополнительными ограничениями,
внесенными новыми квалификаторами. Операция по удалению квалификатора у
объекта приводит к тому, что восстанавливается действие его начальных
квалификаторов, заданных в объявлении этого объекта.
Наконец, указатель на функцию может быть преобразован в указатель на функцию
другого типа. Вызов функции по преобразованному указателю зависит от
реализации; однако, если указатель еще раз преобразовать к его исходному
типу, результат будет идентичен вызову по первоначальному указателю.
Значение (несуществующее) объекта типа void никак нельзя использовать, его
также нельзя явно или неявно привести к типу, отличному от void. Поскольку
выражение типа void обозначает отсутствие значения, его можно применять
только там, где не требуется значения. Например, в качестве выражения-
инструкции (A9.2) или левого операнда у оператора "запятая" (A7.18).
Выражение можно привести к тину void операцией приведения типа. Например,
применительно к вызову функции, используемому в роли выражения-инструкции,
операция приведения к void явным образом подчеркивает тот факт, что результат
функции отбрасывается.
Тип void не фигурировал в первом издании этой книги, однако за прошедшее
время стал общеупотребительным.
Любой указатель на объект можно привести к типу void * без потери информации.
Если результат подвергнуть обратному преобразованию, то мы получим прежний
указатель. В отличие от преобразований указатель-в-указатель (рассмотренных в
A6.6), которые требуют явных операторов приведения к типу, в присваиваниях и
сравнениях указатель любого типа может выступать в паре с указателем типа
void * без каких-либо предварительных преобразований типа.
Такая интерпретация указателей void * - новая; ранее роль обобщенного
указателя отводилась указателю типа char *. Стандарт ANSI официально
разрешает использование указателей void * совместно с указателями других
типов в присваиваниях и сравнениях; в иных комбинациях указателей стандарт
требует явных преобразований типа.
Приоритеты описываемых операторов имеют тот же порядок, что и пункты данного
параграфа (от высших к низшим). Например, для оператора +, описанного в A7.7,
термин "операнды" означает "выражения, определенные в A7.1—A7.6". В каждом
пункте описываются операторы, имеющие одинаковый приоритет, и указывается их
ассоциативность (левая или правая). Приоритеты и ассоциативность всех
операторов отражены в грамматике, приведенной в A13.
Приоритеты и ассоциативность полностью определены, а вот порядок вычисления
выражения не определен за некоторым исключением даже для подвыражений с
побочным эффектом. Это значит, что если в определении оператора
последовательность вычисления его операндов специально не оговаривается, то в
реализации можно свободно выбирать любой порядок вычислений и даже чередовать
правый и левый порядок. Однако любой оператор использует значения своих
операндов в точном соответствии с грамматическим разбором выражения, в
котором он встречается.
Это правило отменяет ранее предоставлявшуюся свободу в выборе порядка
выполнения операций, которые математически коммутативны и ассоциативны, но
которые в процессе вычислений могут таковыми не оказаться. Это изменение
затрагивает только вычисления с плавающей точкой, выполняющиеся "на грани
точности", и ситуации, когда возможно переполнение.
В языке не определен контроль за переполнением, делением на нуль и другими
исключительными ситуациями, возникающими при вычислении выражения. В
большинстве существующих реализаций Си при вычислении знаковых целочисленных
выражений и присваивании переполнение игнорируется, но результат таких
вычислений не определен. Трактовки деления на нуль и всех исключительных
ситуаций, связанных с плавающей точкой, могут не совпадать в разных
реализациях; иногда для обработки исключительных ситуаций предоставляется
нестандартная библиотечная функция.
Если тип выражения или подвыражения есть "массив из T", где T - некоторый
тип, то значением этого выражения является указатель на первый элемент
массива, и тип такого выражения заменяется на тип "указатель на T". Такая
замена не делается, если выражение является операндом унарного оператора &,
или операндом операций ++, --, sizeof, или левым операндом присваивания, или
операндом оператора . (точка). Аналогично, выражение типа "функция,
возвращающая Т", кроме случая, когда оно является операндом для &,
преобразуется в тип "указатель на функцию, возвращающую T".
Первичные выражения это идентификаторы, константы, строки и выражения в
скобках.
первичное - выражение:
идентификатор
константа
строка
(выражение)
Идентификатор, если он был должным образом объявлен (о том, как это делается,
речь пойдет ниже), - первичное выражение. Тип идентификатора специфицируется
в его объявлении. Идентификатор есть lvalue, если он обозначает объект (A5)
арифметического типа, либо объект типа "структура", "объединение" или
"указатель".
Константа - первичное выражение. Ее тип зависит от формы записи, которая была
рассмотрена в A2.5.
Строковый литерал - первичное выражение. Изначально его тип - "массив из char"
("массив из wchar_t" для строки символов расширенного набора), но в
соответствии с правилом, приведенным в A7.1, указанный тип обычно
превращается в "указатель на char" ("указатель на wchar_t") с результирующим
значением "указатель на первый символ строки". Для некоторых инициализаторов
такая замена типа не делается. (см. A8.7)
Выражение в скобках - первичное выражение, тип и значение которого идентичны типу и значению этого же
выражения без скобок. Наличие или отсутствие скобок не влияет на то, является
ли данное выражение lvalue или нет.
В постфиксных выражениях операторы выполняются слева направо.
постфиксное-выражение:
первичное-выражение
постфиксное-выражение [выражение]
постфиксное-выражение (список-аргументов-выраженийнеоб)
постфиксное-выражение.идентификатор
постфиксное-выражение->идентификатор
постфиксное-выражение ++
постфиксное-выражение –-
список-аргументов-выражений:
выражение-присваивание
список-аргументов-выражений , выражение-присваивание
Постфиксное выражение, за которым следует выражение в квадратных скобках,
есть постфиксное выражение, обозначающее обращение к индексируемому массиву.
Одно из этих двух выражений должно принадлежать типу "указатель на T", где T
- некоторый тип, а другое - целочисленному типу; тип результата
индексирования есть T. Выражение E1[E2] по определению идентично выражению
*((E1)+(E2)). Подробности см. в A8.6.
Вызов функции есть постфиксное выражение (оно называется именующим выражением
функции - function designator), за которым следуют скобки, содержащие
(возможно пустой) список разделенных запятыми выражений-присваиваний (A7.17),
представляющих собой аргументы этой функции. Если постфиксное выражение —
идентификатор, не объявленный в текущей области видимости, то считается, что
этот идентификатор как бы неявно описан объявлением
extern int identifier();
помещенным в самом внутреннем блоке, содержащем вызов соответствующей
функции. Постфиксное выражение (после, возможно неявного, описания и
генерации указателя, см. A7.1) должно иметь тип "указатель на функцию,
возвращающую T", где T - тип возвращаемого значения.
В первой версии языка для именующего выражения функции допускался только тип
"функция", и чтобы вызвать функцию через указатель, требовался явный
оператор *. ANSI-стандарт поощряет практику некоторых существующих
компиляторов, разрешающих иметь одинаковый синтаксис для обращения просто к
функции и обращения к функции, специфицированной указателем. Возможность
применения старого синтаксиса остается.
Термин аргумент используется для выражения, задаваемого в вызове функции;
термин параметр - для обозначения получаемого ею объекта (или его
идентификатора) в определении или объявлении функции. Вместо этих понятий
иногда встречаются термины "фактический аргумент (параметр)" и "формальный
аргумент (параметр)", имеющие те же смысловые различия.
При вызове функции каждый ее аргумент копируется; передача аргументов
осуществляется строго через их значения. Функции разрешается изменять
значения своих параметров, которые являются лишь копиями аргументов-
выражений, но эти изменения не могут повлиять на значения самих аргументов.
Однако можно передать указатель, чтобы позволить функции изменить значение
объекта, на который указывает этот указатель.
Имеются два способа объявления функции. В новом способе типы параметров
задаются явно и являются частью типа функции; такое объявление называется
прототипом функции. При старом способе типы параметров не указываются.
Способы объявления функций обсуждаются в A8.6.3 и A10.1.
Если вызов находится в области видимости объявления, написанного по-старому,
каждый его аргумент подвергается операции повышения типа: для целочисленных
аргументов осуществляется целочисленное повышение (A6.1), а для аргументов
типа float - преобразование в double. Если число аргументов не соответствует
количеству параметров в определении функции или если типы аргументов после
повышения не согласуются с типами соответствующих параметров, результат
вызова не определен. Критерий согласованности типов зависит от способа
определения функции (старого или нового). При старом способе сравниваются
повышенный тип аргумента в вызове и повышенный тип соответствующего
параметра; при новом способе повышенный тип аргумента и тип параметра (без
его повышения) должны быть одинаковыми.
Если вызов находится в области видимости объявления, написанного по-новому,
аргументы преобразуются, как если бы они присваивались переменным, имеющим
типы соответствующих параметров прототипа. Число аргументов должно совпадать
с числом явно описанных параметров, если только список параметров не
заканчивается многоточием (, ...). В противном случае число аргументов должно
быть больше числа параметров или равно ему; "скрывающиеся" под многоточием
аргументы подвергаются операции повышения типа (так, как это было описано в
предыдущем абзаце). Если определение функции задано по-старому, то типы
параметров в прототипе, которые неявно присутствуют в вызове, должны
соответствовать типам параметров в определении функции после их повышения.
Эти правила особенно усложнились из-за того, что они призваны обслуживать
смешанный способ (старого с новым) задания функций. По возможности его
следует избегать.
Очередность вычисления аргументов не определяется, в разных компиляторах она
различна. Однако гарантируется, что аргументы и именующее выражение функции
вычисляются полностью (включая и побочные эффекты) до входа в нее. Любая
функция допускает рекурсивное обращение.
Постфиксное выражение, за которым стоит точка с последующим идентификатором,
является постфиксным выражением. Выражение первого операнда должно быть
структурой или объединением, а идентификатор - именем элемента структуры или
объединения. Значение - именованный элемент структуры или объединения, а тип
значения - тип элемента структуры или объединения. Выражение является lvalue,
если первое выражение - lvalue и если тип второго выражения - не "массив".
Постфиксное выражение, за которым стоит стрелка (составленная из знаков - и
>) с последующим идентификатором, является постфиксным выражением.
Выражение первого операнда должно быть указателем на структуру (объединение),
а идентификатор - именем элемента структуры (объединения). Результат -
именованный элемент структуры (объединения), на которую указывает указатель,
а тип значения - тип элемента структуры (объединения); результат - lvalue,
если тип не есть "массив".
Таким образом, выражение E1->MOS означает то же самое, что и выражение
(*E1).MOS. Структуры и объединения рассматриваются в A8.3.
В первом издании книги уже было приведено правило, по которому имя элемента
должно принадлежать структуре или объединению, упомянутому в постфиксном
выражении. Там, однако, оговаривалось, что оно не является строго
обязательным. Последние компиляторы и ANSI делают его обязательным.
Постфиксное выражение, за которым следует ++ или --, есть постфиксное
выражение. Значением такого выражения является значение его операнда. После
того как значение было взято, операнд увеличивается (++) или уменьшается (--)
на 1. Операнд должен быть lvalue; информация об ограничениях, накладываемых
на операнд, и деталях операций содержится в A7.7, где обсуждаются аддитивные
операторы, и в A7.17, где рассматривается присваивание. Результат
инкрементирования или декрементирования не есть lvalue.
Выражения с унарными операторами выполняются справа налево.
унарное-выражение:
постфиксное-выражение
++ унарное-выражение
-- унарное-выражение
унарный-оператор выражение-приведенное-к-типу
sizeof унарное-выражение
sizeof (имя-типа)
унарный-оператор: один из
& * + - ~ !
Унарное выражение, перед которым стоит ++ или --, есть унарное выражение.
Операнд увеличивается (++) или уменьшается (--) на 1.
Значением выражения является значение его операнда после увеличения
(уменьшения). Операнд всегда должен быть lvalue; информация об ограничениях
на операнд и о деталях операции содержится в A7.7, где обсуждаются аддитивные
операторы, и в A7.17, где рассматривается присваивание. Результат
инкрементирования и декрементирования не есть lvalue.
Унарный оператор & обозначает операцию получения адреса своего операнда.
Операнд должен быть либо lvalue, не ссылающимся ни на битовое поле, ни на
объект, объявленный как register, либо иметь тип "функция". Результат -
указатель на объект (или функцию), адресуемый этим lvalue. Если тип операнда
есть T, то типом результата является "указатель на T".
Унарный оператор * обозначает операцию косвенного доступа (раскрытия
указателя), возвращающую объект (или функцию), на который указывает ее
операнд. Результат есть lvalue, если операнд - указатель на объект
арифметического типа или на объект типа "структура", "объединение" или
"указатель". Если тип выражения - "указатель на T", то тип результата - T.
Операнд унарного + должен иметь арифметический тип, результат - значение
операнда. Целочисленный операнд подвергается целочисленному повышению. Типом
результата является повышенный тип операнда.
Унарный + был добавлен для симметрии с унарным -.
Операнд для унарного минуса должен иметь арифметический тип, результат -
значение операнда с противоположным знаком. Целочисленный операнд
подвергается целочисленному повышению. Отрицательное значение от беззнаковой
величины вычисляется вычитанием из nmax+1 приведенного к повышенному типу
операнда, где nmax - максимальное число повышенного типа; однако минус нуль
есть нуль. Типом результата будет повышенный тип операнда.
Операнд оператора ~ должен иметь целочисленный тип, результат - дополнение
операнда до единиц по всем разрядам. Выполняется целочисленное повышение типа
операнда. Если операнд беззнаковый, то результат получается вычитанием его
значения из самого большого числа повышенного типа. Если операнд знаковый, то
результат вычисляется посредством приведения "повышенного операнда" к
беззнаковому типу, выполнения операции ~ и обратного приведения его к
знаковому типу. Тип результата - повышенный тип операнда.
Операнд оператора ! должен иметь арифметический тип или быть указателем.
Результат равен 1, если сравнение операнда с 0 дает истину, и равен 0 в
противном случае. Тип результата - int.
Оператор sizeof дает число байтов, требуемое для хранения объекта того типа,
который имеет его операнд. Операнд - либо выражение (которое не вычисляется),
либо имя типа, записанное в скобках. Примененный к char оператор sizeof дает
1. Для массива результат равняется общему количеству байтов в массиве, для
структуры или объединения - числу байтов в объекте, включая и байты-
заполнители, которые понадобились бы, если бы из элементов составлялся
массив. Размер массива из n элементов всегда равняется n, помноженному на
размер отдельного его элемента. Данный оператор нельзя применять к операнду
типа "функция", к незавершенному типу и к битовому полю. Результат -
беззнаковая целочисленная константа: конкретный ее тип зависит от реализации.
В стандартном заголовочном файле <stddef.h>(см. приложение B) этот тип
определяется под именем size_t.
Имя типа, записанное перед унарным выражением н скобках, вызывает приведение
значения этого выражения к указанному типу.
выражение-приведенное-к-типу:
унарное-выражение
(имя-типа) выражение-приведенное-к-типу
Данная конструкция называется приведением. Имена типов даны в A8.8. Результат
преобразований описан в A6. Выражение с приведением типа не является lvalue.
Мультипликативные операторы *, / и % выполняются слева направо.
мультипликативное-выражение:
выражение-приведенное-к-типу
мультипликативное-выражение * выражение-приведенное-к-типу
мультипликативное-выражение / выражение-приведенное-к-типу
мультипликативное-выражение % выражение-приведенное-к-типу
Операнды операторов * и / должны быть арифметического типа, оператора % -
целочисленного типа. Над операндами осуществляются обычные арифметические
преобразования, которые приводят их значения к типу результата.
Бинарный оператор * обозначает умножение.
Бинарный оператор / получает частное, а % - остаток от деления первого
операнда на второй; если второй операнд есть 0, то результат не определен. В
противном случае всегда выполняется соотношение: (a / b) * b + a % b
равняется a. Если оба операнда не отрицательные, то остаток не отрицательный
и меньше делителя; в противном случае стандарт гарантирует только одно: что
абсолютное значение остатка меньше абсолютного значения делителя.
Аддитивные операторы + и - выполняются слева направо. Если операнды имеют
арифметический тип, то осуществляются обычные арифметические преобразования.
Для каждого оператора существует еще несколько дополнительных сочетаний
типов.
аддитивное-выражение:
мультипликативное-выражение
аддитивное-выражение + мультипликативное-выражение
аддитивное-выражение - мультипликативное-выражение
Результат выполнения оператора + есть сумма его операндов. Указатель на
объект в массиве можно складывать с целочисленным значением. При этом
последнее преобразуется в адресное смещение посредством умножения его на
размер объекта, на который ссылается указатель. Сумма является указателем на
объект того же типа; только ссылается этот указатель на другой объект того же
массива, отстоящий от первоначального соответственно вычисленному смещению.
Так, если p - указатель на объект в массиве, то p+1 - указатель на следующий
объект того же массива. Если полученный в результате суммирования указатель
указывает за границы массива, то, кроме случая, когда он указывает на место,
находящееся непосредственно за концом массива, результат будет
неопределенным.
Возможность для указателя указывать на элемент, расположенный сразу за
концом массива, является новой. Тем самым узаконена общепринятая практика
организации циклического перебора элементов массива.
Результат выполнения оператора - (минус) есть разность операндов. Из
указателя можно вычитать значение любого целочисленного типа с теми же
преобразованиями и при тех же условиях, что и в сложении.
Если к двум указателям на объекты одного и того же типа применить оператор
вычитания, то в результате получится целочисленное значение со знаком,
представляющее собой расстояние между объектами, на которые указывают эти
указатели: указатель на следующий объект на 1 больше указателя на предыдущий
объект. Тип результата зависит от реализации: в стандартном заголовочном
файле <stddef.h> он определен под именем ptrdiff_t. Значение не
определено, если указатели указывают на объекты не одного и того же массива;
однако если p указывает на последний элемент массива, то p+1-p имеет
значение, равное 1.
Операторы сдвига << и >> выполняются слева направо. Для обоих
операторов каждый операнд должен иметь целочисленный тип, и каждый из них
подвергается целочисленному повышению. Тип результата совпадает с повышенным
типом левого операнда. Результат не определен, если правый операнд
отрицателен или его значение превышает число битов в типе левого выражения
или равно ему.
сдвиговое-выражение:
аддитивное -выражение
сдвиговое-выражение >> аддитивное-выражение
сдвиговое-выражение << аддитивное-выражение
Значение E1<<E2 равно значению E1 (рассматриваемому как цепочка битов),
сдвинутому влево на E2 битов; при отсутствии переполнения такая операция
эквивалентна умножению на 2E2. Значение E1>>E2 равно значению
E1, сдвинутому вправо на E2 битовые позиции. Если E1 - беззнаковое или имеет
неотрицательное значение, то правый сдвиг эквивалентен делению на 2E2,
в противном случае результат зависит от реализации.
Операторы отношения выполняются слева направо, однако это свойство едва ли
может оказаться полезным: согласно грамматике языка выражение a < b < c
трактуется так же, как (a < b) < c, а результат вычисления a < b
может быть только 0 или 1.
выражение-отношения:
сдвиговое -выражение
выражение-отношения < сдвиговое-выражение
выражение-отношения > сдвиговое-выражение
выражение-отношения <= сдвиговое-выражение
выражение-отношения >= сдвиговое-выражение
Операторы: < (меньше), > (больше), <= (меньше или равно) и >=
(больше или равно) — все выдают 0, если специфицируемое отношение ложно, и 1,
если оно истинно. Тип результата - int. Над арифметическими операндами
выполняются обычные арифметические преобразования. Можно сравнивать указатели
на объекты одного и того же типа (без учета квалификаторов); результат будет
зависеть от относительного расположения в памяти. Допускается, однако,
сравнение указателей на разные части одного и того же объекта: если два
указателя указывают на один и тот же простой объект, то они равны; если они
указывают на элементы одной структуры, то указатель на элемент с более
поздним объявлением в структуре больше; если указатели указывают на элементы
одного и того же объединения, то они равны; если указатели указывают на
элементы некоторого массива, то сравнение этих указателей эквивалентно
сравнению их индексов. Если p указывает на последний элемент массива, то p+1
больше, чем p, хотя p+1 указывает за границы массива. В остальных случаях
результат сравнения не определен.
Эти правила несколько ослабили ограничения, установленные в первой редакции
языка. Они позволяют сравнивать указатели на различные элементы структуры и
объединения и легализуют сравнение с указателем на место, которое
расположено непосредственно за концом массива.
выражение-равенства:
выражение-отношения
выражение-равенства == выражение-отношения
выражение-равенства != выражение-отношения
Операторы == (равно) и != (не равно) аналогичны операторам отношения с той
лишь разницей, что имеют более низкий приоритет. (Таким образом, a < b ==
c < d есть 1 тогда и только тогда, когда отношения a < b и c < d или
оба истинны, или оба ложны.)
Операторы равенства подчиняются тем же правилам, что и операторы отношения.
Кроме того, они дают возможность сравнивать указатель с целочисленным
константным выражением, значение которого равно нулю, и с указателем на void
(см. A6.6.).
И-выражение:
выражение-равенства
И-выражение & выражение-равенства
Выполняются обычные арифметические преобразования: результат - побитовое AND
операндов. Оператор применяется только к целочисленным операндам.
исключающее-ИЛИ-выражение:
И-выражение
исключающее-ИЛИ-выражение ^ И-выражение
Выполняются обычные арифметические преобразования; результат – побитовое
XOR операндов. Оператор применяется только к целочисленным
операндам.
ИЛИ выражение:
исключающее-ИЛИ-выражение
ИЛИ-выражение | исключающее-ИЛИ-выражение
Выполняются обычные арифметические преобразования; результат - побитовое OR
операндов. Оператор применяется только к целочисленным операндам.
логическое-И-выражение:
ИЛИ-выражение
логическое-И-выражение && ИЛИ-выражение
Операторы && выполняются слева направо. Оператор && выдает 1, если оба
операнда не равны нулю, и 0 в противном случае. В отличие от &, &&
гарантирует, что вычисления будут проводиться слева направо: вычисляется
первый операнд со всеми побочными эффектами; если он равен 0, то значение
выражения есть 0. В противном случае вычисляется правый операнд, и, если он
ранен 0, то значение выражения есть 0, в противном случае оно равно 1.
Операнды могут принадлежать к разным типам, но при этом каждый из них должен
иметь либо арифметический тип, либо быть указателем. Тип результата - int.
логическое-ИЛИ-выражение:
логическое-И-выражение
логическое-ИЛИ-выражение || логическое-И-выражение
Операторы || выполняются слева направо. Оператор || выдает 1, если по крайней
мере один из операндов не равен нулю, и 0 в противном случае. В отличие от |,
оператор || гарантирует, что вычисления будут проводиться слева направо:
вычисляется первый операнд, включая все побочные эффекты; если он не равен 0,
то значение выражения есть 1. В противном случае вычисляется правый операнд,
и если он не равен 0, то значение выражения есть 1, в противном случае оно
равно 0.
Операнды могут принадлежать разным типам, но операнд должен иметь либо
арифметический тип, либо быть указателем. Тип результата - int.
условное-выражение:
логическое-ИЛИ-выражение
логическое-ИЛИ-выражение ? выражение : условное-выражение
Вычисляется первое выражение, включая все побочные эффекты; если оно не равно
0, то результат есть значение второго выражения, в противном случае -
значение третьего выражения. Вычисляется только один из двух последних
операндов: второй или третий. Если второй и третий операнды арифметические,
то выполняются обычные арифметические преобразования, приводящие к некоторому
общему типу, который и будет типом результата. Если оба операнда имеют тип
void, или являются структурами или объединениями одного и того же типа, или
представляют собой указатели на объекты одного и того же типа, то результат
будет иметь тот же тип, что и операнды. Если один из операндов имеет тип
"указатель", а другой является константой 0, то 0 приводится к типу
"указатель", этот же тип будет иметь и результат. Если один операнд является
указателем на void, а второй - указателем другого типа, то последний
преобразуется в указатель на void, который и будет типом результата.
При сравнении типов указателей квалификаторы типов (A8.2) объектов, на
которые указатели ссылаются, во внимание не принимаются, но тип результата
наследует квалификаторы обеих ветвей условного выражения.
Существует несколько операторов присваивания; они выполняются справа налево.
выражение-присваивания:
условное-выражение
унарное-выражение оператор-присваивания выражение-присваивания
оператор-присваивания: один из
=
*=
/=
%=
+=
-=
<<=
>>=
&=
^=
|=
Операторы присваивания в качестве левого операнда требуют lvalue, причем
модифицируемого; это значит, что оно не может быть массивом, или иметь
незавершенный тип, или быть функцией. Тип левого операнда, кроме того, не
может иметь квалификатора const; и, если он является структурой или
объединением, в них не должно быть элементов или подэлементов (для вложенных
структур или объединений) с квалификаторами const.
Тип выражения присваивания соответствует типу его левого операнда, а значение
равно значению его левого операнда после завершения присваивания.
В простом присваивании с оператором = значение выражения замещает объект, к
которому обращается lvalue. При этом должно выполняться одно из следующих
условий: оба операнда имеют арифметический тип (если типы операндов разные,
правый операнд приводится к типу левого операнда); оба операнда есть
структуры или объединения одного и того же типа; один операнд есть указатель,
а другой - указатель на void; левый операнд - указатель, а правый -
константное выражение со значением 0; оба операнда - указатели на функции или
объекты, имеющие одинаковый тип (за исключением возможного отсутствия const
или volatile у правого операнда).
Выражение E1 op= E2 эквивалентно выражению E1 = E1 op(E2)с одним
исключением: E1 вычисляется только один раз.
выражение:
выражение-присваивания
выражение , выражение-присваивания
Два выражения, разделенные запятой, вычисляются слева направо, и значение
левого выражения отбрасывается. Тип и значение результата совпадают с типом и
значением правого операнда. Вычисление всех побочных эффектов левого операнда
завершается перед началом вычисления правого операнда. В контексте, в котором
запятая имеет специальное значение, например в списках аргументов функций
(A7.3.2) или в списках инициализаторов (A8.7) (здесь в качестве
синтаксических единиц фигурируют выражения присваивания), оператор запятая
может появиться только в группирующих скобках. Например, в
f(a, (t=3, t+2), c)
три аргумента, из которых второй имеет значение 5.
Синтаксически, константное выражение - это выражение с ограниченным
подмножеством операторов:
константное-выражение:
условное-выражение
При указании case-меток в переключателе, задании границ массивов и длин полей
битов, на месте значений перечислимых констант и инициализаторов, а также в
некоторых выражениях для препроцессора требуются выражения, вычисление
которых приводит к константе.
Константные выражения не могут содержать присваиваний, операторов
инкрементирования и декрементирования, вызовов функций и операторов-запятых;
перечисленные ограничения не распространяются на операнд оператора sizeof.
Если требуется получить целочисленное константное выражение, то его операнды
должны состоять из целых, перечислимых (enum), символьных констант и констант
с плавающей точкой; операции приведения должны специфицировать целочисленный
тип, а любая константа с плавающей точкой - приводиться к целому. Из этого
следует, что в константном выражении не может быть массивов, операций
косвенного обращения (раскрытия указателя), получения адреса и доступа к
полям структуры. (Однако для sizeof возможны операнды любого вида.)
Для константных выражений в инициализаторах допускается большая свобода;
операндами могут быть константы любого типа, а к внешним или статическим
объектам и внешним и статическим массивам, индексируемым константными
выражениями, возможно применять унарный оператор &. Унарный оператор & может
также неявно присутствовать при использовании массива без индекса или функции
без списка аргументов. Вычисление инициализатора должно давать константу или
адрес ранее объявленного внешнего или статического объекта плюс-минус
константа.
Меньшая свобода допускается для целочисленных константных выражений,
используемых после #if: не разрешаются sizeof-выражения, константы типа enum
и операции приведения типа. (см. A12.5.)
То, каким образом интерпретируется каждый идентификатор, специфицируется
объявлениями; они не всегда резервируют память для описываемых ими
идентификаторов. Объявления, резервирующие память, называются определениями и
имеют следующий вид:
объявление:
спецификаторы-объявления список-инициализаторов-объявителейнеоб
Объявители в списке-инициализаторов-объявителей содержат объявляемые
идентификаторы; спецификаторы-объявления представляют собой
последовательности, состоящие из спецификаторов типа и класса памяти.
спецификаторы-объявления:
спецификатор-класса-памяти спецификаторы-объявлениянеоб
спецификатор-типа спецификаторы-объявлениянеоб
квалификатор-типа спецификаторы-объявлениянеоб
список-инициализаторов-объявителей:
инициализатор-объявитель
список-инициализаторов-объявителей , инициализатор-объявитель
инициализатор-объявитель:
объявитель
объявитель = инициализатор
Объявители содержат подлежащие объявлению имена. Мы рассмотрим их позже, в
A8.5. Объявление должно либо иметь по крайней мере один объявитель, либо его
спецификатор типа должен определять тег структуры или объединения, либо -
задавать элементы перечисления; пустое объявление недопустимо.
Класс памяти специфицируется следующим образом:
спецификатор-класса-памяти:
auto
register
static
extern
typedef
Смысл классов памяти обсуждался в A4.
Спецификаторы auto и register дают объявляемым объектам класс автоматической
памяти, и эти спецификаторы можно применять только внутри функции. Объявления
с auto и register одновременно являются определениями и резервируют намять.
Спецификатор register эквивалентен auto, но содержит подсказку, сообщающую,
что в программе объявленные им объекты используются интенсивно. На регистрах
может быть размещено лишь небольшое число объектов, причем определенного
типа: указанные ограничения зависят от реализации. В любом случае к register-
объекту нельзя применять (явно или неявно) унарный оператор &.
Новым является правило, согласно которому вычислять адрес объекта класса
register нельзя, а класса auto можно.
Спецификатор static дает объявляемым объектам класс статической памяти, он
может использоваться и внутри, и вне функций. Внутри функции этот
спецификатор вызывает выделение памяти и служит определением; его роль вне
функций будет объяснена в A11.2.
Объявление со спецификатором extern, используемое внутри функции, объявляет,
что для объявляемого объекта где-то выделена память; о ее роли вне функций
будет сказано в A11.2.
Спецификатор typedef не резервирует никакой памяти и назван спецификатором
класса памяти из соображений стандартности синтаксиса; речь об этом
спецификаторе пойдет в A8.9.
Объявление может содержать не более одного спецификатора класса памяти. Если
он в объявлении отсутствует, то действуют следующие правила: считается, что
объекты, объявляемые внутри функций, имеют класс auto; функции, объявляемые
внутри функций, - класс extern; объекты и функции, объявляемые вне функций, -
статические и имеют внешние связи (см. A10, A11).
Спецификаторы типа определяются следующим образом:
спецификатор-типа:
void
char
short
int
long
float
double
signed
unsigned
структуры-или-объединения-спецификатор
спецификатор-перечисления
typedef-имя
Вместе с int допускается использование еще какого-то одного слова - long или
short; причем сочетание long int имеет тот же смысл, что и просто long:
аналогично short int - то же самое, что и short. Слово long может
употребляться вместе с double. С int и другими его модификациями (short, long
или char) разрешается употреблять одно из слов signed или unsigned. Любое из
последних может использоваться самостоятельно, в этом случае подразумевается
int.
Спецификатор signed бывает полезен, когда требуется обеспечить, чтобы объекты
типа char имели знак; его можно применять и к другим целочисленным типам, но
в этих случаях он избыточен.
За исключением описанных выше случаев объявление не может содержать более
одного спецификатора типа. Если в объявлении нет ни одного спецификатора
типа, то имеется в виду тип int.
Для указания особых свойств объявляемых объектов предназначаются
квалификаторы:
квалификатор-типа:
const
volatile
Квалификаторы типа могут употребляться с любым спецификатором типа.
Разрешается инициализировать const-объект, однако присваивать ему что-либо в
дальнейшем запрещается. Смысл квалификатора volatile зависит от реализации.
Средства const и volatile (изменчивый) введены стандартом ANSI. Квалификатор
const применяется, чтобы разместить объекты в памяти, открытой только на
чтение (ПЗУ), или чтобы способствовать возможной оптимизации. Назначение
квалификатора volatile - подавить оптимизацию, которая без этого указания
могла бы быть проведена. Например, в машинах, где адреса регистров ввода-
вывода отображены на адресное пространство памяти, указатель на регистр
некоторого устройства мог бы быть объявлен как volatile, чтобы запретить
компилятору экономить очевидно избыточную ссылку через указатель. Компилятор
может игнорировать указанные квалификаторы, однако обязан сигнализировать о
явных попытках изменить значение const-объектов.
Структура - это объект, состоящий из последовательности именованных элементов
различных типов. Объединение - объект, который в каждый момент времени
содержит один из нескольких элементов различных типов. Объявления структур и
объединений имеют один и тот же вид.
спецификатор структуры-или-объединения:
структуры-или-объединения идентификаторнеоб {список-объявлений-структуры}
структуры-или-объединения идентификатор
структура-или-объединение:
struct
union
Список-объявлений-структуры является последовательностью объявлений элементов
структуры или объединения:
список-объявлений-структуры:
объявление-структуры
список-объявлений-структуры объявление-структуры
объявление-структуры:
список-спецификаторов-квалификаторов список-структуры-объявителей;
список-спецификаторов-квалификаторов:
спецификатор-типа список-спецификаторов-квалификаторовнеоб
квалификатор-типа список-спецификаторов-квалификаторовнеоб
список-структуры-объявителей:
структуры-объявитель
список-структуры-объявителей , структуры-объявитель
Обычно объявление-структуры является просто объявлением для элементов
структуры или объединения. Элементы структуры, в свою очередь, могут состоять
из заданного числа разрядов (битов). Такой элемент называется битовым полем
или просто полем. Его размер отделяется от имени поля двоеточием:
структуры-объявитель:
объявитель
объявительнеоб : константное-выражение
Спецификатор типа, имеющий вид
структуры-или-объединения идентификатор { список-объявлений-структуры }
объявляет идентификатор тегом структуры или объединения, специфицированных
списком. Последующее объявление в той же или внутренней области видимости
может обращаться к тому же типу, используя в спецификаторе тег без списка:
структуры-или-объединения идентификатор
Если спецификатор с тегом, но без списка появляется там, где тег не объявлен,
специфицируется незавершенный тип. Объекты с незавершенным типом структуры
или объединения могут упоминаться в контексте, где не требуется знать их
размер — например в объявлениях (но не определениях) для описания указателя
или создания typedef, но не в иных случаях. Тип становится завершенным при
появлении последующего спецификатора с этим тегом, содержащего список
объявлений. Даже в спецификаторах со списком объявляемый тип структуры или
объединения является незавершенным внутри списка и становится завершенным
только после появления символа }, заканчивающего спецификатор.
Структура не может содержать элементов незавершенного типа. Следовательно,
невозможно объявить структуру или объединение, которые содержат сами себя.
Однако, кроме придания имени типу структуры или объединения, тег позволяет
определять структуры, обращающиеся сами к себе; структура или объединение
могут содержать указатели на самих себя, поскольку указатели на незавершенные
типы объявлять можно.
Особое правило применяется к объявлениям вида
структуры-или-объединения идентификатор;
которые объявляют структуру или объединение, но не имеют списка объявления и
объявителя. Даже если идентификатор имеет тег структуры или объединения во
внешней области видимости (A11.1), это объявление делает идентификатор тегом
новой структуры или объединения незавершенного типа во внутренней области
видимости.
Это невразумительное правило - новое в ANSI. Оно предназначено для взаимно
рекурсивных структур, объявленных во внутренней области видимости, но теги
которых могут быть уже объявлены во внешней области видимости.
Спецификатор структуры или объединения со списком, но без тега создает
уникальный тип, к которому можно обращаться непосредственно только в
объявлении, частью которого он является.
Имена элементов и тегов не конфликтуют друг с другом или обычными
переменными. Имя элемента не может появляться дважды в одной и той же
структуре или объединении, но тот же элемент можно использовать в разных
структурах или объединениях.
В первой редакции этой книги имена элементов структуры и объединения не
связывались со своими родителями. Однако в компиляторах эта связь стала
обычной задолго до появления стандарта ANSI.
Элемент структуры или объединения, не являющийся полем, может иметь любой тип
объекта. Поле (которое не имеет объявителя и, следовательно, может быть
безымянным) имеет тип int, unsigned int или signed int и интерпретируется
как объект целочисленного типа указанной в битах длины. Считается ли поле int
знаковым или беззнаковым, зависит от реализации. Соседний элемент-поле
упаковывается в ячейки памяти в зависимости от реализации в зависящем от
реализации направлении. Когда следующее за полем другое поле не влезает в
частично заполненную ячейку памяти, оно может оказаться разделенным между
двумя ячейками, или ячейка может быть забита балластом. Безымянное поле
нулевой ширины обязательно приводит к такой забивке, так что следующее поле
начнется с края следующей ячейки памяти.
Стандарт ANSI делает поля еще более зависимыми от реализации, чем в первой
редакции книги. Чтобы хранить битовые поля в "зависящем от реализации" виде
без квалификации, желательно прочитать правила языка. Структуры с битовыми
полями могут служить переносимым способом для попытки уменьшить размеры
памяти под структуру (вероятно, ценой увеличения кода программы и времени на
доступ к полям) или непереносимым способом для описания распределения памяти
на битовом уровне. Во втором случае необходимо понимать правила местной
реализации.
Элементы структуры имеют возрастающие по мере объявления элементов адреса.
Элементы структуры, не являющиеся полями, выравниваются по границам адресов в
зависимости от своего типа; таким образом, в структуре могут быть безымянные
дыры. Если указатель на структуру приводится к типу указателя на ее первый
элемент, результат указывает на первый элемент.
Объединение можно представить себе как структуру, все элементы которой
начинаются со смещением 0 и размеры которой достаточны для хранения любого из
элементов. В любой момент времени в объединении хранится не больше одного
элемента. Если указатель на объединение приводится к типу указателя на один
из элементов, результат указывает на этот элемент.
Вот простой пример объявления структуры:
struct tnode {
char tword[20];
int count;
struct tnode *left;
struct tnode *right;
};
Эта структура содержит массив из 20 символов, число типа int и два указателя
на подобную структуру. Если дано такое объявление, то
struct tnode s, *sp;
объявит s как структуру заданного вида, a sp - как указатель на такую
структуру. Согласно приведенным определениям выражение
sp->count
обращается к элементу count в структуре, на которую указывает sp;
s.left
- указатель на левое поддерево в структуре s, а
s.right->tword[0]
- это первый символ из tword - элемента правого поддерева s.
Вообще говоря, невозможно проконтролировать, тот ли используется элемент
объединения, которому последний раз присваивалось значение. Однако
гарантируется выполнение правила, облегчающего работу с элементами
объединения: если объединение содержит несколько структур, начинающихся с
общей для них последовательности данных, и если объединение в текущий момент
содержит одну из этих структур, то к общей части данных разрешается
обращаться через любую из указанных структур. Так, правомерен следующий
фрагмент программы:
union {
struct {
int type;
} n;
struct {
int type;
int intnode;
} ni;
struct {
int type;
float floatnode;
} nf;
} u;
...
u.nf.type = FLOAT;
u.nf.floatnode = 3.14;
...
if (u.n.type == FLOAT)
... sin(u.nf.floatnode) ...
Перечисления - это уникальный тип, значения которого покрываются множеством
именованных констант, называемых перечислителями. Вид спецификатора
перечисления заимствован у структур и объединений.
спецификатор-перечисления:
enum идентификаторнеоб {список-перечислителей}
enum идентификатор
список-перечислителей:
перечислитель
список-перечислителей , перечислитель
перечислитель:
идентификатор
идентификатор = константное-выражение
Идентификаторы, входящие в список перечислителей, объявляются константами
типа int и могут употребляться везде, где требуется константа. Если в этом
списке нет ни одного перечислителя со знаком =, то значения констант
начинаются с 0 и увеличиваются на 1 по мере чтения объявления слева направо.
Перечислитель со знаком = дает соответствующему идентификатору значение;
последующие идентификаторы продолжают прогрессию от заданного значения.
Имена перечислителей, используемые в одной области видимости, должны
отличаться друг от друга и от имен обычных переменных, однако их значения
могут и совпадать.
Роль идентификатора в переч-спецификаторе аналогична роли тега структуры в
структ-спецификаторе: он является именем некоторого конкретного перечисления.
Правила для списков и переч-спецификаторов (с тегами и без) те же, что и для
спецификаторов структур или объединений, с той лишь оговоркой, что элементы
перечислений не бывают незавершенного типа; тег переч-спецификатора без
списка перечислителей должен иметь в пределах области видимости спецификатор
со списком.
В первой версии языка перечислений не было, но они уже несколько лет
применяются.
Объявители имеют следующий синтаксис:
объявитель:
указательнеоб собственно-объявитель
собственно-объявитель:
идентификатор
(объявитель)
собственно-объявитель [константное-выражениенеоб]
собственно-объявитель (список-типов-параметров)
собственно-объявитель (список-идентификаторовнеоб)
указатель:
* список-квалификаторов-типанеоб
* список-квалификаторов-типанеоб указатель
список-квалификаторов-типа:
квалификатор-типа
список-квалификаторов-типа квалификатор-типа
У структуры объявителя много сходных черт со структурой подвыражений,
поскольку в объявителе, как и в подвыражении, допускаются операции косвенного
обращения, обращения к функции и получения элемента массива (с тем же
порядком применения).
Список объявителей располагается сразу после спецификаторов типа и указателя
класса памяти. Главный элемент любого объявителя - это объявляемый им
идентификатор; в простейшем случае объявитель из него одного и состоит, что
отражено в первой строке продукции грамматики с именем собственно-
объявитель. Спецификаторы класса памяти относятся непосредственно к
идентификатору, а его тип зависит от вида объявителя. Объявитель следует
воспринимать как утверждение: если в выражении идентификатор появляется в том
же контексте, что и в объявителе, то он обозначает объект специфицируемого
типа.
Если соединить спецификаторы объявления, относящиеся к типу (A8.2), и
некоторый конкретный объявитель, то объявление примет вид "T D", где T - тип,
a D - объявитель. Эта запись индуктивно придает тип идентификатору любого
объявителя.
В объявлении T D, где D - просто идентификатор, тип идентификатора есть T.
В объявлении T D, где D имеет вид
( D1 )
тип идентификатора в D1 тот же, что и в D. Скобки не изменяют тип, но могут
повлиять на результаты его "привязки" к идентификаторам в сложных объявителях.
В объявления T D, где D имеет вид
* список-квалификаторов-типанеоб D1
а тип идентификатора объявления T D1 есть "модификатор-типа T", тип
идентификатора D есть "модификатор-типа список-квалификаторов-типа указатель
на T". Квалификаторы, следующие за *, относятся к самому указателю, а не к
объекту, на который он указывает. Рассмотрим, например, объявление
int *ap[];
Здесь ap[] играет роль D1; объявление int ap[] следует расшифровать (см.
ниже) как "массив из int": список квалификаторов типа здесь пуст, а
модификатор типа есть "массив из". Следовательно, на самом деле объявление ap
гласит: "массив из указателей на int". Вот еще примеры объявлений:
int i, *pi, *const cpi = &i;
const int ci = 3, *pci;
В них объявляются целое i и указатель на целое pi. Значение указателя cpi
неизменно; cpi всегда будет указывать в одно и то же место, даже если
значение, на которое он указывает, станет иным. Целое ci есть константа, оно
измениться не может (хотя может инициализироваться, как в данном случае). Тип
указателя pci произносится как "указатель на const int"; сам указатель можно
изменить; при этом он будет указывать на другое место, но значение, на
которое он будет указывать, с помощью pci изменить нельзя.
В объявления T D, где D имеет вид
D1 [константное-выражениенеоб]
и где тип идентификатора объявления T D1 есть "модификатор-типа Т", тип
идентификатора D есть "модификатор-типа массив из T". Если константное
выражение присутствует, то оно должно быть целочисленным и больше 0. Если
константное выражение, специфицирующее количество элементов в массиве,
отсутствует, то массив имеет незавершенный тип.
Массив можно конструировать из объектов арифметического типа, указателей,
структур и объединений, а также других массивов (генерируя при этом
многомерные массивы). Любой тип, из которого конструируется массив, должен
быть завершенным, он не может быть, например, структурой или массивом
незавершенного типа. Это значит, что для многомерного массива пустой может
быть только первая размерность. Незавершенный тип массива получает свое
завершение либо в другом объявлении этого массива (A10.2), либо при его
инициализации (A8.7). Например, запись
float fa[17], *afp[17];
объявляет массив из чисел типа float и массив из указателей на числа типа
float. Аналогично
static int x3d[3][5][7];
объявляет статический трехмерный массив целых размера 3 x 5 x 7. На самом деле,
если быть точными, x3d является массивом из трех элементов, каждый из которых
есть массив из пяти элементов, содержащих по 7 значений типа int.
Операция индексирования E1[E2] определена так, что она идентична операции
*(E1+E2). Следовательно, несмотря на асимметричность записи, индексирование -
коммутативная операция. Учитывая правила преобразования, применяемые для
оператора + и массивов (A6.6, A7.1, A7.7), можно сказать, что если E1 -
массив, а E2 - целое, то E1[E2] обозначает E2-й элемент массива E1.
Так, x3d[i][j][k] означает то же самое, что и *(x3d[i][j]+k). Первое
подвыражение, x3d[i][j], согласно A7.1, приводится к типу "указатель на
массив целых"; по A7.7 сложение включает умножение на размер объекта типа
int. Из этих же правил следует, что массивы запоминаются "построчно"
(последние индексы меняются чаще) и что первая размерность в объявлении
помогает определить количество памяти, занимаемой массивом, однако в
вычислении адреса элемента массива участия не принимает.
В новом способе объявление функции T D, где D имеет вид
D1(список-типов-параметров)
и тип идентификатора объявления T D1 есть "модификатор-типа T", тип
идентификатора в D есть "модификатор-типа функция с аргументами список-типов-
параметров, возвращающая T". Параметры имеют следующий синтаксис:
список-типов-параметров:
список-параметров
список-параметров , ...
список-параметров:
объявление-параметра
список-параметров, объявление-параметра
объявление-параметра:
спецификаторы-объявления объявитель
спецификатор-объявления абстрактный-объявительнеоб
При новом способе объявления функций список параметров специфицирует их типы,
а если функция вообще не имеет параметров, на месте списка типов указывается
одно слово - void. Если список типов параметров заканчивается многоточием
",...", то функция может иметь больше аргументов, чем число явно описанных
параметров. (См. A7.3.2.)
Типы параметров, являющихся массивами и функциями, заменяются на указатели в
соответствии с правилами преобразования параметров (A10.1). Единственный
спецификатор класса памяти, который разрешается использовать в объявлении
параметра, - это register, однако он игнорируется, если объявитель функции не
является заголовком ее определения. Аналогично, если объявители в объявлениях
параметров содержат идентификаторы, а объявитель функции не является
заголовком определения функции, то эти идентификаторы тотчас же выводятся из
текущей области видимости.
При старом способе объявление функции T D, где D имеет вид
D1(список-идентификаторовнеоб)
и тип идентификатора объявления T D1 есть "модификатор-типа Т", тип
идентификатора в D есть "модификатор-типа функция от неспецифицированных
аргументов, возвращающая Т". Параметры, если они есть, имеют следующий вид:
список-идентификаторов:
идентификатор
список-идентификаторов, идентификатор
При старом способе, если объявитель функции не используется в качестве
заголовка определения функции (A10.1), список идентификаторов должен
отсутствовать. Никакой информации о типах параметров в объявлениях не
содержится. Например, объявление
int f(), *fpi(), (*pfi)();
объявляет функцию f, возвращающую число типа int, функцию fpi, возвращающую
указатель на число типа int, и указатель pfi на функцию, возвращающую число
типа int. Ни для одной функции в объявлении не указаны типы параметров; все
функции описаны старым способом.
Вот как выглядит объявление в новой записи:
int strcpy(char *dest, const char *source), rand(void);
Здесь strcpy - функция с двумя аргументами, возвращающая значение типа int;
первый аргумент - указатель на значение типа char, а второй - указатель на
неизменяющееся значение типа char. Имена параметров играют роль хороших
комментариев. Вторая функция, rand, аргументов не имеет и возвращает int.
Объявители функций с прототипами параметров - наиболее важное нововведение
ANSI-стандарта. В сравнении со старым способом, принятым в первой редакции
языка, они позволяют проверять и приводить к нужному типу аргументы во всех
вызовах. Следует однако отметить, что их введение привнесло в язык некоторую
сумятицу и необходимость согласования обеих форм. Чтобы обеспечить
совместимость, потребовались некоторые "синтаксические уродства", а именно
void, для явного указания на отсутствие параметров.
Многоточие ", ..." применительно к функциям с варьируемым числом аргументов
- также новинка, которая вместе со стандартным заголовочным файлом макросов
<stdarg.h> формализует неофициально используемый, но официально
запрещенный в первой редакции механизм. Указанные способы записи
заимствованы из языка C++.
С помощью иниц-объявителя можно указать начальное значение объявляемого
объекта. Инициализатору, представляющему собой выражение или список
инициализаторов, заключенный в фигурные скобки, предшествует знак =. Этот
список может завершаться запятой; ее назначение сделать форматирование более
четким.
инициализатор:
выражение-присваивания
{ список-инициализаторов }
{ список-инициализаторов, }
список-инициализаторов:
инициализатор
список-инициализаторов , инициализатор
В инициализаторе статического объекта или массива все выражения должны быть
константными (A7.19). Если инициализатор auto- и register-объекта или массива
находится в списке, заключенном в фигурных скобки, то входящие в него
выражения также должны быть константными. Однако в случае автоматического
объекта с одним выражением инициализатор не обязан быть константным
выражением, он просто должки иметь соответствующий объекту тип.
В первой редакции не разрешалась инициализация автоматических структур,
объединений и массивов. ANSI-стандарт позволяет это; однако, если
инициализатор не может быть представлен одним простым выражением,
инициализация может быть выполнена только с помощью константных конструкций.
Статический объект, инициализация которого явно не указана, инициализируется
так, как если бы ему (или его элементам) присваивалась константа 0. Начальное
значение автоматического объекта, явным образом не инициализированного, не
определено.
Инициализатор указателя или объекта арифметического типа - это одно выражение
(возможно, заключенное в фигурные скобки), которое присваивается объекту.
Инициализатор структуры - это либо выражение того же структурного типа, либо
заключенные в фигурные скобки инициализаторы ее элементов, заданные по
порядку. Безымянные битовые поля игнорируются и не инициализируются. Если
инициализаторов в списке меньше, чем элементов, то оставшиеся элементы
инициализируются нулем. Инициализаторов не должно быть больше числа
элементов.
Инициализатор массива - это список инициализаторов его элементов, заключенный
в фигурные скобки. Если размер массива не известен, то он считается равным
числу инициализаторов, при этом тип его становится завершенным. Если размер
массива известен, то число инициализаторов не должно превышать числа его
элементов; если инициализаторов меньше, оставшиеся элементы обнуляются.
Как особый выделен случай инициализации массива символов. Последний можно
инициализировать с помощью строкового литерала; символы инициализируют
элементы массива в том порядке, как они заданы в строковом литерале. Точно
так же, с помощью литерала из расширенного набора символов (A2.6), можно
инициализировать массив типа wchar_t. Если размер массива не известен, то он
определяется числом символов в строке, включая и завершающий NULL-символ; если
размер массива известен, то число символов в строке, не считая завершающего
NULL-символа, не должно превышать его размера.
Инициализатором объединения может быть либо выражение того же типа, либо
заключенный в фигурные скобки инициализатор его первого элемента.
В первой версии языка не позволялось инициализировать объединения. Правило
"первого элемента" не отличается изяществом, однако не требует нового
синтаксиса. Стандарт ANSI проясняет еще и семантику не инициализируемых явно
объединений.
Введем для структуры и массива обобщенное имя: агрегат. Если агрегат содержит
элементы агрегатного типа, то правила инициализации применяются рекурсивно.
Фигурные скобки в некоторых случаях инициализации можно опускать. Если
инициализатор элемента агрегата, который сам является агрегатом, начинается с
левой фигурной скобки, то этот подагрегат инициализируется последующим
списком разделенных запятыми инициализаторов; считается ошибкой, если
количество инициализаторов подагрегата превышает число его элементов. Если,
однако, инициализатор подагрегата не начинается с левой фигурной скобки, то
чтобы его инициализировать, нужно отсчитать соответствующее число элементов
из списка; при этом остальные элементы инициализируются следующими
инициализаторами агрегата, для которого данный подагрегат является частью.
Например
int x[] = { 1, 3, 5 };
объявляет и инициализирует x как одномерный массив с тремя элементами,
поскольку размер не был указан, а список состоит из трех инициализаторов.
float y[4][3] = {
{ 1, 3, 5 },
{ 2, 4, 6 },
{ 3, 5, 7 },
};
представляет собой инициализацию с полным набором фигурных скобок: 1, 3 и 5
инициализируют первую строку в массиве у[0], т. е. y[0][0], у[0][1] и
y[0][2]. Аналогично инициализируются следующие две строки: y[1] и y[2].
Инициализаторов не хватило на весь массив, поэтому элементы строки y[3] будут
нулевыми. В точности тот же результат был бы достигнут с помощью следующего
объявления:
float у[4][3] = {
1, 3, 5, 2, 4, 6, 3, 5, 7
};
Инициализатор для y начинается с левой фигурной скобки, но для y[0] скобки
нет, поэтому из списка будут взяты три элемента. Аналогично по три элемента
будут взяты для y[1], а затем и для y[2]. В
float у[4][3] = {
{ 1 }, { 2 }, { 3 }, { 4 }
};
инициализируется первый столбец матрицы y, все же другие элементы остаются
нулевыми. Наконец,
char msg[] = "Синтаксическая ошибка в строке %s\n";
представляет собой пример массива символов, элементы которого
инициализируются с помощью строки; в его размере учитывается и завершающий
NULL-символ.
В ряде случаев возникает потребность в применении имени типа данных (например
при явном приведении к типу, в указании типов параметров внутри объявлений
функций, в аргументе оператора sizeof). Эта потребность реализуется с помощью
имени типа, определение которого синтаксически почти совпадает с объявлением
объекта того же типа. Оно отличается от объявления лишь тем, что не содержит
имени объекта.
имя-типа:
список-спецификаторов-квалификаторов абстрактный-объявительнеоб
абстрактный-объявитель:
указатель
указательнеоб собственно-абстрактный-объявитель
собственно-абстрактный-объявитель:
( абстрактный-объявитель )
собственно-абстрактный-объявительнеоб[константное-выражениенеоб]
собственно-абстрактный-объявительнеоб(список-типов-параметровнеоб)
Можно указать одно-единственное место в абстрактном объявителе, где мог бы
оказаться идентификатор, если бы данная конструкция была полноценным
объявителем. Именованный тип совпадает с типом этого "невидимого
идентификатора". Например
int
int *
int *[3]
int (*)[]
int *()
int (*[])(void)
соответственно обозначают типы int, "указатель на int", "массив из трех
указателей на int", "указатель на массив из неизвестного количества int",
"функция неизвестного количества параметров, возвращающая указатель на int",
"массив неизвестного количества указателей на функции без параметров, каждая
из которых возвращает int".
Объявления, в которых спецификатор класса памяти есть typedef, не объявляют
объектов - они определяют идентификаторы, представляющие собой имена типов.
Эти идентификаторы называются typedef-именами.
typedef-имя:
идентификатор
Объявление typedef приписывает тип каждому имени своего объявителя обычным
способом (см. A8.6.). С этого момента typedef-имя синтаксически эквивалентно
ключевому слову спецификатора типа, обозначающему связанный с ним тип.
Например, после
typedef long Blockno, *Blockptr;
typedef struct { double r, theta; } Complex;
допустимы следующие объявления:
Blockno b;
extern Blockptr bp;
Complex z, *zp;
b принадлежит типу long, bp — типу "указатель на long"; z — это структура
заданного вида, a zp - принадлежит типу "указатель на такую структуру".
Объявление typedef не вводит новых типов, оно только дает имена типам, которые
могли бы быть специфицированы и другим способом. Например, b имеет тот же
тип, что и любой другой объект типа long.
typedef-имена могут быть перекрыты другими определениями во внутренней
области видимости, но при условии, что в них присутствует указание типа.
Например
extern Blockno;
не переобъявляет Blockno, а вот
extern int Blockno;
переобъявляет.
Два списка спецификаторов типа эквивалентны, если они содержат одинаковый
набор спецификаторов типа с учетом синонимичности названий (например, long и
int long считаются одинаковыми типами). Структуры, объединения и перечисления
с разными тегами считаются разными, а каждое безтеговое объединение, структура
или перечисление представляет собой уникальный тип.
Два типа считаются совпадающими, если их абстрактные объявители (A8.8) после
замены всех typedef-имен их типами и выбрасывания имен параметров функций
составят эквивалентные списки спецификаторов типов. При сравнении учитываются
размеры массивов и типы параметров функция.
За исключением оговоренных случаев инструкции выполняются том порядке, как
они написаны. Инструкции не имеют значений и выполняются, чтобы произвести
определенные действия. Все виды инструкций можно разбить на несколько групп:
инструкция:
помеченная–инструкция
инструкция–выражение
составная-инструкция
инструкция-выбора
циклическая-инструкция
инструкция-перехода
Инструкции может предшествовать метка.
помеченная-инструкция:
идентификатор : инструкция
case константное-выражение : инструкция
default : инструкция
Метка, состоящая из идентификатора, одновременно служит и объявлением этого
идентификатора. Единственное назначение идентификатора-метки - указать место
перехода для goto. Областью видимости идентификатора-метки является текущая
функция. Так как метки имеют свое собственное пространство имен, они не
"конфликтуют" с другими идентификаторами и не могут быть перекрыты (см.
A11.1.).
case-метки и default-метки используются в инструкции switch (A9.4).
Константное выражение в case должно быть целочисленным. Сами по себе метки не
изменяют порядка вычислений.
Наиболее употребительный вид инструкции - это инструкция-выражение.
инструкция-выражение:
выражениенеоб ;
Чаще всего инструкция-выражение - это присваивание или вызов функции. Все
действия, реализующие побочный эффект выражения, завершаются, прежде чем
начинает выполняться следующая инструкция. Если выражение в инструкции
опущено, то она называется пустой; пустая инструкция часто используется для
обозначения пустого тела циклической инструкции или в качестве места для
метки.
Так как в местах, где по синтаксису полагается одна инструкция, иногда
возникает необходимость выполнить несколько, предусматривается возможность
задания составной инструкции (которую также называют блоком). Тело
определения функции есть составная инструкция;
составная-инструкция:
{ список-объявлений список-инструкцийнеоб }
список-объявлений:
объявление
список-объявлений объявление
список-инструкций:
инструкция
список-инструкций инструкция
Если идентификатор из списка объявлений находился в области видимости
объемлющего блока, то действие внешнего объявления при входе внутрь данного
блока приостанавливается (A11.1), а после выхода из него возобновляется.
Внутри блока идентификатор может быть объявлен только один раз. Для каждого
отдельного пространства имен эти правила действуют независимо (A11);
идентификаторы из разных пространств имен всегда различны.
Инициализация автоматических объектов осуществляется при каждом входе в блок
и продолжается по мере продвижения по объявителям. При передаче управления
внутрь блока никакие инициализации не выполняются. Инициализации статических
объектов осуществляются только один раз перед запуском программы.
Инструкции выбора осуществляют отбор одной из нескольких альтернатив,
определяющих порядок выполнения инструкций.
инструкция-выбора:
if ( выражение ) инструкция
if ( выражение ) инструкция else инструкция
switch ( выражение ) инструкция
Оба вида if-инструкций содержат выражение, которое должно иметь
арифметический тип или тип указателя. Сначала вычисляется выражение со всеми
его побочными эффектами, результат сравнивается с 0. В случае несовпадения с
0 выполняется первая подинструкция. В случае совпадения с 0 для второго типа
if выполняется вторая подинструкция. Связанная со словом else неоднозначность
разрешается тем, что слово else соотносят с последней еще не имеющей else if-
инструкцией, расположенной в одном с этим else блоке и на одном уровне
вложенности блоков.
Инструкция switch вызывает передачу управления на одну из нескольких
инструкций в зависимости от значения выражения, которое должен иметь
целочисленный тип.
Управляемая с помощью switch подинструкция обычно составная. Любая инструкция
внутри этой подинструкции может быть помечена одной или несколькими case-
метками (A9.1). Управляющее выражение подвергается целочисленному повышению
(A6.1), а case-константы приводятся к повышенному типу. После такого
преобразования никакие две case-константы в одной инструкции switch не должны
иметь одинаковых значений. Co switch-инструкцией может быть связано не более
одной default-метки. Конструкции switch допускается вкладывать друг в друга;
case и default-метки относятся к самой внутренней switch-инструкции из тех,
которые их содержат.
Инструкция switch выполняется следующим образом. Вычисляется выражение со
всеми побочными эффектами, и результат сравнивается с каждой case-константой.
Если одна из case-констант равна значению выражения, управление переходит на
инструкцию с соответствующей case-меткой. Если ни с одной из case-констант
нет совпадения, управление передается на инструкцию с default-меткой, если
такая имеется, в противном случае ни одна из подинструкций switch не
выполняется.
В первой версии языка требовалось, чтобы выражение и case-константы в switch
были типа int.
Циклические инструкции специфицируют циклы.
циклическая-инструкция:
while (выражение) инструкция
do инструкция while (выражение)
for (выражениенеоб ; выражениенеоб ; выражениенеоб ) инструкция
В инструкциях while и do выполнение подинструкций повторяется до тех пор,
пока значение выражения не станет нулем. Выражение должно иметь
арифметический тип или тип указателя. В while вычисление выражения со всеми
побочными эффектами и проверка осуществляются перед каждым выполнением
инструкции, a в do — после.
В инструкции for первое выражение вычисляется один раз, тем самым
осуществляется инициализация цикла. На тип этого выражения никакие
ограничения не накладываются. Второе выражение должно иметь арифметический
тип или тип указателя; оно вычисляется перед каждой итерацией. Как только его
значение становится равным 0, for прекращает свою работу. Третье выражение
вычисляется после каждой итерации и, следовательно, выполняет повторную
инициализацию цикла. Никаких ограничений на его тип нет. Побочные эффекты
всех трех выражений заканчиваются по завершении их вычислений. Если
подинструкция не содержит в себе continue) то
for (выражение1 ; выражение2 ; выражениеЗ) инструкция
эквивалентно конструкции
выражение1;
while (выражение2) {
инструкция
выражениеЗ;
}
Любое из трех выражений цикла может быть опущено. Считается, что отсутствие
второго выражения равносильно сравнению с нулем ненулевой константы.
Инструкции перехода осуществляют безусловную передачу управления.
инструкция-перехода:
goto идентификатор;
continue;
break;
return выражениенеоб;
В goto-инструкции идентификатор должен быть меткой (A9.1), расположенной в
текущей функции. Управление передается на помеченную инструкцию.
Инструкцию continue можно располагать только внутри цикла. Она вызывает
переход к следующей итерации самого внутреннего содержащего ее цикла. Говоря
более точно, для каждой из конструкций
while (...){
...
contin: ;
}
do {
...
contin: ;
} while (...);
for (...){
...
contin: ;
}
инструкция continue, если она не помещена в еще более внутренний цикл, делает
то же самое, что и goto contin.
Инструкция break встречается в циклической или в switch-инструкции, и только
в них. Она завершает работу самой внутренней циклической или switch-
инструкции, содержащей данную инструкцию break, после чего управление
переходит к следующей инструкции.
С помощью return функция возвращает управление в программу, откуда была
вызвана. Если за return следует выражение, то его значение возвращается
вызвавшей эту функцию программе. Значение выражения приводится к типу так,
как если бы оно присваивалось переменной, имеющей тот же тип, что и функция.
Ситуация, когда "путь" вычислений приводит в конец функции (т. е. на
последнюю закрывающую фигурную скобку), равносильна выполнению return-
инструкции без выражения. При этом, а также в случае явного задания return
без выражения возвращаемое значение не определено.
То, что подготовлено в качестве ввода для Си-компилятора, называется единицей
трансляции. Она состоит из последовательности внешних объявлений, каждое из
которых представляет собой либо объявление, либо определение функции.
единица-трансляции:
внешнее-объявление
единица-трансляции внешнее-объявление
внешнее-объявление:
определение-функции
объявление
Область видимости внешних объявлений простирается до конца единицы
трансляции, в которой они объявлены, точно так же, как область видимости
объявлений в блоке распространяется до конца этого блока. Синтаксис внешнего
объявления не отличается от синтаксиса любого другого объявления за одним
исключением: код функции можно определять только с помощью внешнего
объявления.
Определение функции имеет следующий вид:
определение-функции:
спецификаторы-объявлениянеоб объявитель список-объявленийнеоб
составная-инструкция
Из спецификаторов класса памяти в спецификаторах-объявлениях возможны только
extern и static; различия между последними рассматриваются в A11.2.
Типом возвращаемого функцией значения может быть арифметический тип,
структура, объединение, указатель и void, но не "функция" и не "массив".
Объявитель в объявлении функции должен явно указывать на то, что описываемый
им идентификатор имеет тип "функция", т. е. он должен иметь одну из следующих
двух форм (A8.6.3):
собственно-объявитель ( список-типов-параметров )
собственно-объявитель ( список-идентификаторовнеоб)
где собственно-объявитель есть идентификатор или идентификатор, заключенный
в скобки. Заметим, что тип "функция" посредством typedef получить нельзя.
Первая форма соответствует определению функции новым способом, для которого
характерно объявление параметров в списке-типов-параметров вместе с их типами;
за объявителем не должно быть списка-объявлений. Если список-типов-параметров
не состоит из одного-единственного слова void, показывающего, что параметров
у функции нет, то в каждом объявителе в списке-типов-параметров обязан
присутствовать идентификатор. Если список-типов-параметров заканчивается
знаками ", ...", то вызов функции может иметь аргументов больше, чем
параметров; в таком случае, чтобы обращаться к дополнительным аргументам,
следует пользоваться механизмом макроса va_arg из заголовочного файла
<stdarg.h>, описанного в приложении B. Функции с переменным числом
аргументов должны иметь по крайней мере один именованный параметр.
Вторая форма - определение функции старым способом. Список-идентификаторов
содержит имена параметров, а список-объявлений приписывает им типы. В списке-
объявлении разрешено объявлять только именованные параметры, инициализация
запрещается, и из спецификаторов класса памяти возможен только register.
И в том и другом способе определения функции мыслится, что все параметры как
бы объявлены в самом начале составной инструкции, образующей тело функции, и
совпадающие с ними имена здесь объявляться не должны (хотя, как и любые
идентификаторы, их можно переобъявить в более внутренних блоках). Объявление
параметра "массив из типа" можно трактовать как "указатель на тип",
аналогично объявлению параметра объявление "функция, возвращающая тип" можно
трактовать как "указатель на функцию, возвращающую тип". В момент вызова
функции ее аргументы соответствующим образом преобразуются и присваиваются
параметрам (см. A7.3.2).
Новый способ определения функций введен ANSI-стандартом. Есть также
небольшие изменения в операции повышения типа; в первой версии языка
параметры типа float следовало читать как double. Различие между float и
double становилось заметным, лишь когда внутри функции генерировался
указатель на параметр.
Ниже приведен пример определения функции новым способом:
int max(int a, int b, int c)
{
int m;
m = (a > b) ? a: b;
return (m > с) ? m : с;
}
Здесь int-спецификаторы-объявления; max(int a, int b, int с) - объявитель
функции, a { ... } - блок, задающий ее код. Определение старым способом той
же функции выглядит следующим образом:
int max(a, b, с)
int а, b, с;
{
/* ... */
}
где max(a, b, c) – объявитель, а int a, b, c - список-объявлений для
параметров.
Внешние объявления специфицируют характеристики объектов, функций и других
идентификаторов. Термин "внешний" здесь используется, чтобы подчеркнуть тот
факт, что объявления расположены вне функций; впрямую с ключевым словом
extern ("внешний") он не связан. Класс памяти для объекта с внешним
объявлением либо вообще не указывается, либо специфицируется как extern или
static.
В одной единице трансляции для одного идентификатора может содержаться
несколько внешних объявлений, если они согласуются друг с другом по типу и
способу связи и если для этого идентификатора существует не более одного
определения.
Два объявления объекта или функции считаются согласованными по типу в
соответствии с правилами, рассмотренными в A8.10. Кроме того, если объявления
отличаются лишь тем, что в одном из них тип структуры, объединения или
перечисления незавершен (A8.3), а в другом соответствующий ему тип с тем же
тегом завершен, то такие типы считаются согласованными. Если два типа массива
(A8.6.2) отличаются лишь тем, что один завершенный, а другой незавершенный, то
такие типы также считаются согласованными. Наконец, если один тип
специфицирует функцию старым способом, а другой - ту же функцию новым
способом (с объявлениями параметров), то такие типы также считаются
согласованными.
Если первое внешнее объявление функции или объекта помечено спецификатором
static, то объявленный идентификатор имеет внутреннюю связь; в противном
случае - внешнюю связь. Способы связей обсуждаются в A11.2.
Внешнее объявление объекта считается определением, если оно имеет
инициализатор. Внешнее объявление, в котором нет инициализатора и нет
спецификатора extern, считается пробным определением. Если в единице
трансляции появится определение объекта, то все его пробные определения просто
станут избыточными объявлениями. Если никакого определения для этого объекта
в единице трансляции не обнаружится, то все его пробные определения будут
трактоваться как одно определение с инициализатором 0.
Каждый объект должен иметь ровно одно определение. Для объекта с внутренней
связью это правило относится к каждой отдельной единице трансляции, поскольку
объекты с внутренними связями в каждой единице уникальны. В случае объектов с
внешними связями указанное правило действует в отношении всей программы в
целом.
Хотя правило одного определения формулируется несколько иначе, чем в первой
версии языка, по существу оно совпадает с прежним. Некоторые реализации его
ослабляют, более широко трактуя понятие пробного определения. В другом
варианте указанного правила, который распространен в системах UNIX и признан
как общепринятое расширение стандарта, все пробные определения объектов с
внешними связями из всех транслируемых единиц программы рассматриваются
вместе, а не отдельно в каждой единице. Если где-то в программе
обнаруживается определение, то пробные определения становятся просто
объявлениями, но, если никакого определения не встретилось, то все пробные
определения становятся одним-единственным определением с инициализатором 0.
Каждый раз компилировать всю программу целиком нет необходимости. Исходный
текст можно хранить в нескольких файлах, представляющих собой единицы
трансляции. Ранее скомпилированные программы могут загружаться из библиотек.
Связи между функциями программы могут осуществляться через вызовы и внешние
данные.
Следовательно, существуют два вида областей видимости: первая - это
лексическая область идентификатора: т. е. область в тексте программы, где
имеют смысл все его характеристики; вторая область - это область,
ассоциируемая с объектами и функциями, имеющими внешние связи,
устанавливаемые между идентификаторами из раздельно компилируемых единиц
трансляции.
Каждый идентификатор попадает в одно из нескольких пространств имен. Эти
пространства никак не связаны друг с другом. Один и тот же идентификатор
может использоваться в разных смыслах даже в одной области видимости, если он
принадлежит разным пространствам имен. Ниже через точку с запятой перечислены
классы объектов, имена которых представляют собой отдельные независимые
пространства: объекты, функции, typedef-имена и enum-константы; метки
инструкций; теги структур, объединений и перечислений; элементы каждой
отдельной структуры или объединения.
Сформулированные правила несколько отличаются от прежних, описанных в первом
издании. Метки инструкций не имели раньше собственного пространства; теги
структур и теги объединений (а в некоторых реализациях и теги перечислений)
имели отдельные пространства. Размещение тегов структур, объединений и
перечислений в одном общем пространстве - это дополнительное ограничение,
которого раньше не было. Наиболее существенное отклонение от первой редакции
в том, что каждая отдельная структура (или объединение) создает свое
собственное пространство имен для своих элементов. Таким образом, одно и то
же имя может использоваться в нескольких различных структурах. Это правило
широко применяется уже несколько лет.
Лексическая область видимости идентификатора объекта (или функции),
объявленного во внешнем объявлении, начинается с места, где заканчивается его
объявитель, и простирается до конца единицы трансляции, в которой он
объявлен. Область видимости параметра в определении функции начинается с
начала блока, представляющего собой тело функции, и распространяется на всю
функцию; область видимости параметра в описании функции заканчивается в конце
этого описания. Область видимости идентификатора, объявленного в начале
блока, начинается от места, где заканчивается его объявитель, и продолжается
до конца этого блока. Областью видимости метки является вся функция, где эта
метка встречается. Область видимости тега структуры, объединения или
перечисления начинается от его появления в спецификаторе типа и продолжается
до конца единицы трансляции для объявления внешнего уровня и до конца блока
для объявления внутри функции.
Если идентификатор явно объявлен в начале блока (в том числе тела функции),
то любое объявление того же идентификатора, находящееся снаружи этого блока,
временно перестает действовать вплоть до конца блока.
Если встречается несколько объявлений, имеющих одинаковый идентификатор и
описывающих объект (или функцию), то все эти объявления в случае внешней
связи относятся к одному объекту (функции) - уникальному для всей программы;
если же связь внутренняя, то свойство уникальности распространяется только на
единицу трансляции.
Как говорилось в A10.2, если первое внешнее объявление имеет спецификатор
static, то оно описывает идентификатор с внутренней связью, если такого
спецификатора нет, то - с внешней связью. Если объявление находится внутри
блока и не содержит extern, то соответствующий идентификатор ни с чем не
связан и уникален для данной функции. Если объявление содержит extern и блок
находится к области видимости внешнего объявления этого идентификатора, то
последний имеет ту же связь и относится к тому же объекту (функции). Однако
если ни одного внешнего объявления для этого идентификатора нет, то он имеет
внешнюю связь.
Препроцессор выполняет макроподстановку, условную компиляцию, включение
именованных файлов. Строки, начинающиеся со знака # (перед которым возможны
символы-разделители), устанавливают связь с препроцессором. Их синтаксис не
зависит от остальной части языка; они могут появляться где угодно и оказывать
влияние (независимо от области видимости) вплоть до конца транслируемой
единицы. Границы строк принимаются во внимание: каждая строка анализируется
отдельно (однако есть возможность "склеивать" строки, см. A12.2). Лексемами
для препроцессора являются все лексемы языка и последовательности символов,
задающие имена файлов, как, например, в директиве #include (A12.4). Кроме
того, любой символ, неопределенный каким-либо другим способом, воспринимается
как лексема. Влияние символов-разделителей, отличающихся от пробелов и
горизонтальных табуляций, внутри строк препроцессора не определено.
Само препроцессирование проистекает в нескольких логически последовательных
фазах. В отдельных реализациях некоторые фазы объединены.
- Трехзнаковые последовательности, описанные в A12.1, заменяются их
эквивалентами. Между строками вставляются символы новой строки, если того
требует операционная система.
- Выбрасываются пары символов, состоящие из обратной наклонной черты с
последующим символом новой строки; тем самым осуществляется "склеивание"
строк (A12.2).
- Программа разбивается на лексемы, разделенные символами-разделителями.
Комментарии заменяются единичными пробелами. Затем выполняются директивы
препроцессора и макроподстановки (A12.3-A12.10).
- Эскейп-последовательности в символьных константах и строковых литералах
(A2.5.2, A2.6) заменяются на символы, которые они обозначают. Соседние
строковые литералы конкатенируются.
- Результат транслируется. Затем устанавливаются связи с другими программами
и библиотеками посредством сбора необходимых программ и данных и соединения
ссылок на внешние функции и объекты с их определениями.
Множество символов, из которых набираются исходные Си-программы, основано на
семибитовом ASCII-коде. Однако он шире, чем инвариантный код символов ISO
646-1983 (ISO 646-1983 Invariant Code Set). Чтобы дать возможность
пользоваться сокращенным набором символов, все указанные ниже трехзнаковые
последовательности заменяются на соответствующие им единичные символы. Замена
осуществляется до любой иной обработки.
??= #
??( [
??< {
??/ \
??) ]
??> }
??' ^
??! |
??- ~
Никакие другие замены, кроме указанных, не делаются.
Трехзнаковые последовательности введены ANSI-стандартом.
Строка, заканчивающаяся обратной наклонной чертой, соединяется со следующей,
поскольку символ \ и следующий за ним символ новой строки выбрасываются. Это
делается перед "разбиением" текста на лексемы.
Управляющая строка вида
#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(хсat(1, 2), 3)
в итоге даст 123, поскольку сам xcat не использует оператора ##.
Аналогично сработает и ABSDIFF(ABSDIFF(a, b), c), и мы получим правильный
результат.
Управляющая строка
#include <имя-файла>
заменяется на содержимое файла с именем имя-файла. Среди символов,
составляющих имя-файла, не должно быть знака > и символа новой строки.
Результат не определен, если имя-файла содержит любой из символов ", ', \ или
пару символов /*. Порядок поиска указанного файла зависит от реализации.
Подобным же образом выполняется управляющая строка
#include "имя-файла"
Сначала поиск осуществляется по тем же правилам, по каким компилятор ищет
первоначальный исходный файл (механизм этого поиска зависит от реализации), а
в случае неудачи осуществляется методом поиска, принятым в #include первого
типа. Результат остается неопределенным, если имя файла содержит ", \ или /*;
использование знака > разрешается.
Наконец, директива
#include последовательность-лексем
не совпадающая ни с одной из предыдущих форм, рассматривает
последовательность лексем как текст, который в результате всех
макроподстановок должен дать #include <...> или #include "...".
Сгенерированная таким образом директива далее будет интерпретироваться в
соответствии с полученной формой.
Файлы, вставляемые с помощью #include, сами могут содержать в себе директивы
#include.
Части программы могут компилироваться условно, если они оформлены в
соответствии со следующим схематично изображенным синтаксисом:
условная - конструкция -препроцессора:
if-строка текст elif-части else-частьнеоб #endif
if-строка:
#if константное-выражение
#ifdef идентификатор
#ifndef идентификатор
elif-части:
elif-строка текст
elif-частинеоб
elif-строка:
#elif константное-выражение
else-часть:
else-строка текст
else-строка:
#else
Каждая из директив (if-строка, elif-строка, else-строка и #endif)
записывается на отдельной строке. Константные выражения в #if и последующих
строках #elif вычисляются по порядку, пока не обнаружится выражение с
ненулевым (истинным) значением; текст, следующий за строкой с нулевым
значением, выбрасывается. Текст, расположенный за директивой с ненулевым
значением, обрабатывается обычным образом. Под словом "текст" здесь имеется в
виду любая последовательность строк, включая строки препроцессора, которые не
являются частью условной структуры; текст может быть и пустым. Если строка
#if или #elif с ненулевым значением выражения найдена и ее текст обработан,
то последующие строки #elif и #else вместе со своими текстами выбрасываются.
Если все выражения имеют нулевые значения и присутствует строка #else, то
следующий за ней текст обрабатывается обычным образом. Тексты "неактивных"
ветвей условных конструкций, за исключением тех, которые заведуют
вложенностью условных конструкций, игнорируются.
Константные выражения в #if и #elif являются объектами для обычной
макроподстановки. Более того, прежде чем просматривать выражения вида
defined идентификатор
и
defined ( идентификатор )
на предмет наличия в них макровызова, они заменяются на 1L или 0L в
зависимости от того, был или не был определен препроцессором указанный в них
идентификатор. Все идентификаторы, оставшиеся после макрорасширения,
заменяются на 0L. Наконец, предполагается, что любая целая константа всегда
имеет суффикс L, т. е. вся арифметика имеет дело с операндами только типа
long или unsigned long.
Константное выражение (A7.19) здесь используется с ограничениями: оно должно
быть целочисленным, не может содержать в себе перечислимых констант,
преобразований типа и операторов sizeof.
Управляющие строки
#ifdef идентификатор
#ifndef идентификатор
эквивалентны соответственно строкам
#if defined идентификатор
#if !defined идентификатор
Строки #elif не было в первой версии языка, хотя она и использовалась в
некоторых препроцессорах. Оператор препроцессора defined - также новый.
Для удобства работы с другими препроцессорами, генерирующими Си-программы,
можно использовать одну из следующих директив:
#line константа "имя-файла"
#line константа
Эти директивы предписывают компилятору считать, что указанные десятичное
целое и идентификатор являются номером следующей строки и именем текущего
файла соответственно. Если имя файла отсутствует, то ранее запомненное имя не
изменяется. Расширения макровызовов в директиве #line выполняются до
интерпретации последней.
Строка препроцессора вида
#error последовательность-лексемнеоб
приказывает ему выдать диагностическое сообщение, включающее заданную
последовательность лексем.
Управляющая строка вида
#pragma последовательность-лексемнеоб
призывает препроцессор выполнить зависящие от реализации действия.
Неопознанная прагма игнорируется.
Строка препроцессора вида
#
не вызывает никаких действий.
Препроцессор "понимает" несколько заранее определенных идентификаторов; их
он заменяет специальной информацией. Эти идентификаторы (и оператор
препроцессора defined в том числе) нельзя повторно переопределять, к ним
нельзя также применять директиву #undef. Это следующие идентификаторы:
__LINE__ Номер текущей строки исходного текста, десятичная константа.
__FILE__ Имя компилируемого файла, строка.
__DATE__ Дата компиляции в виде "MMM DD YYYY",строка.
__TIME__ Время компиляции в виде "hh:mm:ss", строка.
__STDC__ Константа 1. Предполагается, что этот идентификатор определен как 1
только в тех реализациях, которые следуют стандарту.
Строки #error и #pragma впервые введены ANSI-стандартом. Заранее
определенные макросы препроцессора также до сих пор не описывались, хотя и
использовались в некоторых реализациях.
Ниже приведены грамматические правила, которые мы уже рассматривали в данном
приложении. Они имеют то же содержание, но даны в ином порядке.
Здесь не приводятся определения следующих символов-терминов: целая-константа,
символьная-константа, константа-с-плавающей-точкой, идентификатор, строка и
константа-перечисление. Слова, набранные обычным латинским шрифтом (не
курсивом), и знаки рассматриваются как символы-термины и используются точно в
том виде, как записаны. Данную грамматику можно механически трансформировать
в текст, понятный системе автоматической генерации грамматического
распознавателя. Для этого помимо добавления некоторых синтаксических пометок,
предназначенных для указания альтернативных продукций, потребуется
расшифровка конструкции со словами "один из" и дублирование каждой продукции,
использующей символ с индексом необ., причем один вариант продукции должен
быть написан с этим символом, а другой - без него. С одним изменением, а
именно - удалением продукции typedef-имя:идентификатор и объявлением typedef-имени
символом-термином, данная грамматика будет понятна генератору
грамматического распознавателя YACC. Ей присуще лишь одно противоречие,
вызываемое неоднозначностью конструкции if-else.
единица–трансляции:
внешнее-объявление
единица-трансляции внешнее-объявление
внешнее-объявление:
определение-функции
объявление
определение функции:
спецификаторы-объявлениянеоб объявитель
список-объявленийнеоб составная-инструкция
объявление:
спецификаторы-объявления список-инициализаторов-объявителейнеоб
список-объявлений:
объявление
список-объявлений объявление
спецификаторы-объявления:
спецификатор-класса-памяти спецификаторы-объявлениянеоб
спецификатор-типа спецификаторы-объявлениянеоб
квалификатор-типа спецификаторы-объявлениянеоб
спецификатор-класса-памяти: один из
auto register static extern typedef
спецификатор-типа: один из
void char short int long float double signed unsigned
спецификатор-структуры-или-объединения
спецификатор-перечисления
typedef-имя
квалификатор-типа: один из
const volatile
спецификатор-структуры-или-объединения:
структуры-или-объединения-идентификаторнеоб { список-объявлений-структуры }
структуры-или-объединения идентификатор
структура-или-объединение: одно из
struct union
список-объявлений-структуры:
объявление-структуры
список-объявлений-структуры объявление-структуры
список-объявителей-ииициализаторов:
объявитель-инициализатор
список-объявителей-инициализаторов , объявитель-инициализатор
объявитель-инициализатор:
объявитель
объявитель = инициализатор
объявление-структуры:
список-спецификаторов-квалификаторов список-объявителей-структуры
список-спецификаторов-квалификаторов:
спецификатор-типа список-спецификаторов-квалификаторовнеоб
квалификатор-типа список-спецификаторов-квалификаторовнеоб
список-структуры-объявителей:
структуры–объявитель
список-структуры-объявителей , структуры-объявитель
структуры-объявитель:
объявитель
объявительнеоб : константное-выражение
спецификатор-перечисления:
enum идентификаторнеоб { список-перечислителей }
enum идентификатор
список-перечислителей:
перечислитель
список-перечислителей перечислитель
перечислитель:
идентификатор
указательнеоб собственно-объявитель
собственно-объявитель:
идентификатор
( объявитель )
собственно-объявитель [ константное-выражениенеоб ]
собственно-объявитель ( список-типов-параметров )
собственно-объявитель ( список-идентификаторовнеоб )
указатель:
* список~квалификаторов-типанеоб
* список-квалификаторов-типанеоб указатель
список-квалификаторов-типа:
квалификатор-типа
список-квалификаторов-типа квалификатор-типа
список-типов-параметров:
список-параметров
список-параметров , ...
список-параметров:
объявление-параметра
список-параметров , объявление-параметра
объявление-параметра:
спецификаторы-объявления объявитель
спецификаторы-объявления абстрактный-объявительнеоб
список-идентификаторов:
идентификатор
список-идентификаторов , идентификатор
инициализатор:
выражение-присваивания
{ список-инициализаторов }
{ список-инициализаторов, }
список-инициализаторов:
инициализатор
список-инициализаторов , инициализатор
имя-типа:
список-спецификаторое-квалификаторов абстрактный-объявительнеоб
абстрактный-объявитель:
указатель
указательнеоб собственно-абстрактный-объявитель
собственно-абстрактный-объявитель:
( абстрактный-объявитель )
собственно-абстрактный-объявительнеоб [константное-выражениенеоб]
собственно-абстрактный-объявительнеоб (список-типов-параметровнеоб)
typedef-имя:
идентификатор
инструкция:
помеченная-инструкция
инструкция–выражение
составная-инструкция
инструкция-выбора
циклическая-инструкция
инструкция-перехода
помеченная-инструкция:
идентификатор : инструкция
case константное-выражение : инструкция
default : инструкция
инструкция-выражение:
выражениенеоб;
составная-инструкция:
( список-объявленийнеоб список-инструкцийнеоб)
список-инструкций:
инструкция
список-инструкций инструкция
инструкция-выбора:
if ( выражение ) инструкция
if ( выражение ) инструкция else инструкция
switch ( выражение ) инструкция
циклическая-инструкция:
while ( выражение ) инструкция
do инструкция while ( выражение )
return выражениенеоб;
выражение:
выражение-присваивания
выражение , выражение-присваивания
выражение-присваивания:
условное-выражение
унарное-выражение оператор-присваивания выражение-присваивания
оператор-присваивания: один из
= *= /= %= += -= <<= >>= &= ^= |=
условное-выражение:
логическое-ИЛИ-выражение
логическое-ИЛИ-выражение ? выражение : условное-выражение
константное-выражение:
условное-выражение
логическое-ИЛИ-выражение:
логическое-И-выражение
логическое-ИЛИ-выражение || логическое-И-выражение
логическое-И-выражение:
ИЛИ-выражение
логическое-И-выражение && ИЛИ-выражение
ИЛИ-выражение:
исключающее-ИЛИ-выражение
ИЛИ-выражение | исключающее-ИЛИ-выражение
исключающее-ИЛИ-выражение:
И-выражение
исключающее-ИЛИ-выражение ^ И-выражение
И-выражение:
выражение-равенства
И-выражение & выражение-равенства
выражение-равенства:
выражение-отношения
выражение-равенства == выражение-отношения
выражение-равенства != выражение-отношения
выражение-отношения:
сдвиговое-выражение
выражение-отношения < сдвиговое-выражение
выражение-отношения > сдвиговое-выражение
выражение-отношения <= сдвиговое-выражение
выражение-отношения >= сдвиговое-выражение
сдвиговое-выражение:
аддитивное-выражение
сдвиговое-выражение >> аддитивное-выражение
сдвиговое-выражение << аддитивное-выражение
аддитивное-выражение:
мультипликативное-выражение
аддитивное-выражение + мультипликативное-выражение
аддитивное-выражение - мультипликативное-выражение
мультипликативное-выражение:
выражение-приведенное-к-типу
мультипликативное-выражение * выражение-приведенное-к-типу
мультипликативное-выражение / выражение-приведенное-к-типу
мультипликативное-выражение % выражение-приведенное-к-типу
выражение-приведенное-к-типу:
унарное-выражение
( имя-типа ) выражение-приведенное-к-типу
унарное-выражение:
постфиксное –выражение
++ унарное-выражение
-- унарное-выражение
унарный-оператор выражение-приведенное-к-типу
sizeof унарное-выражение
sizeof( имя-типа )
унарный-оператор: один из
& * + - ~ !
постфиксное-выражение:
первичное-выражение
постфиксное-выражение [ выражение ]
постфиксное-выражение ( список-аргументов-выраженийнеоб )
постфиксное-выражение , идентификатор
постфиксное-выражение -> идентификатор
постфиксное-выражение ++
постфиксное-выражение –
первичное -выражение:
идентификатор
константа
строка
( выражение )
список-аргументов-выражений:
выражение-присваивания
список-аргументов-выражений , выражение-присваивания
константа:
целая-константа
символьная-константа
константа-с-плавающей-точкой
константа-перечисление
Ниже приводится грамматика языка препроцессора в виде перечня структур
управляющих строк. Для механического получения программы грамматического
разбора она не годится. Грамматика включает символ текст, который означает
текст обычной программы, безусловные управляющие строки препроцессора и его
законченные условные конструкции.
управляющая-строка:
#define идентификатор последовательность-лексем
#define идентификатор ( идентификатор, ..., идентификатор) последовательность-лексем
#undef идентификатор
#include <имя-файла>
#include "имя-файла"
#include последовательность-лексем
#line константа "идентификатор"
#line константа
#error последовательность-лексемнеоб
#pragma последовательность-лексемнеоб
#
условная-конструкция-препроцессора
условная-конструкция-препроцессора:
if-строка текст elif-части else-частьнеоб #endif
if-строка:
#if константное-выражение
#ifdef идентификатор
#ifndef идентификатор
elif-части:
elif-строка текст
elif-частинеоб
elif-строка:
#elif константное-выражение
else-часть:
else-строка текст
else-строка:
#else
Приложение B. Стандартная библиотека
B1. Ввод-вывод: <stdio.h>
B1.1. Операции над файлами
B1.2. Форматный вывод
B1.3. Форматный ввод
B1.4. Функции ввода-вывода символов
B1.5. Функции прямого ввода-вывода
B1.6. Функции позиционирования файла
B1.7. Функции обработки ошибок
B2. Проверки класса символа: <ctype.h>
B3. Функции, оперирующие со строками: <string.h>
B4. Математические функции: <math.h>
B5. Функции общего назначения: <stdlib.h>
B6. Диагностика: <assert.h>
B7. Списки аргументов переменной длины: <stdarg.h>
B8. Дальние переходы: <setjmp.h>
B9. Сигналы: <signal.h>
B10. Функции даты и времени: <time.h>
B11. Зависящие от реализации пределы: <limits.h> и <float.h>
Настоящее приложение представляет собой краткое изложение библиотеки,
утвержденной в качестве ANSI-стандарта. Сама по себе библиотека не является
частью языка, однако, заложенный в ней набор функций, а также определений
типов и макросов составляет системную среду, поддерживающую стандарт Си. Мы
не приводим здесь несколько функций с ограниченной областью применения – те,
которые легко синтезируются из других функций, а также опускаем все то, что
касается многобайтовых символов и специфики, обусловленной языком,
национальными особенностями и культурой.
Функции, типы и макросы объявляются в следующих стандартных заголовочных
файлах:
<assert.h>
<ctype.h>
<errno.h>
<float.h>
<limits.h>
<locale.h>
<math.h>
<setjmp.h>
<signal.h>
<stdarg.h>
<stddef.h>
<stdio.h>
<stdlib.h>
<string.h>
<time.h>
Доступ к заголовочному файлу осуществляется с помощью строки препроцессора
#include <заголовочный файл>
Заголовочные файлы можно включать в любом порядке и сколько угодно раз.
Строка #include не должна быть внутри внешнего объявления или определения и
должна встретиться раньше, чем что-нибудь из включаемого заголовочного файла
будет востребовано. В конкретной реализации заголовочный файл может и не
быть исходным файлом.
Внешние идентификаторы, начинающиеся со знака подчеркивания, а также все
другие идентификаторы, начинающиеся с двух знаков подчеркивания или с
подчеркивания и заглавной буквы, зарезервированы для использования в
библиотеке.
B1. Ввод-вывод: <stdio.h>
Определенные в <stdio.h> функции ввода-вывода, а также типы и макросы
составляют приблизительно одну треть библиотеки.
Поток - это источник или получатель данных; его можно связать с диском или с
каким-то другим внешним устройством. Библиотека поддерживает два вида
потоков: текстовый и бинарный, хотя на некоторых системах, в частности в
UNIXe, они не различаются. Текстовый поток - это последовательность строк;
каждая строка имеет нуль или более символов и заканчивается символом '\n'.
Операционная среда может потребовать коррекции текстового потока (например,
перевода '\n' в символы возврат-каретки и перевод-строки).
Бинарный поток - это последовательность непреобразованных байтов,
представляющих собой некоторые промежуточные данные, которые обладают тем
свойством, что если их записать, а затем прочесть той же системой ввода-
вывода, то мы получим информацию, совпадающую с исходной.
Поток соединяется с файлом или устройством посредством его открытия,
указанная связь разрывается путем закрытия потока. Открытие файла возвращает
указатель на объект типа FILE, который содержит всю информацию, необходимую
для управления этим потоком. Если не возникает двусмысленности, мы будем
пользоваться терминами "файловый указатель" и "поток" как равнозначными.
Когда программа начинает работу, уже открыты три потока: stdin, stdout и
stderr.
B1.1. Операции над файлами
Ниже перечислены функции, оперирующие с файлами. Тип size_t - беззнаковый
целочисленный тип, используемый для описания результата оператора sizeof.
FILE *fopen(const char *filename, const char *mode);
fopen открывает файл с заданным именем и возвращает поток или NULL, если
попытка открытия оказалась неудачной. Режим mode допускает следующие
значения:
"r" |
- текстовый файл открывается для чтения (от read (англ.) - читать); |
"w" |
- текстовый файл создается для записи; старое содержимое
(если оно было) выбрасывается (от write (англ.) - писать); |
"а" | - текстовый файл открывается или создается для записи в
конец файла (от append (англ.) - добавлять); |
"r+" | - текстовый файл открывается для исправления (т. е. для
чтения и для записи); |
"w+" | - текстовый файл создается для исправления; старое
содержимое (если оно было) выбрасывается; |
"a+" | - текстовый файл открывается или создается для
исправления уже существующей информации и добавления новой в конец файла.
|
Режим "исправления" позволяет читать и писать в один и тот же файл; при
переходах от операций чтения к операциям записи и обратно должны
осуществляться обращения к fflush или к функции позиционирования файла. Если
указатель режима дополнить буквой b (например "rb" или "w+b"), то это будет
означать, что файл бинарный. Ограничение на длину имени файла задано
константой FILENAME_MAX. Константа FOPEN_MAX ограничивает число одновременно
открытых файлов.
FILE *freopen(const char *filename, const char *mode, FILE *stream);
freopen открывает файл с указанным режимом и связывает его с потоком stream.
Она возвращает stream или, в случае ошибки, NULL. Обычно freopen используется
для замены файлов, связанных с stdin, stdout или stderr, другими файлами.
int fflush(FILE *stream);
Применяемая к потоку вывода функция fflush производит дозапись всех
оставшихся в буфере (еще не записанных) данных, для потока ввода эта функция
не определена. Возвращает EOF в случае возникшей при записи ошибки или нуль в
противном случае. Обращение вида fflush(NULL) выполняет указанные операции
для всех потоков вывода.
int fclose(FILE *stream);
fсlose производит дозапись еще не записанных буферизованных данных,
сбрасывает несчитанный буферизованный ввод, освобождает все автоматически
запрошенные буфера, после чего закрывает поток. Возвращает EOF в случае
ошибки и нуль в противном случае.
int remove(const char *filename);
remove удаляет файл с указанным именем; последующая попытка открыть файл с
этим именем вызовет ошибку. Возвращает ненулевое значение в случае неудачной
попытки.
int rename(const char *oldname, const char *newname);
rename заменяет имя файла; возвращает ненулевое значение в случае, если
попытка изменить имя оказалась неудачной. Первый параметр задает старое имя,
второй - новое.
FILE *tmpfile(void);
tmpfile создает временный файл с режимом доступа "wb+", который автоматически
удаляется при его закрытии или обычном завершении программой своей работы.
Эта функция возвращает поток или, если не смогла создать файл, NULL.
char *tmpnam(char s[L_tmpnam]);
tmpnam(NULL) создает строку, не совпадающую ни с одним из имен существующих
файлов, и возвращает указатель на внутренний статический массив. tmpnam(s)
запоминает строку в s и возвращает ее в качестве значения функции; длина s
должна быть не менее L_tmpnam. При каждом вызове tmpnam генерируется новое
имя; при этом гарантируется не более TMPMAX различных имен за один сеанс
работы программы. Заметим, что tmpnam создает имя, а не файл.
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
setvbuf управляет буферизацией потока; к ней следует обращаться прежде, чем
будет выполняться чтение, запись или какая-либо другая операция, mode со
значением _IOFBF вызывает полную буферизацию, с _IOLBF - "построчную"
буферизацию текстового файла, a mode со значением _IONBF отменяет всякую
буферизацию. Если параметр buf не есть NULL, то его значение - указатель на
буфер, в противном случае под буфер будет запрашиваться память. Параметр size
задает размер буфера. Функция setvbuf в случае ошибки выдает ненулевое
значение.
void setbuf(FILE *stream, char *buf);
Если buf есть NULL, то для потока stream буферизация выключается. В противном
случае вызов setbuf приведет к тем же действиям, что и вызов
(void) setvbuf (stream, buf, _IOFBF, BUFSIZ).
B1.2. Форматный вывод
Функции printf осуществляют вывод информации по формату.
int fprintf(FILE *stream, const char *format, ...);
fprintf преобразует и пишет вывод в поток stream под управлением format.
Возвращаемое значение - число записанных символов или, в случае ошибки,
отрицательное значение.
Форматная строка содержит два вида объектов: обычные символы, копируемые в
выводной поток, и спецификации преобразования, которые вызывают
преобразование и печать остальных аргументов в том порядке, как они
перечислены. Каждая спецификация преобразования начинается с % и
заканчивается символом-спецификатором преобразования. Между % и символом-
спецификатором в порядке, в котором они здесь перечислены, могут быть
расположены следующие элементы информации:
Флаги (в любом порядке), модифицирующие спецификацию:
- |
- указывает на то, что преобразованный аргумент
должен быть прижат к левому краю поля; |
+ |
- предписывает печатать число всегда со знаком;
|
пробел |
- если первый символ - не знак, то числу должен
предшествовать пробел; |
0 |
- указывает, что числа должны дополняться слева
нулями до всей ширины поля; |
# |
- указывает на одну из следующих форм вывода: для o
первой цифрой должен быть 0; для x или X ненулевому
результату должны предшествовать 0x или 0X; для e, E, f,
g и G вывод должен обязательно содержать десятичную
точку; для g и G завершающие нули не отбрасываются.
|
Число, специфицирующее минимальную ширину поля. Преобразованный аргумент
будет напечатан в поле, размер которого не меньше указанной ширины, а если
потребуется, в поле большего размера. Если число символов преобразованного
аргумента меньше ширины поля, то поле будет дополнено слева (или справа, если
число прижимается к левому краю). Обычно поле дополняется пробелами (или
нулями, если присутствует флаг дополнения нулями).
Точка, отделяющая указатель ширины поля от указателя точности.
Число, задающее точность, которое специфицирует максимальное количество
символов, печатаемых из строки, или количество цифр после десятичной точки в
преобразованиях e, E или f, или количество значащих цифр для g или G -
преобразования, или минимальное количество цифр при печати целого (до
необходимой ширины поля число дополняется слева нулями).
Модификаторы h, l (буква ell) или L. "h" указывает на то, что
соответствующий аргумент должен печататься как short или unsigned short; "l"
сообщает, что аргумент имеет тип long или unsigned long; "L" информирует, что
аргумент принадлежит типу long double.
Ширина, или точность, или обе эти характеристики могут быть специфицированы с
помощью *; в этом случае необходимое число "извлекается" из следующего
аргумента, который должен иметь тип int (в случае двух звездочек используются
два аргумента).
Символы-спецификаторы и разъяснение их смысла приведены в таблице B-1. Если
за % нет правильного символа-спецификатора, результат не определен.
int printf(const char *format, ...);
printf(...) полностью эквивалентна fprintf(stdout, ...).
int sprintf(char *s, const char *format, ...)
sprintf действует так же, как и printf, только вывод осуществляет в строку s,
завершая ее символом '\0'. Строка s должна быть достаточно большой, чтобы
вмещать результат вывода. Возвращает количество записанных символов, в число
которых символ '\0' не входит.
int vprintf (const char *format, va_list arg)
int vfprintf (FILE *stream, const char *format, va_list arg)
int vsprintf (char *s, const char *format, va_list arg)
Функции vprintf, vfprintf и vsprintf эквивалентны соответствующим printf-
функциям с той лишь разницей, что переменный список аргументов представлен
параметром arg, инициализированным макросом va_start и, возможно, вызовами
va_arg (см. в B7 описание <stdarg.h>).
Таблица B-1. Преобразования printf
Символ |
Тип аргумента; вид печати |
d, i |
int; знаковая десятичная запись |
o |
unsigned int; беззнаковая восьмеричная запись (без 0 слева)
|
x, X |
unsigned int; беззнаковая шестнадцатеричная запись
(без 0x или 0X слева), в качестве цифр от 10 до 15
используются abcdef для x и ABCDEF для X |
u |
unsigned int; беззнаковое десятичное целое |
c |
int; единичный символ после преобразования в unsigned
char |
s |
char *; символы строки печатаются, пока не встретится
'\0' или не исчерпается количество символов, указанное
точностью |
f |
double; десятичная запись вида [-]mmm.ddd, где
количество d специфицируется точностью. По умолчанию
точность равна 6; нулевая точность подавляет печать
десятичной точки |
e, E |
double; десятичная запись вида [-]m.dddddde±xx или
запись вида [-]m.ddddddE±xx, где количество d
специфицируется точностью. По умолчанию точность равна 6;
нулевая точность подавляет печать десятичной
точки |
g, G |
double; используется %e и %E, если порядок меньше -4
или больше или равен точности; в противном случае
используется %f. Завершающие нули и точка в конце не
печатаются |
p |
void *; печатает в виде указателя (представление
зависит от реализации |
n |
int *; число символов, напечатанных к данному моменту
данным вызовом printf, записывается в аргумент. Никакие
другие аргументы не преобразуются |
% |
никакие аргументы не преобразуются; печатается % |
B1.3. Форматный ввод
Функции scanf имеют дело с форматным преобразованием при вводе
int fscanf(FILE *stream, const char *format, ...);
fscanf читает данные из потока stream под управлением format и
преобразованные величины присваивает по порядку аргументам, каждый из которых
должен быть указателем. Завершает работу, если исчерпался формат. Выдает EOF
по исчерпании файла или перед любым преобразованием, если возникла ошибка; в
остальных случаях функция возвращает количество преобразованных и введенных
элементов.
Форматная строка обычно содержит спецификации преобразования, которые
используются для управления вводом. В форматную строку могут входить:
пробелы и табуляции, которые игнорируются;
обычные символы (кроме %), которые ожидаются в потоке ввода среди символов, отличных от символов-разделителей;
спецификации преобразования, состоящие из %; необязательного знака *,
подавляющего присваивание; необязательного числа, специфицирующего
максимальную ширину поля; необязательных h, l или L, указывающих размер
присваиваемого значения, и символа-спецификатора преобразования.
Спецификация преобразования определяет преобразование следующего поля ввода.
Обычно результат размещается в переменной, на которую указывает
соответствующий аргумент. Однако если присваивание подавляется с помощью
знака *, как, например, в %*s, то поле ввода просто пропускается, и никакого
присваивания не происходят. Поле ввода определяется как строка символов,
отличных от символов-разделителей; при этом ввод строки прекращается при
выполнении любого из двух условий: если встретился символ-разделитель или
если ширина поля (в случае, когда она указана) исчерпана. Из этого следует,
что при переходе к следующему полю scanf может "перешагивать" через границы
строк, поскольку символ новой строки является символом-разделителем. (Под
символами-разделителями понимаются символы пробела, табуляции, новой строки,
возврата каретки, вертикальной табуляции и смены страницы.)
Символ-спецификатор указывает на способ интерпретации поля ввода.
Соответствующий аргумент должен быть указателем. Список допустимых символов-спецификаторов приводится в таблице B-2.
Символам-спецификаторам d, i, n, o, u и x может предшествовать h, если
аргумент есть указатель на short (а не int) или l (буква ell), если аргумент
есть указатель на long. Символам-спецификаторам e, f и g может предшествовать
l, если аргумент - указатель на double (а не float), или L, если аргумент -
указатель на long double.
int scanf (const char *format, ...);
scanf(...) делает то же, что и fscanf(stdin, ...).
int sscanf (const char *s, const char *format, ...);
sscanf(s, ...) делает то же, что и scanf(...), только ввод символов
осуществляет из строки s.
Таблица B-2. Преобразования scanf
Символ |
Данные на вводе; тип аргумента |
d |
десятичное целое; int * |
i |
целое: int *. Целое может быть восьмеричным (с нулем
слева) или шестнадцатеричным (с 0x или 0X слева)
|
o |
восьмеричное целое (с нулем слева или без него); int * |
u |
беззнаковое десятичное целое; unsigned int * |
x |
шестнадцатеричное целое (с 0x или 0X слева или без них): int * |
c |
символы, char *. Символы ввода размещаются в
указанном массиве в количестве, заданном шириной поля; по
умолчанию это количество равно 1. Символ'\0' не
добавляется. Символы-разделители здесь рассматриваются
как обычные символы и поступают в аргумент. Чтобы
прочесть следующий символ-разделитель, используйте %1s |
s |
строка символов, отличных от символов-разделителей
(записывается без кавычек); char *, указывающий на массив
размера достаточного, чтобы вместить строку и добавляемый
к ней символ '\0' |
e, f, g |
число с плавающей точкой; float *. Формат ввода для
float состоит из необязательного знака, строки цифр,
возможно с десятичной точкой, и необязательного порядка,
состоящего из E или e и целого, возможно со
знаком |
p |
значение указателя в виде, в котором printf ("%р")
его напечатает; void * |
n |
записывает в аргумент число символов, прочитанных к
этому моменту в этом вызове; int *. Никакого чтения ввода
не происходит. Счетчик числа введенных элементов не
увеличивается |
[...] |
выбирает из ввода самую длинную непустую строку,
состоящую из символов, заданных в квадратных скобках:
char *. В конец строки добавляется '\0'. Спецификатор
вида []...] включает ] в задаваемое множество
символов |
[^...] |
выбирает из ввода самую длинную непустую строку,
состоящую из символов, не входящих в заданное в скобках
множество. В конец добавляется '\0'. Спецификатор вида
[^]...] включает ] в задаваемое множество
символов |
% |
обычный символ %; присваивание не делается |
B1.4. Функции ввода-вывода символов
int fgetc(FILE *stream);
fgetc возвращает следующий символ из потока stream в виде unsigned char
(переведенную в int) или EOF, если исчерпан файл или обнаружена ошибка.
char *fgets(char *s, int n, FILE *stream);
fgets читает не более n-1 символов в массив s, прекращая чтение, если
встретился символ новой строки, который включается в массив; кроме того,
записывает в массив '\0'. Функция fgets возвращает s или, если исчерпан файл
или обнаружена ошибка, NULL.
int fputc(int с, FILE *stream);
fputc пишет символ c (переведенный в unsigned char) в stream. Возвращает
записанный символ или EOF в случае ошибки.
int fputs(const char *s, FILE *stream);
fputs пишет строку s (которая может не иметь '\n' ) в stream; возвращает
неотрицательное целое или EOF в случае ошибки.
int getc(FILE *stream);
getc делает то же, что и fgetc, но в отличие от последней, если она - макрос,
stream может браться более одного раза.
int getchar(void);
getchar() делает то же, что getc(stdin).
char *gets(char *s);
gets читает следующую строку ввода в массив s, заменяя символ новой строки на
'\0'. Возвращает s или, если исчерпан файл или обнаружена ошибка, NULL.
int putc(int с, FILE *stream);
putc делает то же, что и fputc, но в отличие от последней, если putc -
макрос, значение stream может браться более одного раза.
int putchar(int c);
putchar(c) делает тоже, что putc(c, stdout).
int puts(const char *s);
puts пишет строку s и символ новой строки в stdout. Возвращает EOF в случае
ошибки, или неотрицательное значение, если запись прошла нормально.
int ungetc(int с, FILE *stream);
ungetc отправляет символ c (переведенный в unsigned char) обратно в stream;
при следующем чтении из stream он будет получен снова. Для каждого потока
вернуть можно не более одного символа. Нельзя возвращать EOF. В качестве
результата ungetc выдает отправленный назад символ или, в случае ошибки, EOF.
B1.5. Функции прямого ввода-вывода
size_t fread(void *ptr, size_t size, size_t nobj, FILE *stream);
fread читает из потока stream в массив ptr не более nobj объектов размера
size. Она возвращает количество прочитанных объектов, которое может быть
меньше заявленного. Для индикации состояния после чтения следует использовать
feof и ferror.
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *stream);
fwrite пишет из массива ptr в stream nobj объектов размера size; возвращает
число записанных объектов, которое в случае ошибки меньше nobj.
B1.6. Функции позиционирования файла
int fseek(FILE *stream, long offset, int origin);
fseek устанавливает позицию для stream; последующее чтение или запись будет
производиться с этой позиции. В случае бинарного файла позиция
устанавливается со смещением offset - относительно начала, если origin равен
SEEK_SET; относительно текущей позиции, если origin равен SEEK_CUR; и
относительно конца файла, если origin равен SEEK_END. Для текстового файла
offset должен быть нулем или значением, полученным с помощью вызова функции
ftell. При работе с текстовым файлом origin всегда должен быть равен
SEEK_SET.
long ftell(FILE *stream);
ftell возвращает текущую позицию потока stream или -1L, в случае ошибки.
void rewind(FILE *stream);
rewind(fp) делает то же, что и fseek(fp, 0L, SEEK_SET); clearerr(fp).
int fgetpos(FILE *stream, fpos_t *ptr);
fgetpos записывает текущую позицию потока stream в *ptr для последующего
использования ее в fsetpos. Тип fpos_t позволяет хранить такого рода
значения, В случае ошибки fgetpos возвращает ненулевое значение.
int fsetpos(FILE *stream, const fpos_t *ptr);
fsetpos устанавливает позицию в stream, читая ее из *ptr, куда она была
записана ранее с помощью fgetpos. В случае ошибки fsetpos возвращает
ненулевое значение.
B1.7. Функции обработки ошибок
Многие функции библиотеки в случае ошибки или конца файла устанавливают
индикаторы состояния. Эти индикаторы можно проверять и изменять. Кроме того,
целое выражение errno (объявленное в <errno.h>) может содержать номер
ошибки, который дает дополнительную информацию о последней из обнаруженных
ошибок.
void clearerr(FILE *stream);
clearerr очищает индикаторы конца файла и ошибки потока stream.
int feof(FILE *stream);
feof возвращает ненулевое значение, если для потока stream установлен
индикатор конца файла.
int ferror(FILE *stream);
ferror возвращает ненулевое значение, если для потока stream установлен
индикатор ошибки.
void perror(const char *s);
perror(s) печатает s и зависимое от реализации сообщение об ошибке,
соответствующее целому значению в errno, т. е. делает то же, что и обращение
к функции fprintf вида
fprintf(stderr, "%s: %s\n", s, "сообщение об ошибке")
См. strerror в параграфе B3.
B2. Проверки класса символа: <ctype.h>
Заголовочный файл <ctype.h> объявляет функции, предназначенные для
проверок символов. Аргумент каждой из них имеет тип int и должен либо
представлять собой EOF, либо быть значением unsigned char, приведенным к int;
возвращаемое значение тоже имеет тип int. Функции возвращают ненулевое
значение ("истина"), когда аргумент c удовлетворяет описанному условию или
принадлежит указанному классу символов, и нуль в противном случае.
isalnum(c) |
isalpha(c) или isdigit(c) есть истина |
isalpha(c)
|
isupper(c) или islower(c) есть истина |
iscntrl(c)
|
управляющий символ |
isdigit(c) |
десятичная цифра |
isgraph(c) |
печатаемый символ кроме пробела |
islower(c) |
буква нижнего регистра |
isprint(c) |
печатаемый символ, включая пробел |
ispunct(c) |
печатаемый символ кроме пробела, буквы или цифры |
isspace(c) |
пробел, смена страницы, новая строка, возврат каретки, табуляция, вертикальная табуляция |
isupper(c) |
буква верхнего регистра |
isxdigit(c) | шестнадцатеричная цифра |
В наборе семибитовых ASCII-символов печатаемые символы находятся в диапазоне
от 0x20 (' ') до 0x7E ('~'); управляющие символы - от 0 (NUL) до 0x1F (US) и
0x7F (DEL).
Помимо перечисленных есть две функции, приводящие буквы к одному из
регистров:
int tolower(int c) – переводит c на нижний регистр;
int toupper(int c) - переводит c на верхний регистр.
Если c - буква на верхнем регистре, то tolower(c) выдаст эту букву на нижнем
регистре; в противном случае она вернет c. Если c - буква на нижнем регистре,
то toupper(c) выдаст эту букву на верхнем регистре; в противном случае она
вернет c.
B3. Функции, оперирующие со строками: <string.h>
Имеются две группы функций, оперирующих со строками. Они определены в
заголовочном файле <string.h>. Имена функций первой группы начинаются с
букв str, второй - с mem. Если копирование имеет дело с объектами,
перекрывающимися по памяти, то, за исключением memmove, поведение функций не
определено. Функции сравнения рассматривают аргументы как массивы элементов
типа unsigned char.
В таблице на с. 321 переменные s и t принадлежат типу char *, cs и ct – типу
const char *, n - типу size_t, а c - значение типа int, приведенное к типу
char.
Последовательные вызовы strtok разбивают строку s на лексемы. Ограничителем
лексемы служит любой символ из строки ct. В первом вызове указатель s не
равен NULL. Функция находит в строке s первую лексему, состоящую из символов,
не входящих в ct; ее работа заканчивается тем, что поверх следующего символа
пишется '\0' и возвращается указатель на лексему. Каждый последующий вызов, в
котором указатель s равен NULL, возвращает указатель на следующую лексему,
которую функция будет искать сразу за концом предыдущей. Функция strtok
возвращает NULL, если далее никакой лексемы не обнаружено. Параметр ct от
вызова к вызову может варьироваться.
Здесь и ниже под такими выражениями как cs<ct не следует понимать
арифметическое сравнение указателей. Подразумевается лексикографическое
сравнение, т. е. cs меньше (больше) ct, если первый несовпавший элемент в cs
арифметически меньше (больше) соответствующего элемента из ct.— Примеч. ред.
char *strcpy(s,ct) |
копирует строку ct в строку s, включая '\0'; возвращает s |
char *strncpy(s,ct,n) |
копирует не более n символов строки ct в s; возвращает s. Дополняет результат символами '\0', если символов в ct меньше n |
char *strcat(s,ct) |
приписывает ct к s; возвращает s |
char *strncat(s,ct,n) |
приписывает не более n символов ct к s, завершая s символом '\0'; возвращает s |
char strcmp(cs,st) |
сравнивает cs и ct; возвращает <0, если cs<ct; 0, если cs==ct; и >0, если cs>ct (I.B.: вообще-то, функция возвращает int) |
char strncmp(cs,ct) |
сравнивает не более n символов cs и ct;
возвращает <0, если cs<ct, 0, если cs==ct, и >0, если cs>ct (I.B.: тоже int должна возвращать) |
char *strchr(cs,c) |
возвращает указатель на первое вхождение c в cs или, если такового не оказалось, NULL |
char *strrchr(cs,c) |
возвращает указатель на последнее вхождение c в cs или, если такового не оказалось, NULL |
size_t strspn(cs,ct) |
возвращает длину начального сегмента cs, состоящего из символов, входящих в строку ct |
size_t strcspn(cs,ct) |
возвращает длину начального сегмента cs, состоящего из символов, не входящих в строку ct |
char *strpbrk(cs,ct) |
возвращает указатель в cs на первый символ, который совпал с одним из символов, входящих в ct, или, если такового не оказалось, NULL |
char *strstr(cs, ct) |
возвращает указатель на первое вхождение ct в cs или, если такового не оказалось, NULL |
size_t strlen(cs) |
возвращает длину cs |
char * strerror(n) |
возвращает указатель на зависящую от реализации строку, соответствующую номеру ошибки n |
char * strtok(s, ct) |
strtok ищет в s лексему, ограниченную символами из ct; более подробное описание этой функции см. ниже |
Функции mem... предназначены для манипулирования с объектами как с массивами
символов; их назначение - получить интерфейсы к эффективным программам. В
приведенной ниже таблице s и t принадлежат типу void *; cs и ct - типу const
void *; n - типу size_t; а c имеет значение типа int, приведенное к типу
char.
void *memcpy(s,ct, n) |
копирует n символов из ct в s и возвращает s |
void *memmove(s,ct,n) |
делает то же самое, что и memcpy, но работает и в случае "перекрывающихся" объектов. |
int memcmp(cs, ct, n) |
сравнивает первые n символов cs и ct; выдает тот же результат, что и функция strcmp |
void *memchr(cs, c, n) |
возвращает указатель на первое вхождение символа c в cs или, если среди первых n символов c не встретилось, NULL |
void *memset(s, c, n) |
размещает символ c в первых n позициях строки s и возвращает s |
B4. Математические функции: <math.h>
В заголовочном файле <math.h> описываются математические функции и
определяются макросы.
Макросы EDOM и ERANGE (находящиеся в <errno.h>) задают отличные от нуля
целочисленные константы, используемые для фиксации ошибки области и ошибки
диапазона; HUGE_VAL определена как положительное значение типа double. Ошибка
области возникает, если аргумент выходит за область значений, для которой
определена функция. Фиксация ошибки области осуществляется присвоением errno
значения EDOM; возвращаемое значение зависит от реализации. Ошибка диапазона
возникает тогда, когда результат функции не может быть представлен в виде
double. В случае переполнения функция возвращает HUGE_VAL с правильным знаком
и в errno устанавливается значение ERANGE. Если результат оказывается меньше,
чем возможно представить данным типом, функция возвращает нуль, а
устанавливается ли в этом случае errno в ERANGE, зависит от реализации. Далее
x и y имеют тип double, n - тип int, и все функции возвращают значения типа
double. Углы в тригонометрических функциях задаются в радианах.
sin(x) |
синус x |
cos(x) |
косинус x |
tan(x) |
тангенс x |
asin(x) |
арксинус x в диапазоне [-pi/2,pi/2], x в диапазоне [-1,1] |
acos(x) |
арккосинус x в диапазоне [0, pi], x в диапазоне [-1,1] |
atan(x) |
арктангенс x в диапазоне [-pi/2, pi/2] |
atan2(y,x) |
арктангенс y/x в диапазоне [-pi, pi] |
sinh(x) |
гиперболический синус x |
cosh(x) |
гиперболический косинус x |
tanh(x) |
гиперболический тангенс x |
exp(x) |
Экспоненциальная функция ex |
log(x) |
натуральный логарифм ln(x), x > 0 |
log10(x) |
десятичный логарифм lg(x), x > 0 |
pow(x,y) |
xy, ошибка области, если x = 0 или y<=0 или x<0 и y – не целое |
sqrt(x) |
квадратный корень x, x >= 0 |
ceil(x) |
наименьшее целое в виде double, которое не меньше x |
floor(x) |
наибольшее целое в виде double, которое не больше x |
fabs(x) |
абсолютное значение |x| |
ldexp(x, n) |
x * 2n |
frexp(x, int *еxр) |
разбивает x на два сомножителя, первый из которых -
нормализованная дробь в интервале [1/2, 1), которая
возвращается, а второй - степень двойки, эта степень
запоминается в *exp. Если x - нуль, то обе части
результата равны нулю
|
modf(x,double *ip) |
разбивается на целую и дробную части, обе имеют тот же
знак, что и x. Целая часть запоминается в *ip, дробная
часть выдается как результат
|
fmod(x, y) |
остаток от деления x на y в виде числа с плавающей
точкой. Знак результата совпадает со знаком x. Если y
равен нулю, результат зависит от реализации
|
B5. Функции общего назначения: <stdlib.h>
Заголовочный файл <stdlib.h> объявляет функции, предназначенные для
преобразования чисел, запроса памяти и других задач.
double atof(const char *s)
atof переводит s в double; эквивалентна strtod(s, (char**) NULL).
int atoi(const char *s)
atoi переводит s в int; эквивалентна (int)strtol(s, (char**)NULL, 10).
int atol(const char *s)
atol переводит s в long; эквивалентна strtol(s, (char**) NULL, 10).
double strtod(const char *s, char **endp)
strtod преобразует первые символы строки s в double, игнорируя начальные
символы-разделители; запоминает указатель на непреобразованный конец в *endp
(если endp не NULL), при переполнении она выдает HUGE_VAL с соответствующим
знаком, в случае, если результат оказывается меньше, чем возможно представить
данным типом, возвращается 0; в обоих случаях в errno устанавливается ERANGE.
long strtol(const char *s, char **endp, int base)
strtol преобразует первые символы строки s в long, игнорируя начальные
символы-разделители; запоминает указатель на непреобразованный конец в *endp
(если endp не NULL). Если base находится в диапазоне от 2 до 36, то
преобразование делается в предположении, что на входе - запись числа по
основанию base. Если base равно нулю, то основанием числа считается 8, 10 или
16; число, начинающееся с цифры 0, считается восьмеричным, а с 0x или 0X -
шестнадцатеричным. Цифры от 10 до base-1 записываются начальными буквами
латинского алфавита в любом регистре. При основании, равном 16, в начале
числа разрешается помещать 0x или 0X. В случае переполнения функция
возвращает LONG_MAX или LONG_MIN (в зависимости от знака), a в errno
устанавливается ERANGE.
unsigned long strtoul(const char *s, char **endp, int base)
strtoul работает так же, как и strtol, с той лишь разницей, что возвращает
результат типа unsigned long, а в случае переполнения - ULONG_MAX.
int rand(void)
rand выдает псевдослучайное число в диапазоне от 0 до RAND_MAX; RAND_MAX не
меньше 32767.
void srand(unsigned int seed)
srand использует seed в качестве семени для новой последовательности
псевдослучайных чисел. Изначально параметр seed равен 1.
void *calloc(size_t nobj, size_t size)
calloc возвращает указатель на место в памяти, отведенное для массива nobj
объектов, каждый из которых размера size, или, если памяти запрашиваемого
объема нет, NULL. Выделенная область памяти обнуляется.
void *malloc(size_t size)
malloc возвращает указатель на место в памяти для объекта размера size или,
если памяти запрашиваемого объема нет, NULL. Выделенная область памяти не
инициализируется.
void *realloc(void *p, size_t size)
realloc заменяет на size размер объекта, на который указывает p. Для части,
размер которой равен наименьшему из старого и нового размеров, содержимое не
изменяется. Если новый размер больше старого, дополнительное пространство не
инициализируется, realloc возвращает указатель на новое место памяти или,
если требования не могут быть удовлетворены, NULL (*p при этом не
изменяется).
void free(void *р)
free освобождает область памяти, на которую указывает p; эта функция ничего
не делает, если p равно NULL. В p должен стоять указатель на область памяти,
ранее выделенную одной из функций: calloc, malloc или realloc.
void abort(void *р)
abort вызывает аварийное завершение программы, ее действия эквивалентны
вызову raise(SIGABRT).
void exit(int status)
exit вызывает нормальное завершение программы. Функции, зарегистрированные с
помощью atexit, выполняются в порядке, обратном их регистрации. Производится
опорожнение буферов открытых файлов, открытые потоки закрываются, и
управление возвращается в среду, из которой был произведен запуск программы.
Значение status, передаваемое в среду, зависит от реализации, однако при
успешном завершении программы принято передавать нуль. Можно также
использовать значения EXIT_SUCCESS (в случае успешного завершения) и
EXIT_FAILURE (в случае ошибки).
int atexit(void (*fcn)(void))
atexit регистрирует fcn в качестве функции, которая будет вызываться при
нормальном завершении программы; возвращает ненулевое значение, если
регистрация не может быть выполнена.
int system(const char *s)
system пepeдaeт cтpoку s oпepaциoннoй cpeдe для выпoлнeния. Если s есть NULL
и существует командный процессор, то system возвращает ненулевое значение.
Если s не NULL, то возвращаемое значение зависит от реализации.
char *getenv(const char *name)
getenv возвращает строку среды, связанную с name, или, если никакой строки не
существует, NULL. Детали зависят от реализации.
void *bsearch(const void *key, const void *base,
size_t n, size_t size,
int (*cmp)(const void *keyval, const void *datum))
bsearch ищет среди base[0]...base[n-1] элемент с подходящим ключом *key.
Функция cmp должна сравнивать первый аргумент (ключ поиска) со своим вторым
аргументом (значением ключа в таблице) и в зависимости от результата
сравнения выдавать отрицательное число, нуль или положительное значение.
Элементы массива base должны быть упорядочены в возрастающем порядке, bsearch
возвращает указатель на элемент с подходящим ключом или, если такого не
оказалось, NULL.
void qsort(void *base, size_t n, size_t size,
int (*cmp)(const void *, const void *))
qsort сортирует массив base[0]...base[n-1] объектов размера size в
возрастающем порядке. Функция сравнения cmp - такая же, как и в bsearch.
int abs(int n)
abs возвращает абсолютное значение аргумента типа int.
long labs(long n)
labs возвращает абсолютное значение аргумента типа long.
div_t div(int num, int denom)
div вычисляет частное и остаток от деления num на denom. Результаты типа int
запоминаются в элементах quot и rem структуры div_t.
ldiv_t ldiv(long num, long denom)
ldiv вычисляет частное и остаток от деления num на denom. Результаты типа
long запоминаются в элементах quot и rem структуры ldiv_t.
B6. Диагностика: <assert.h>
Макрос assert используется для включения в программу диагностических
сообщений.
void assert (int выражение)
Если выражение имеет значение нуль, то
assert (выражение)
напечатает в stderr сообщение следующего вида:
Assertion failed: выражение, file имя-файла, line nnn
после чего будет вызвана функция abort, которая завершит вычисления. Имя
исходного файла и номер строки будут взяты из макросов __FILE__ и __LINE__.
Если в момент включения файла <assert.h> было определено имя NDEBUG, то
макрос assert игнорируется.
B7. Списки аргументов переменной длины: <stdarg.h>
Заголовочный файл <stdarg.h> предоставляет средства для перебора
аргументов функции, количество и типы которых заранее не известны. Пусть
lastarg - последний именованный параметр функции f с переменным числом
аргументов. Внутри f объявляется переменная ap типа va_list, предназначенная
для хранения указателя на очередной аргумент:
va_list ар;
Прежде чем будет возможен доступ к безымянным аргументам, необходимо один раз
инициализировать ap, обратившись к макросу va_start:
va_start(va_list ap, lastarg);
С этого момента каждое обращение к макросу:
type va_arg(va_list ap, type);
будет давать значение очередного безымянного аргумента указанного типа, и
каждое такое обращение будет вызывать автоматическое приращение указателя ap,
чтобы последний указывал на следующий аргумент. Один раз после перебора
аргументов, но до выхода из f необходимо обратиться к макросу
void va_end(va_list ap);
B8. Дальние переходы: <setjmp.h>
Объявления в <setjmp.h> предоставляют способ отклониться от обычной
последовательности "вызов - возврат"; типичная ситуация - необходимость
вернуться из "глубоко вложенного" вызова функции на верхний уровень, минуя
промежуточные возвраты.
int setjmp(jmp_buf env);
Макрос setjmp сохраняет текущую информацию о вызовах в env для последующего
ее использования в longjmp. Возвращает нуль, если возврат осуществляется
непосредственно из setjmp, и не нуль, если - от последующего вызова longjmp.
Обращение к setjmp возможно только в определенных контекстах, в основном это
проверки в if, switсh и циклах, причем только в простых выражениях отношения.
if (setjmp() == 0)
/* после прямого возврата */
else
/* после возврата из longjmp */
void longjmp(jmp_buf env, int val);
longjmp восстанавливает информацию, сохраненную в самом последнем вызове
setjmp, по информации из env; выполнение программы возобновляется, как если
бы функция setjmp только что отработала и вернула ненулевое значение val.
Результат будет непредсказуемым, если в момент обращения к longjmp функция,
содержащая вызов setjmp, уже "отработала" и осуществила возврат. Доступные ей
объекты имеют те значения, которые они имели в момент обращения к longjmp;
setjmp не сохраняет значений.
B9. Сигналы: <signal.h>
Заголовочный файл <signal.h> предоставляет средства для обработки
исключительных ситуаций, возникающих во время выполнения программы, таких как
прерывание, вызванное внешним источником или ошибкой в вычислениях.
void (*signal(int sig, void (*handler)(int)))(int)
signal устанавливает, как будут обрабатываться последующие сигналы. Если
параметр handler имеет значение SIG_DFL, то используется зависимая от
реализации "обработка по умолчанию"; если значение handler равно SIG_IGN, то
сигнал игнорируется; в остальных случаях будет выполнено обращение к функции,
на которую указывает handler с типом сигнала в качестве аргумента. В число
допустимых видов сигналов входят:
SIGABRT |
- аварийное завершение, например от abort; |
SIGFPE |
- арифметическая ошибка: деление на 0 или
переполнение; |
SIGILL |
- неверный код функции (недопустимая команда);
|
SIGINT |
- запрос на взаимодействие, например прерывание;
|
SIGSEGV |
- неверный доступ к памяти, например выход за
границы; |
SIGTERM |
- требование завершения, посланное в программу.
|
signal возвращает предыдущее значение handler в случае специфицированного
сигнала, или SIGERR в случае возникновения ошибки.
Когда в дальнейшем появляется сигнал sig, сначала восстанавливается
готовность поведения "по умолчанию", после чего вызывается функция, заданная
в параметре handler, т.е. как бы выполняется вызов (*handler)(sig). Если
функция handler вернет управление назад, то вычисления возобновятся с того
места, где застал программу пришедший сигнал. Начальное состояние сигналов
зависит от реализации.
int raise(int sig)
raise посылает в программу сигнал sig. В случае неудачи возвращает
ненулевое значение.
B10. Функции даты и времени: <time.h>
Заголовочный файл <time.h> объявляет типы и функции, связанные с датой
и временем. Некоторые функции имеют дело с местным временем, которое может
отличаться от календарного, например в связи с зонированием времени. Типы
clосk_t и time_t - арифметические типы для представления времени, a struct tm
содержит компоненты календарного времени:
int tm_sec; - секунды от начала минуты (0,61); -- I.B.: все же наверно от 0 до 59
int tm_min; - минуты от начала часа (0,59);
int tm_hour; - часы от полуночи (0,23);
int tm_mday; - число месяца (1,31);
int tm_mon; - месяцы с января(0,11);
int tm_year; - годы с 1900;
int tm_wday; - дни с воскресенья (0,6);
int tm_yday; - дни с 1 января (0,365);
int tm_isdst; - признак летнего времени.
Значение tm_isdst - положительное, если время приходится на сезон, когда
время суток сдвинуто на 1 час вперед, нуль в противном случае и
отрицательное, если информация не доступна.
clock_t clock(void)
clock возвращает время, фиксируемое процессором от начала выполнения
программы, или -1, если оно не известно. Для выражения этого времени в
секундах применяется формула clock()/CLOCKS_PER_SEC.
time_t time(time_t *tp)
time возвращает текущее календарное время (т. е. время, прошедшее после
определенной даты, - обычно после 0 ч 00 мин 00 с GMT 1-го января 1970 г. -
примеч. ред.) или -1, если время не известно. Если tp не равно NULL, то
возвращаемое значение записывается и в *tp.
double difftime(time_t time2, time_t time1)
difftime возвращает разность time2 - time1, выраженную в секундах.
time_t mktime(struct tm *tp)
mktime преобразует местное время, заданное структурой *tp, в календарное,
выдавая его в том же виде, что и функция time. Компоненты будут иметь
значения в указанных диапазонах. Функция mktime возвращает календарное время
или -1, если оно не представимо.
Следующие четыре функции возвращают указатели на статические объекты, каждый
из которых может быть изменен другими вызовами.
char *asctime(const struct tm *tp)
asctime переводит время в структуре *tp в строку вида
Sun Jan 3 15:14:13 1988\n\0
char *ctime(const time_t *tp)
ctime переводит календарное время в местное, что эквивалентно
выполнению asctime(localtime(tp))
struct tm *gmtime(const time_t *tp)
gmtime переводит календарное время во Всемирное координированное время
(Coordinated Universal Time - UTC). Выдаст NULL, если UTC не известно. Имя
этой функции, gmtime, происходит от Greenwich Mean Time (среднее время по
Гринвичскому меридиану).
struct tm *localtime(const time_t *tp)
localtime переводит календарное время *tp в местное.
size_t strftime(char *s, size_t smax, const char *fmt, const struct tm *tp)
strftime форматирует информацию о дате и времени из *tp в строку s согласно
формату fmt, который имеет много общих черт с форматом, задаваемым в функции
printf. Обычные символы (включая и завершающий символ '\0') копируются в s.
Каждая пара, состоящая из % и буквы, заменяется, как показано ниже, с
использованием значений по форме, соответствующей местным традициям. В s
размещается не более smax символов; strftime возвращает число символов без
учета '\0' или нуль, если число сгенерированных символов больше smax.
%a | сокращенное название дня недели |
%A | полное название дня недели |
%b | сокращенное название месяца |
%B | полное название месяца |
%c | местное представление даты и времени |
%d | день месяца (01-31) |
%H | час (24-часовое время) (00-23) |
%I | час (12-часовое время) (01-12) |
%j | день от начала года (001-366) |
%m | месяц (01-12) |
%M | минута (00-59) |
%p | местное представление AM или РМ (до или после полудня) |
%S | секунда (00-61) |
%U | неделя от начала года (считая, что воскресенье - 1-й день недели) (00-53) |
%w | день недели (0-6, номер воскресенья - 0) |
%W | неделя от начала года (считая, что понедельник - 1-й день недели) (00-53) |
%x | местное представление даты |
%X | местное представление времени |
%y | год без указания века (00-99) |
%Y | год с указанием века |
%Z | название временной зоны, если она есть |
%% | % |
B11. Зависящие от реализации пределы: <limits.h> и <float.h>
Заголовочный файл <limits.h> определяет константы для размеров
целочисленных типов. Ниже перечислены минимальные приемлемые величины, но в
конкретных реализациях могут использоваться и большие значения.
CHAR_BIT | 8 | битов в значении char |
SCHAR_MAX | UCHAR_MAX или SCHAR_MAX | максимальное значение char |
CHAR_MIN | 0 или CHAR_MIN | минимальное значение char |
INT_MAX | +32767 | максимальное значение int |
INT_MIN | -32767 (I.B.:обычно это значение -32768) | минимальное значение int |
LONG_MAX | +2147463647 | максимальное значение long |
LONG_MIN | -2147483647 (I.B.:обычно это значение -2147483648) | минимальное значение long |
SCHAR_MAX | +127 | максимальное значение signed char |
SCHAR_MIN | -127 (I.B.:обычно это значение -128) | минимальное значение signed char |
SHRT_MAX | +32767 | максимальное значение short |
SHRT_MIN | -32767 (I.B.:обычно это значение -32768) | минимальное значение short |
UCHAR_MAX | 255 | максимальное значение unsigned char |
UINT_MAX | 65535 | максимальное значение unsigned int |
ULONG_MAX | 4294967295 | максимальное значение unsigned long |
USHRT_MAX | 65535 | максимальное значение unsigned short |
Имена, приведенные в следующей таблице, взяты из <float.h> и являются
константами, имеющими отношение к арифметике с плавающей точкой. Значения
(если они есть) представляют собой минимальные значения для соответствующих
величин. В каждой реализации устанавливаются свои значения.
FLT_RADIX | 2 | основание для представления порядка, например: 2, 16 |
FLT_ROUNDS | | способ округления при сложении чисел с плавающей точкой |
FLT_DIG | 6 | количество верных десятичных цифр |
FLT_EPSILON | 1E-5 | минимальное х, такое, что 1.0 + х != 1.0 |
FLT_MANT_DIG | | количество цифр по основанию FLT_RADIX в мантиссе |
FLT_MAX | 1E+37 | максимальное число с плавающей точкой |
FLT_MAX_EXP | | максимальное n, такое, что FLT_RADIXn-1 представимо |
FLT_MIN | 1E-37 | минимальное нормализованное число с плавающей точкой |
FLT_MIN_EXP | | минимальное n, такое, что 10n представимо в виде нормализованного числа |
DBL_DIG | 10 | количество верных десятичных цифр для типа double |
DBL_EPSILON | 1E-9 | минимальное х, такое, что 1.0 + x != 1.0, где x принадлежит типу double |
DBL_MANT_DIG | | количество цифр по основанию FLT_RADIX в мантиссе для чисел типа double |
DBL_MAX | 1E+37 | максимальное число с плавающей точкой типа double |
DBL_MAX_EXP | | максимальное n, такое, что FLT_RADIXn-1 представимо в виде числа типа double |
DBL_MIN | 1E-37 | минимальное нормализованное число с плавающей точкой типа double |
DBL_MIN_EXP | | минимальное n, такое, что 10n представимо в виде нормализованного числа типа double |
Приложение C. Перечень изменений
С момента публикации первого издания этой книги определение языка Си
претерпело изменения. Почти все нововведения - это расширения исходной версии
языка, выполненные так, чтобы сохранилась совместимость с существующими
программами; некоторые изменения касаются устранения двусмысленностей
первоначального описания, а некоторые представляют собой модификации,
привнесенные существующей практикой. Многие из новых возможностей,
впоследствии принятые другими разработчиками Си-компиляторов, были
первоначально объявлены в документах, прилагаемых к компиляторам. Комитет
ANSI, подготавливая стандарт языка, включил большинство этих изменений, а
также ввел другие значительные модификации. Некоторые коммерческие компиляторы
реализовали их еще до выпуска официального Си-стандарта.
В этом приложении сведены воедино различия между языком, определенным в первой
его редакции, и той его версии, которая принята в качестве стандарта. Здесь
рассматривается только сам язык; вопросы, относящиеся к его окружению и
библиотеке, не затрагиваются. Хотя последние и являются важной частью
стандарта, но, поскольку в первом издании не делалось попытки описать среду и
библиотеку, с соответствующими стандартными элементами сравнивать практически
нечего.
В стандарте более тщательно, по сравнению с первым изданием, определено и
расширено препроцессирование: в его основу явно положены лексемы; введены
новые операторы для "склеивания" лексем (##) и создания символьных
строк (#), а также новые управляющие строки, такие как #elif и
#pragma; разрешено повторное определение макроса с той же
последовательностью лексем; отменена подстановка параметров внутри строк.
Разрешено "склеивание" строк с помощью знака \ в любом месте, не только
в строках и макроопределениях (см. A.12).
Минимальное число значимых символов всех внутренних идентификаторов
доведено до 31; для идентификаторов с внешней связью оно остается равным 6;
буквы нижнего и верхнего регистров не различаются. (Многие реализации
допускают большее число значимых символов.)
Для знаков #, \, ^, [, ], {, }, |, ~,
которых может не быть в некоторых
наборах символов, введены трехзнаковые последовательности, начинающиеся с
?? (см. A12.1). Следует заметить, что введение трехзнаковых
последовательностей может повредить значения строк, в которых содержатся
??.
Введены новые ключевые слова (void, const, volatile,
signed, enum), а мертворожденное слово entry из обращения
изъято.
Для символьных констант и строковых литералов определены новые эскейп-
последовательности. Объявлено, что появление за \ символов не из
принятых эскейп-последовательностей приводит к непредсказуемому результату
(см. A2.5.2.).
Узаконено полюбившееся всем тривиальное изменение: 8 и 9 не являются
восьмеричными цифрами.
Введен расширенный набор суффиксов для явного указания типов констант:
U и L - для целых и F и L - для типов с плавающей
точкой. Уточнены также правила определения типа для констант без суффиксов
(A2.5).
Объявлено, что соседние строки конкатенируются.
Предоставлены средства, позволяющие записывать строковые литералы и
символьные константы из расширенного набора символов (A2.6).
Объекты типа char (как и объекты другого типа) можно
специфицировать явно со знаком или без знака. Исключается использование
словосочетания long float в смысле double, но вводится тип
long double для чисел с плавающей точкой повышенной точности.
С некоторых пор доступен тип unsigned char. Стандарт вводит
ключевое слово signed для явного указания, что объект типа char
или другого целочисленного типа имеет знак.
Уже несколько лет в большинстве реализаций доступен тип void.
Стандарт вводит void * в качестве типа обобщенного указателя; раньше
для этой цели использовали char *. Одновременно вступают в силу
правила, по которым запрещается без преобразования типа "смешивать" указатели
и целые или указатели разных типов.
Стандарт устанавливает минимальные пределы диапазонов арифметических
типов, предусматривает заголовочные файлы <limits.h> и
<float.h>, в которых помещаются эти характеристики для каждой
конкретной реализации.
Перечисление - новый тип, которого не было в первой редакции.
Стандарт заимствует из C++ способ записи квалификатора типа, в частности
квалификатора const (A8.2).
Вводится запрет на модификацию строк: это значит, что их разрешается
размещать в памяти, доступной только на чтение (ПЗУ).
Изменены "обычные арифметические преобразования"; по существу, выполнен
переход от принципа "для целых всегда превалирует unsigned; для
плавающей точки всегда используется double" к принципу "повышение до
минимального достаточно вместительного типа" (см. A6.5).
Отменены старые операторы присваивания вроде =+. Каждый оператор
присваивания теперь представляется одной отдельной лексемой. В первом издании
оператор присваивания мог изображаться парой символов, возможно, разделенных
символами-разделителями.
Компиляторам более не разрешается трактовать математическую
ассоциативность операторов как вычислительную ассоциативность.
Введен унарный оператор + для симметрии с унарным -.
Разрешено использовать указатель на функцию в качестве ее именующего
выражения без явного оператора * (см. A7.3.2).
Структурами разрешено оперировать при присваиваниях, можно передавать
структуры в качестве аргументов функциям и получать их в качестве результата
от функций.
Разрешено применять оператор получения адреса & к массиву;
результатом является указатель на массив.
В первой редакции результат операции sizeof имел тип int; во
многих реализациях он заменен на unsigned. Стандарт официально
объявляет его зависимым от реализации, но требует, чтобы он был определен в
заголовочном файле <stddef.h> под именем size_t.
Аналогичное изменение было сделано в отношении типа разности указателей)
который теперь выступает под именем ptrdiff_t (см. A7.4.8 и A7.7).
Запрещено применять оператор получения адреса & к объекту типа
register даже тогда, когда данный компилятор не располагает его на
регистре.
Типом результата операции сдвига является тип ее левого операнда; тип
правого операнда на повышение типа результата влияния не оказывает (см. A7.8).
Стандарт разрешает адресоваться с помощью указателей на место, лежащее
сразу за последним элементом массива, и позволяет оперировать с такими
указателями, как с обычными, см. A7.7.
Стандарт вводит (заимствованный из C++) способ записи прототипа функции с
включением в него типов параметров и явного указания о возможности их
изменения и формализует метод работы с переменным списком аргументов. (см.
A7.3.2, A8.6.3, B7.) С некоторыми ограничениями доступен и старый способ
записи.
Стандартом запрещены пустые объявления, т. е. такие, в которых нет
объявителей и не объявляется ни одной структуры, объединения или перечисления.
Однако объявление с одним тегом структуры (или объединения) переобъявит ее
даже в том случае, если она была объявлена во внешней области действия.
Запрещены объявления внешних данных, не имеющие спецификаторов и
квалификаторов (т. е. объявления с одним "голым" объявителем).
В некоторых реализациях, когда extern-объявление расположено во
внутреннем блоке, его область видимости распространяется на остальную часть
файла. Стандарт вносит ясность в эту ситуацию и объявляет, что область
видимости такого объявления ограничена блоком.
Область видимости параметров "вставляется" в составную инструкцию,
представляющую собой тело функции, так что объявления на верхнем уровне
функции не могут их "затенить".
Несколько изменены именные пространства идентификаторов. Всем тегам
структур, объединений и перечислений стандарт выделяет одно именное
пространство; для меток инструкций вводится отдельное именное пространство
(см. A11.1). Кроме того, имена элементов связаны со структурой или
объединением, частью которого они являются. (С некоторых пор это общепринятая
практика.)
Допускается инициализация объединения; инициализатор относится к первому
элементу объединения.
Разрешается инициализация автоматических структур, объединений и массивов,
хотя и с некоторыми ограничениями.
Разрешается инициализация массива символов с помощью строкового литерала
по точному количеству указанных символов (без '\0').
Управляющее выражение и case-метки в switch могут иметь
любой целочисленный тип.
|