Стандарт ANSI C уточнил многие неопределенные области K&R C (K&R означает Керниган и Ричи). Это приводит к намного более четкому определению корректной программы на языке C. Однако если программа была написана нацеленной на использование некоторых определенных нечетко описанных особенностей K&R C (возможно случайно), то их авторы могут быть очень удивлены при попытке портирования кода в среду ANSI C. В последующих секциях (перевод статьи [1]) представлен список возможно наиболее важных различий между компиляторами ANSI и K&R C. Отличия в поведении библиотек обсуждаются отдельно. Порядок изложения максимально приближен к материалу стандарта ANSI C.
[Элементы лексики]
В ANSI C упорядочивание фраз трансляции четко определено. Особенно это касается препроцессора, поведение которого концептуально основано на символах (что не приводит к тем же результатам, которое было бы при чистой манипуляции текстом, потому что есть видимые границы между сочетающимися символами).
В ANSI C введено некоторое количество новых ключевых слов, и уточнено значение существующих:
• Квалификатор типа volatile означает, что квалифицированный объект может быть изменен способом, неизвестным для реализации, или доступ к нему может привести к другим неизвестным побочным эффектам. Примеры таких объектов, корректно описываемых как volatile, включают регистры устройства, семафоры и данные, которые являются общими с асинхронными обработчиками сигналов. В целом выражения, включающие объекты volatile, не могут быть произвольным образом оптимизированы компилятором. Подробнее про volatile см. [4].
• Квалификатор типа const показывает, что значение объекта не будет изменено при выполнении программы (и в некоторых контекстах разрешает языковой системе принудительно разместить этот объект в хранилище read-only).
• Спецификатор типа void показывает не существующее значение для выражения.
• Спецификатор типа void * описывает generic-указатель, на который или от которого может быть назначен любой тип указателя без потери информации.
• Спецификатор типа signed может использоваться везде, где допустимо использовать unsigned (например, для явного указания знакового символьного типа signed char).
• Новый тип с плавающей точкой: long double.
• Практика K&R C использования long float для обозначения double в стандарте ANSI C признана неправильной.
Также введены изменения лексики:
• Каждый struct и union имеет свое собственное пространство имен для своих членов.
• Суффиксы U и L (или u и l) могут использоваться для явного обозначения констант unsigned и long (например 32L, 64U, 1024UL, и т. д.). Суффикс U введен ANSI C как новый.
• Больше не поддерживается использование восьмеричных констант 8 и 9 (ранее определено для восьмеричных 10 и 11 соответственно).
• Литералы строк считаются строками только для чтения (read-only), aи идентичные строки могут храниться как единственное значение, используемое совместно (это сделано для экономии памяти; по умолчанию это обычное поведение компилятора ARM C). Например, для кода ниже в памяти будет выделена единственная строка "hello", и указатели p1 и p2 получат одинаковое значение:
char *p1 = "hello";
char *p2 = "hello";
Таким образом, программы не должны модифицировать литералы строк (остерегайтесь функций Unix tmpnam() и подобных, которые делают такие модификации).
• Вариативные функции (Variadic-функции, которые принимают переменное количество реальных аргументов) явно декларируются с помощью многоточия (...). Например:
int printf(const char *fmt, ...);
• Пустые комментарии /**/ заменяются одним пробелом (используйте директиву препроцессора ##, чтобы делать вставку токенов, если для этого Вы ранее использовали /**/).
[Арифметика]
ANSI C использует для арифметических преобразований правила обработки с сохранением значения (value-preserving), в то время как реализации K&R C склоняются к использованию правил операций без знака (unsigned-preserving). Например, следующий код по стандарту ANSI C делает знаковое деление, где реализация unsigned-preserving делала бы деление без знака:
int f(int x, unsigned char y)
{
return (x+y)/2;
}
Кроме правил value-preserving, арифметические преобразования работают как в K&R C, с дополнительными правилами для long double и unsigned long int. Теперь допустимо выполнять арифметику float без расширения значения до double (система ARM C этого пока не делает).
При преобразовании значений с плавающей точкой в целые значения происходит округление в сторону нуля с отбрасыванием дробной части.
Недопустимо присваивать указатели на функцию указателям на данные, и наоборот. Должно быть использовано явное преобразование типа (explicit cast). Единственное исключение существует для значения 0, например:
Теперь стала строже возможность присваивания между структурами (struct) и объединениями (union). Для примера:
struct {char a; int b;} v1;
struct {char a; int b;} v2;
v1 = v2; /* недопустимо, потому что v1 и v2 разного типа */
[Выражения]
Структуры (struct) и объединения (union) могут быть переданы в качества аргумента по значению в функции.
Если есть указатель на функцию, определенный как, например, int (*pfi)(), то функция, на которую он указывает, может быть вызвана двумя способами: либо pfi();, либо (*pfi)().
Из-за использования различных пространств имен для членов struct и union, абсолютный машинный адрес должен быть явное преобразован перед использованием его как указатель на struct или union. Например:
((struct io_space *)0x00ff)->io_buf;
[Декларации]
Возможно самое большое влияние на язык C стандарт ANSI оказал адаптацией прототипов функции. Прототип функции декларирует тип возвращаемого значения и типы аргументов функции. Например:
Показанное выше определение декларирует функцию, возвращающую тип int, и принимающую два аргумента, один типа int, другой типа float.
Это означает, что типы аргументов функции являются частью типа функции, давая преимущества строгой проверке типов, особенно между компилируемыми по отдельности исходными файлами кода.
Определение функции (которое также является прототипом) осталось таким же, за исключением того, что должны даваться идентификаторы аргументам, например int f(int i, float f). Все еще можно использовать использовать декларации в старом стиле, однако рекомендуется перейти к декларации функций по-новому. Также можно смешивать старый и новый стили декларации функции. Если декларация функции находится в области действия старого типа, для целочисленных аргументов выполняются нормальные целочисленные продвижения (promotions), и значения float преобразуются в double. Если декларация функции находится в области действия нового стиля, аргументы преобразуются так же, как в обычных операторах присваивания.
Пустые декларации теперь недопустимы.
Массивы не могут быть определены с нулевым или отрицательным размером.
[Операторы (Statements)]
ANSI был определен с минимумом атрибутов управляющих операторов (например, минимальным количеством ветвей case, которое должно поддерживаться компилятором, минимальным количеством вложений управляющих конструкций, и т. д.). Эти минимальные значения не особенно щедрые, и могут создать неприятности, если требуется получить сверх компактный код.
Главным образом предел, накладываемый компилятором ARM C, задается наличием доступной памяти. Будущие релизы могут реализовывать опцию генерации предупреждения, если нарушается любой из гарантированных ANSI пределов.
Значение, возвращаемое из функции main(), гарантируется для использования как код выхода из программы (exit code).
Значения, используемые в операторе управления ветвлением и метками switch, могут быть любого целого типа.
[Препроцессор]
Директивы препроцессора не могут быть переопределены.
Введена новая директива ## для вставки/склеивания токенов.
Существует строковая директива #, которая создает строковый литерал из её последующих символов. Это полезно, когда Вы хотите встроить аргумент макроса в строку.
Порядок фраз трансляции четко определен, и выполняется для фаз обработки следующим образом:
1. Символы исходного файла отображаются на набор символов исходного кода (это включает замену триграфов [3]). 2. Удаляются все символы новой строки, которые следуют сразу после \. 3. Исходный файл делится на токены предварительной обработки и последовательности символов пробела (комментарии при этом заменяются одиночным пробелом). 4. Выполняются директивы препроцессора и раскрываются макросы.
Любые подключаемые директивой #include файлы рекурсивно проходят шаги 1-4 обработки.
Макрос __STDC__ определен как 1 для компиляторов, удовлетворяющих требованиям стандарта ANSI (и в том числе компилятором ARM C).
[Ссылки]
1. ANSI C vs K&R C site:altmer.arts-union.ru. 2. Transitioning to ANSI/ISO C site:northstar-www.dartmouth.edu. 3. Триграф (языки Си) site:wikipedia.org. 4. Как использовать ключевое слово volatile на языке C. |