Раздел «Язык Си».CfaqDeclaration:

Декларация и инициализация (краткий список)

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

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

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

Хотя тип char или unsigned char может использоваться как целочисленный тип наименьшего размера, от этого больше вреда, чем пользы из-за непредсказуемых перемен знака и возрастающего размера программы.

Эти правила, очевидно, не применимы к адресам переменных, поскольку адрес должен иметь совершенно определенный тип.

Если необходимо объявить переменную определенного размера, (единственной причиной тут может быть попытка удовлетворить внешним требованиям к организации памяти; см.,кроме того, вопрос 17.3), непременно изолируйте объявление соответствующим typedef.

Если вам нужны числа, большие чем гарантируют встроенные типы, или вам нужны вычисления с произвольной точностью (или "multiple precision"); см. вопрос 18.15d.

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

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

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

Программисты, желающие писать мобильные программы, должны, следовательно, изолировать 64-битные типы с помощью средства typedef. Разработчики компиляторов, чувствующие необходимость ввести новый целочисленный тип большего размера, должны объявить его как "имеющий по крайней мере 64 бит" (это действительно новый тип, которого нет в традиционном С), а не как "имеющий точно 64 бит".

10.3 У меня совсем не получается определение связанного списка.

Q: У меня совсем не получается определение связанного списка. Я пишу
typedef struct
{
     char *item;
     NODEPTR next;
} *NODEPTR;

но компилятор выдает сообщение об ошибке. Может ли структура в С содержать ссылку на себя?

A: Структуры в С, конечно же, могут содержать указатели на себя; обсуждение этого вопроса и пример в параграфе 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?

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

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

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 Я моделирую Марковский процесс с конечным числом состояний

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

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

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

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

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

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

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

A: Прежде всего заметим, что хотя может быть много деклараций (и во многих файлах) одной "глобальной" (строго говоря "внешней" ) переменной, (или функции), должно быть всего одно определение. (Определение - это такая декларация, при которой действительно выделяется память для переменной, и присваивается, если нужно, начальное значение). Лучше всего поместить определение в какой-то главный (для программы или ее части) .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 при декларации функции?

Q: Что означает ключевое слово extern при декларации функции? A: Устарело. слово extern при декларации функции может быть использовано из соображений хорошего стиля для указания на то, что определение функции, возможно, находится в другом файле. Формально между
extern int f();
и
int f();
нет никакой разницы.

Смотри: ANSI Разд. 3.1.2.2.

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

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

A: Используйте нечто такое

extern int func();
int (*fp)() =   func;
Когда имя функции появляется в выражении, но функция не вызывается (то есть, за именем функции не следует "(" ), оно "сворачивается", как и в случае массивов, в указатель (т.е. неявным образом записанный адрес).

Явное объявление функции обычно необходимо, так как неявного объявления внешней функции в данном случае не происходит (опять-таки из-за того, что за именем функции не следует "(" ).

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

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

A: По первоначальному замыслу создателя С указатель на функцию должен был "превратиться" в настоящую функцию с помощью оператора * и дополнительной пары круглых скобок для правильной интерпретации.

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?

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

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