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

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

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

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

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

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

4. Выражения

5. ANSI C

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

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

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

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

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

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

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

13. Lint.

14. Стиль.

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

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

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

Декларации

10.1    Какой тип целочисленной переменной использовать?

О:    Если могут потребоваться большие числа,    (больше    32767 или меньше -32767), используйте тип long. Если нет, и важна экономия памяти (большие массивы или много структур), используйте short. Во всех остальных случаях используйте int. Если    важно точно определить момент переполнения и/или знак числа не имеет значения, используйте соответствующий тип unsigned. (Но будьте внимательны при совместном использовании типов signed и unsigned в выражениях). Похожие соображения применимы при выборе между float и double.
Хотя тип char или unsigned char может использоваться как целочисленный тип наименьшего размера, от этого больше вреда, чем пользы из-за непредсказуемых перемен знака и возрастающего размера программы.
Эти правила, очевидно, не применимы к адресам переменных, поскольку адрес должен иметь совершенно определенный тип.
Если необходимо объявить переменную _определенного_ размера, (единственной причиной тут может быть попытка удовлетворить внешним требованиям к организации памяти; см.,кроме того, вопрос 17.3), непременно изолируйте объявление соответствующим typedef.

10.2    Каким должен быть новый    64-битный тип на новых 64-битных машинах?

О:      Некоторые поставщики С компиляторов для 64-битных машин поддерживают тип long int длиной 64 бита. Другие же, опасаясь, что слишком многие уже написанные программы зависят от sizeof(int) == sizeof(long) == 32 бита, вводят новый 64-битный тип long long (или __longlong).
Программисты, желающие писать мобильные    программы, должны, следовательно, изолировать 64-битные типы с помощью средства typedef. Разработчики компиляторов, чувствующие необходимость ввести новый целочисленный тип большего размера, должны объявить его    как «имеющий по крайней мере    64 бит»    (это действительно новый тип, которого нет в традиционном С), а не    как «имеющий точно 64 бит».

10.3    У меня совсем не получается определение    связанного списка. Я пишу typedef struct { char *item; NODEPTR next; } *NODEPTR; но компилятор выдает сообщение об ошибке. Может    структура в С содержать ссылку на себя?

О:    Структуры в С, конечно же, могут содержать указатели на    себя; обсуждение этого вопроса и пример в параграфе 6.5 K&R вполне проясняют этот вопрос. В приведенном тексте проблема состоит в том, что определение NODEPTR не закончено в том месте, где объявлется член структуры «next». Для исправления, снабдите сначала структуру тегом («struct node»). Далее объявите «next» как «struct node *next;», и/или поместите декларацию typedef целиком до или целиком после объявления структуры.  Одно из возможных решений будет таким:
struct node { char *item; struct node *next; };
typedef struct node *NODEPTR;
Есть по крайней мере три других одинаково правильных способа сделать то же самое. Сходная    проблема, которая решается примерно так    же, может возникнуть при попытке определить с помощью средства typedef пару cсылающихся друг на друга структур.
Смотри: K&R I Разд. 6.5 c. 101; K&R II Разд. 6.5 c. 139; H&S Разд. 5.6.1 c. 102; ANSI Разд. 3.5.2.3 .

10.4    Как объявить массив из N указателей на функции, возвращающие указатели на функции возвращающие указатели на char?

О:      Есть по крайней мере три варианта ответа:

1.  char *(*(*a[N])())();
2.  Писать декларации    по шагам, используя typedef:
typedef char *pc;   /* указатель на char */ typedef pc fpc();   /* функция,возвращающая указатель на char */ typedef fpc *pfpc;  /* указатель на.. см. выше */ typedef pfpc fpfpc();   /* функция, возвращающая… */ typedef fpfpc *pfpfpc;  /* указатель на… */ pfpfpc a[N];            /* массив… */
3. Использовать программу cdecl, которая переводит с английского на    С и наоборот.
cdecl> declare a as array of pointer to function returning pointer to function returning pointer to char char *(*(*a[])())()
cdecl может также объяснить сложные декларации, помочь при явном приведении типов, и, для случая сложных деклараций, вроде только что разобранного, показать набор круглых скобок, в которые заключены аргументы. Версии cdecl можно найти в comp.sources.unix (см. вопрос 17.12) и в K&R II.  Любая хорошая книга по С должна объяснять, как для понимания сложных деклараций, читать их «изнутри наружу», («декларация напоминает использование»).
Смотри: K&R II Разд. 5.12 c. 122; H&S Разд. 5.10.1 c. 116.

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

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

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

О:    Подразумевается, что функции, вызываемые без декларации    в области видимости (или до такой    декларации), возвращают    значение типа int. Это приведет к противоречию, если впоследствии функция декларирована иначе. Если функция возвращает нецелое значение, она должна быть объявлена до того как будет вызвана.
Смотри: K&R I Разд. 4.2 c. 70; K&R II Разд. 4.2 c. 72; ANSI Разд. 3.3.2.2 .

10.7    Как наилучшим образом декларировать и определить глобальные переменные?

О:    Прежде всего заметим, что хотя может быть много    _деклараций_ (и    во многих файлах) одной «глобальной» (строго говоря «внешней» ) переменной, (или функции), должно быть всего одно _определение_. (Определение — это такая декларация, при которой действительно выделяется память для переменной, и присваивается, если нужно, начальное значение).  Лучше всего поместить определение в какой-то главный (для программы или ее части) .c файл, с внешней декларацией в головном файле .h, который при необходимости подключается с помощью #include. Файл, в котором находится определение переменной, также должен включать головной файл с внешней декларацией, чтобы компилятор мог проверить соответствие декларации и определения.
Это правило обеспечивает высокую мобильность программ и    находится в согласии с требованиями стандарта ANSI C. Заметьте, что многие компиляторы и компоновщики в системе UNIX используют «общую модель», которая разрешает многократные определения без инициализации. Некоторые весьма странные компиляторы могут требовать явной инициализации, чтобы отличить определение от внешней декларации.
С помощью препроцессорного трюка можно устроить так, что декларация будет сделана лишь однажды, в головном файле, и она c помощью #define «превратится» в определение точно при одном включении головного файла.
Смотри: K&R I Разд. 4.5 c. 76-7; K&R II Разд. 4.4 c. 80-1; ANSI Разд. 3.1.2.2 (особенно Rationale), Разд. 3.7, 3.7.2, Разд. F.5.11; H&S Разд. 4.8 c. 79-80; CT&P Разд. 4.2 c. 54-56.

10.8    Что означает ключевое слово extern при декларации функции?

О:      слово extern при декларации функции может быть использовано из соображений хорошего стиля для указания на то, что определение функции, возможно, находится в другом файле. Формально между
extern int f(); и int f();
нет никакой разницы.
Смотри: ANSI Разд. 3.1.2.2 .

10.9    Я, наконец, понял, как объвлять указатели на функции, но как их инициализировать?

О:    Используйте нечто такое
extern int func(); int (*fp)() =    func;
Когда имя функции появляется в выражении, но функция не    вызывается (то есть, за именем функции не следует «(» ), оно «сворачивается», как и в случае массивов, в указатель (т.е. неявным образом записанный адрес).
Явное объявление функции обычно необходимо, так как неявного объявления внешней функции в данном случае не происходит (опять-таки из-за того, что за именем функции не следует «(» ).

10.10    Я видел, что функции вызываются    с помощью указателей и просто как функции. В чем дело?

О:      По первоначальному замыслу создателя С указатель на функцию должен был «превратиться» в настоящую функцию с помощью оператора * и дополнительной пары круглых скобок для правильной интерпретации.
int r, func(), (*fp)() = func; r = (*fp)();
На это можно возразить, что функции всегда вызываются с помощью указателей, но что «настоящие» функции неявно превращаются в указатели (в выражениях, как это происходит при инициализациях) и это не приводит к каким-то проблемам. Этот довод, широко распространенный компилятором pcc и принятый стандартом ANSI, означает, что выражение
r = fp();
работает одинаково правильно, независимо от того, что такое fp — функция    или указатель на нее. (Имя всегда используется однозначно; просто невозможно сделать что-то другое    с указателем на    функцию, за которым следует список аргументов, кроме как    вызвать    функцию). Явное задание *    безопасно и все    еще разрешено (и рекомендуется, если важна совместимость со старыми компиляторами).
Смотри: ANSI Разд. 3.3.2.2 c. 41, Rationale c. 41.

10.11    Где может пригодиться ключевое слово auto?

О:    Нигде, оно вышло из употребления.

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

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