Стив Саммит. Язык си в вопросах и ответах.. Препроцессор С.

Стив  Саммит. Язык си в вопросах и ответах.. Препроцессор С.

Ответы на вопросы разбиты по темам:

1. Нулевые указатели

2. Указатели и массивы.

3. Выделение памяти

4. Выражения

5. ANSI C

6. Препроцессор С.

7. Списки аргументов переменной длины.

8. Булевы выражения и переменные.

9. Структуры, перечисления и объединения.

10. Декларации.

11. Cтандартный ввод/вывод.

12. Библиотечные функции.

13. Lint.

14. Стиль.

15. Операции с плавающей точкой.

16. Интерфейс с операционной системой.

17. Разное (Пребразование Fortran -> C , грамматики для YACC и т.п.)

 Препроцессор    С.

6.1     Как написать макрос для обмена любых двух значений?

О:    На этот    вопрос нет хорошего ответа. При    обмене целых значений может быть использован хорошо    известный трюк с использованием    исключающего ИЛИ, но    это не сработает для чисел с плавающей точкой или указателей. Не годится этот прием и в случае, когда оба числа — на самом деле одно и то же число. Из-за многих побочных эффектов (см. вопросы 4.1 и 4.2) не годится и «очевидное» суперкомпактное решение для целых чисел a^=b^=a^=b. Когда макрос предназначен для переменных произвольного типа (обычно так и бывает), нельзя использовать временную переменную, поскольку не известен ее тип, а    стандартный С не имеет оператора typeof.
Если Вы не хотите передавать тип переменной третьим параметров, то, возможно, наиболее гибким, универсальным решением будет отказ от использования макроса.

6.2     У меня есть старая программа, которая пытается конструировать идентификаторы с помощью макроса
#define Paste(a, b) a/**/b
но у меня это не работает.

О:    То, что    комментарий полностью исчезает,    и, следовательно, может    быть использован для    склеивания соседних лексем (в частности, для создания новых идентификаторов), было недокументированной особенностью некоторых ранних реализаций препроцессора, среди которых заметна была реализация Рейзера (Reiser). Стандарт ANSI, как и K&R, утверждает, что комментарии заменяются единичными пробелами. Но поскольку необходимость склеивания лексем стала очевидной, стандарт ANSI ввел для этого специальный оператор ##, который может быть использован так:
#define Paste(a, b) a##b
Смотрите также вопрос 5.4.
Смотри: ANSI Разд. 3.8.3.3 c. 91, Rationale c. 66-7.

6.3    Как наилучшим образом написать cpp макрос, в котором есть несколько инструкций?

О:    Обычно цель состоит в том, чтобы написать макрос, который не отличался бы по виду от функции. Это значит, что завершающая точка с запятой ставится тем, кто вызывает макрос, а в самом теле макроса ее нет. Тело макроса не может быть просто составной инструкцией, заключенной в фигурные скобки, поскольку возникнут сообщения об ошибке (очевидно, из-за лишней точки с запятой, стоящей после инструкции) в том случае, когда макрос вызывается после if, а в инструкции if/else имеется else-часть.
Обычно эта проблема решается с помощью такого определения:
#define Func() do { \ /* объявления    */ \ что-то1; \ что-то2; \ /* … */ \ } while(0)      /* (нет завершающей ;    ) */
Когда при вызове макроса добавляется точка с запятой, это расширение становится простой инструкцией вне зависимости от контекста. (Оптимизирующий компилятор удалит излишние проверки или переходы по условию 0, хотя lint это может и не принять.)
Если требуется макрос, в котором нет деклараций    или ветвлений, а все инструкции — простые выражения, то возможен    другой подход, когда пишется    одно, заключенное в круглые скобки выражение, использующее одну или несколько запятых. (См. пример    в вопросе 6.10.    Такой подход позволяет также    реализовать «возврат» значения).
Смотри: CT&P Разд.6.3 c. 82-3.

6.4    Можно ли в головной файл с помощью #include включить другой головной файл?

О:      Это вопрос стиля, и здесь возникают большие споры. Многие полагают, что «вложенных с помощью #include файлов» следует избегать: авторитетный Indian Hill Style Guide (см. вопрос 14.3) неодобрительно отзывается о таком стиле; становится труднее найти соответствующее определение; вложенные #include могут привести к сообщениям о многократном объявлении, если головной файл включен дважды; также затрудняется корректировка управляющего файла для утилиты Make.  С другой стороны, становится возможным использовать модульный принцип при создании головных файлов (головной файл включает с помощью #include то, что необходимо только ему; в противном случае придется каждый раз использовать дополнительный #include, что способно вызвать постоянную головную боль); с помощью утилит, подобных grep (или файла tags) можно легко найти нужные определения вне зависимости от того, где они находятся, наконец, популярный прием:
#ifndef HEADERUSED #define HEADERUSED …содержимое    головного файла… #endif
делает головной файл  «идемпотентным», то есть такой файл можно безболезненно включать несколько раз; средства автоматической поддержки файлов для утилиты Make (без которых все равно не обойтись в случае больших проектов) легко обнаруживают зависимости при наличии вложенных #include. См. также раздел 14.

6.5    Работает ли оператор sizeof при    использовании средства препроцессора #if?

О:    Нет. Препроцессор работает на ранней стадии компиляции,    до того    как становятся известны типы переменных. Попробуйте использовать константы, определенные в файле <limits.h>, предусмотренном ANSI, или «сконфигурировать» вместо этого командный файл. (А еще лучше написать программу, которая по самой своей природе нечувствительна к размерам переменных).
Смотри: ANSI Разд. 2.1.1.2 c. 6-7, Разд. 3.8.1 c. 87 примечание 83.

6.6    Можно ли с помощью  #if    узнать,    как организована память    машины — по принципу: младший байт-меньший адрес или наоборот?

О:      Видимо, этого сделать нельзя. (Препроцессор использует для внутренних нужд только длинные целые и не имеет понятия об адресации). А уверены ли Вы, что нужно точно знать тип организации памяти? Уж лучше написать программу, которая от этого не зависит.

6.7    Во время компиляции мне    необходимо сложное препроцесссирование,    и я никак не могу придумать, как это сделать с помощью cpp.

О:      cpp не задуман как универсальный препроцессор. Чем заставлять cpp делать что-то ему не свойственное, подумайте о написании небольшого специализированного препроцессора. Легко раздобыть утилиту типа make(1), которая автоматизирует этот процесс.
Если Вы пытаетесь препроцессировать что-то отличное от С, воспользуйтесь универсальным препроцессором, (таким как m4).

6.8     Мне попалась программа, в которой, на мой взгляд, слишком много директив препроцессора #ifdef. Как обработать текст, чтобы оставить только один вариант условной компиляции, без использования cpp, а также    без раскрытия всех директив #include и #define?

О:    Свободно распространяются программы unifdef, rmifdef и scpp, которые делают в точности то, что Вам нужно. (См. вопрос 17.12).

6.9    Как получить список предопределенных идентификаторов?

О:      Стандартного способа не существует, хотя необходимость возникает часто. Если руководство по компилятору не содержит этих сведений, то, возможно, самый разумный путь — выделить текстовые строки из исполнимых файлов компилятора или препроцессора с помощью утилиты типа strings(1) системы Unix. Имейте в виду, что многие зависящие от системы предопределенные идентификаторы (например, «unix») нестандартны (поскольку конфликтуют с именами пользователя) и поэтому такие идентификаторы удаляются или меняются.

6.10    Как написать cpp макрос    с переменным количеством аргументов?

О:      Популярна такая уловка: определить макрос с одним аргументом, и вызывать его с двумя открывающими и двумя закрывающими круглыми скобками:
#define DEBUG(args) (printf(«DEBUG: «), printf args)
if(n != 0) DEBUG((«n is %d\n», n));
Очевидный недостаток такого подхода в том, что нужно помнить о дополнительных круглых скобках.  Другие решения —  использовать различные макросы (DEBUG1, DEBUG2, и т.п.) в зависимости от количества аргументов, или манипулировать запятыми:
#define DEBUG(args) (printf(«DEBUG: «), printf(args)) #define _ , DEBUG(«i = %d» _ i)
Часто предпочтительнее использовать настоящую функцию, которая стандартным способом может использовать переменное число аргументов. См. вопросы 7.1    и 7.2.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *