Namespaces
Variants

Declarations

From cppreference.net

Декларация — это конструкция языка C, которая вводит один или несколько идентификаторов в программу и определяет их значение и свойства.

Объявления могут появляться в любой области видимости. Каждое объявление заканчивается точкой с запятой (так же как оператор ) и состоит из двух (до C23) трёх (начиная с C23) отдельных частей:

specifiers-and-qualifiers declarators-and-initializers  (необязательно) ; (1)
attr-spec-seq specifiers-and-qualifiers declarators-and-initializers ; (2) (начиная с C23)
attr-spec-seq ; (3) (начиная с C23)

где

specifiers-and-qualifiers - разделенный пробелами список, в любом порядке,
  • спецификаторы типа:


declarators-and-initializers - разделенный запятыми список деклараторов (каждый декларатор предоставляет дополнительную информацию о типе и/или идентификатор для объявления). Деклараторы могут сопровождаться инициализаторами . enum , struct , и union объявления могут опускать деклараторы , в этом случае они только вводят константы перечисления и/или теги.
attr-spec-seq - (C23) опциональный список атрибутов , применяемых к объявленным сущностям, или формирует объявление атрибута, если указан отдельно.
1,2) Простое объявление. Вводит один или несколько идентификаторов, которые обозначают объекты, функции, теги структур/объединений/перечислений, определения типов или константы перечислений.
3) Объявление атрибута. Не объявляет никаких идентификаторов и имеет определяемое реализацией значение, если значение не указано стандартом.

Например,

int a, *b=NULL; // "int" — спецификатор типа,
                // "a" — декларатор
                // "*b" — декларатор, а NULL — его инициализатор
const int *f(void); // "int" — спецификатор типа
                    // "const" — квалификатор типа
                    // "*f(void)" — декларатор
enum COLOR {RED, GREEN, BLUE} c; // "enum COLOR {RED, GREEN, BLUE}" — спецификатор типа
                                 // "c" — декларатор

Тип каждого идентификатора, введённого в объявлении, определяется комбинацией типа, указанного спецификатором типа и модификаций типа, применяемых его декларатором . Тип переменной также может быть выведен, если используется auto спецификатор. (начиная с C23)

Атрибуты (начиная с C23) могут появляться в specifiers-and-qualifiers , и в этом случае они применяются к типу, определяемому предшествующими спецификаторами.

Содержание

Деклараторы

Каждый декларатор представляет собой один из следующих:

identifier attr-spec-seq  (необязательно) (1)
( declarator ) (2)
* attr-spec-seq  (необязательно) qualifiers  (необязательно) declarator (3)
noptr-declarator [ static (необязательно) qualifiers  (необязательно) expression ]

noptr-declarator [ qualifiers  (необязательно) * ]

(4)
noptr-declarator ( parameters-or-identifiers ) (5)
1) идентификатор, который вводит этот декларатор.
2) любой декларатор может быть заключен в круглые скобки; это требуется для введения указателей на массивы и указателей на функции.
3) объявитель указателя : объявление S * cvr D ; объявляет D как cvr -квалифицированный указатель на тип, определяемый S .
4) декларатор массива : объявление S D [ N ] объявляет D как массив из N объектов типа, определяемого S . noptr-declarator - это любой другой декларатор, кроме непомещенного в скобки указательного декларатора.
5) декларатор функции : объявление S D ( params ) объявляет D как функцию, принимающую параметры params и возвращающую S . noptr-declarator — это любой другой декларатор, кроме непомещенного в скобки указательного декларатора.

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

struct C
{
    int member; // "int" - это спецификатор типа
                // "member" - это декларатор
} obj, *pObj = &obj;
// "struct C { int member; }" - это спецификатор типа
// декларатор "obj" определяет объект типа struct C
// декларатор "*pObj" объявляет указатель на C,
// инициализатор "= &obj" предоставляет начальное значение для этого указателя
int a = 1, *p = NULL, f(void), (*pf)(double);
// спецификатор типа - "int"
// декларатор "a" определяет объект типа int
//   инициализатор "=1" предоставляет его начальное значение
// декларатор "*p" определяет объект типа указатель на int
//   инициализатор "=NULL" предоставляет его начальное значение
// декларатор "f(void)" объявляет функцию, принимающую void и возвращающую int
// декларатор "(*pf)(double)" определяет объект типа указатель
//   на функцию, принимающую double и возвращающую int
int (*(*foo)(double))[3] = NULL;
// спецификатор типа - int
// 1. декларатор "(*(*foo)(double))[3]" является декларатором массива:
//    объявленный тип - "/вложенный декларатор/ массив из 3 int"
// 2. вложенный декларатор - "*(*foo)(double))", который является декларатором указателя
//    объявленный тип - "/вложенный декларатор/ указатель на массив из 3 int"
// 3. вложенный декларатор - "(*foo)(double)", который является декларатором функции
//    объявленный тип - "/вложенный декларатор/ функция, принимающая double и возвращающая
//        указатель на массив из 3 int"
// 4. вложенный декларатор - "(*foo)", который является (в скобках, как требует
//        синтаксис декларатора функции) декларатором указателя.
//    объявленный тип - "/вложенный декларатор/ указатель на функцию, принимающую double
//        и возвращающую указатель на массив из 3 int"
// 5. вложенный декларатор - "foo", который является идентификатором.
// Объявление вводит идентификатор "foo" для ссылки на объект типа
// "указатель на функцию, принимающую double и возвращающую указатель на массив из 3 int"
// Инициализатор "= NULL" предоставляет начальное значение этого указателя.
// Если "foo" используется в выражении в форме декларатора, его тип будет
// int.
int x = (*(*foo)(1.2))[0];

Конец каждого декларатора, который не является частью другого декларатора, является точкой следования .

Во всех случаях, attr-spec-seq является необязательной последовательностью атрибутов (начиная с C23) . Когда она появляется непосредственно после идентификатора, она применяется к объявляемому объекту или функции.

Определения

Определение — это объявление, которое предоставляет всю информацию об идентификаторах, которые оно объявляет.

Каждое объявление enum или typedef является определением.

Для функций объявление, которое включает тело функции, является определением функции :

int foo(double); // объявление
int foo(double x) { return x; } // определение

Для объектов объявление, которое выделяет память ( автоматическая или статическая , но не extern), является определением, тогда как объявление, которое не выделяет память ( внешнее объявление ), не является определением.

extern int n; // объявление
int n = 10; // определение

Для структур и объединений объявления, которые определяют список членов, являются определениями:

struct X; // объявление
struct X { int n; }; // определение

Повторное объявление

Объявление не может вводить идентификатор, если другое объявление для того же идентификатора в той же области видимости появляется ранее, за исключением случаев, когда

  • Объявления объектов с линковкой (внешней или внутренней) могут повторяться:
extern int x;
int x = 10; // OK
extern int x; // OK
static int n;
static int n = 10; // OK
static int n; // OK
  • Non-VLA typedef может повторяться до тех пор, пока он обозначает один и тот же тип:
typedef int int_t;
typedef int int_t; // OK
struct X;
struct X { int n; };
struct X;

Эти правила упрощают использование заголовочных файлов.

Примечания

В C89 объявления внутри любого составного оператора (область видимости блока) должны появляться в начале блока, перед любыми операторами .

Также в C89 функции, возвращающие int , могут быть неявно объявлены с помощью оператора вызова функции , и параметры функций типа int не должны быть объявлены при использовании старых определений функций .

(до C99)

Пустые деклараторы запрещены; простая декларация должна иметь как минимум один декларатор или объявлять как минимум один тег struct/union/enum, или вводить как минимум одну константу перечисления.

Если какая-либо часть декларатора является декларатором массива переменной длины (VLA), то тип всего декларатора называется "вариативно-модифицированным типом". Типы, определенные на основе вариативно-модифицированных типов, также являются вариативно модифицированными (VM).

Объявления любых вариативно-модифицированных типов могут появляться только в области видимости блока или области видимости прототипа функции и не могут быть членами структур или объединений. Хотя VLA может иметь только автоматическую или выделенную длительность хранения , VM-тип, такой как указатель на VLA, может быть статическим. Существуют другие ограничения на использование VM-типов, см. goto , switch . longjmp

(начиная с C99)

static_asserts считаются объявлениями с точки зрения грамматики C (поэтому они могут появляться везде, где может появляться объявление), но они не вводят никаких идентификаторов и не следуют синтаксису объявления.

(since C11)

Атрибуты также считаются объявлениями (поэтому они могут появляться везде, где может появляться объявление), но они не вводят никаких идентификаторов. Одиночная ; без attr-spec-seq не является объявлением атрибута, а является оператором.

(начиная с C23)

Ссылки

  • Стандарт C23 (ISO/IEC 9899:2024):
  • 6.7 Объявления (стр.: TBD)
  • Стандарт C17 (ISO/IEC 9899:2018):
  • 6.7 Объявления (стр: 78-105)
  • Стандарт C11 (ISO/IEC 9899:2011):
  • 6.7 Объявления (стр: 108-145)
  • Стандарт C99 (ISO/IEC 9899:1999):
  • 6.7 Объявления (стр: 97-130)
  • Стандарт C89/C90 (ISO/IEC 9899:1990):
  • 3.5 Объявления

Смотрите также

C++ documentation для Declarations