Где в объявлении может быть указан спецификатор класса хранения?
Например, рассмотрим спецификатор класса static
хранения. Вот несколько примеров действительного и плохо сформированного использования этого спецификатора класса хранения:
static int a; // valid
int static b; // valid
static int* c; // valid
int static* d; // valid
int* static e; // ill-formed
static int const* f; // valid
int static const* g; // valid
int const static* h; // valid
int const* static i; // ill-formed
typedef int* pointer;
static pointer j; // valid
pointer static k; // valid
(Декларации, помеченные как "действительные", были приняты Visual С++ 2012, g++ 4.7.2 и Clang++ 3.1. Объявления, помеченные как "плохо сформированные", были отклонены всеми этими компиляторами.)
Это кажется странным, потому что спецификатор класса хранения применяется к объявленной переменной. Это объявленная переменная static
, а не тип объявленной переменной. Почему e
и i
плохо сформированы, но k
хорошо сформирован?
Каковы правила, которые определяют правильное размещение спецификаторов класса хранения? Хотя в этом примере я использовал static
, этот вопрос относится ко всем спецификациям класса хранения. Предпочтительно, полный ответ должен привести соответствующие разделы стандарта языка С++ 11 и объяснить их.
Ответы
Ответ 1
В целом, где угодно в спецификаторе декларации (см. раздел 7.1 в ИСО/МЭК 14882-2012), т.е. до *
. Квалификаторы после *
связаны с объявлением указателя, а не с спецификатором типа, и static
не имеет смысла в контексте декларатора указателя.
Рассмотрим следующие случаи:
Вы можете объявить нормальный int и указатель на int в том же списке объявлений, как это:
int a, *b;
это потому, что спецификатор типа int
, тогда у вас есть два объявления с использованием этого спецификатора типа int
, a
и декларатора указателя *a
, который объявляет указатель на int
. Теперь рассмотрим:
int a, static b; // error
int a, *static b; // error
int a, static *b; // error
которые должны выглядеть не так (как они есть), и причина (как определено в разделах 7.1 и 8.1) заключается в том, что C и С++ требуют, чтобы ваши спецификаторы хранилища шли с вашим спецификатором типа, а не в вашем деклараторе.
Итак, теперь должно быть ясно, что следующее неверно, так как вышеупомянутые три также неверны:
int *static a; // error
В последнем примере
typedef int* pointer;
static pointer j; // valid
pointer static k; // valid
оба действительны и оба эквивалентны, потому что тип pointer
определяется как спецификатор типа, и вы можете поместить свой спецификатор типа и хранилище specifeir в любом порядке. Обратите внимание, что они оба эквивалентны и будут эквивалентны выражению
static int *j;
static int *k;
или
int static *j;
int static *k;
Ответ 2
Если вы используете "Золотое правило" (что также не относится только к указателям), это естественно, интуитивно, и это избегает много ошибок и ловушек при объявлении переменных в C/С++. "Золотое правило" не должно быть нарушено (есть редкие исключения, например const
, применяемые к массивам typedefs, которые распространяют const
на базовый тип и ссылки, которые поставляются с С++).
K & R, Приложение A, Раздел 8.4, Значение Declarators:
Каждый декларатор считается утверждением, что при построении той же формы, что и декларатор в выражении, он дает объект указанного типа и класс хранения.
Чтобы объявить переменную в C/С++, вы действительно должны думать о выражении, которое вы должны применить к нему, чтобы получить базовый тип.
1) Должно быть имя переменной
2) Затем приходит выражение как действительное * из инструкции объявления, применяемое к имени переменной
3) Затем появляется оставшаяся информация и свойства объявления типа базового типа и хранилища
Хранение не является характеристикой, которую вы всегда можете отнести к результату выражений, например, в противоположность константе. Это имеет смысл только при объявлении. Поэтому хранилище должно прибывать куда-то еще, а не в 2.
int * const *pp;
/*valid*/
int * static *pp;
/*invalid, this clearly shows how storage makes no sense for 2 and so breaks */
/*the golden rule. */
/*It not a piece of information that goes well in the middle of a expression.*/
/*Neither it a constraint the way const is, it just tells the storage of */
/*what being declared. */
Я думаю, что K & R хотел, чтобы мы использовали инвертированные рассуждения при объявлении переменных, это часто не обычная привычка. При использовании он избегает большинства сложных ошибок и трудностей декларирования.
* valid не в строгом смысле, поскольку некоторые изменения происходят, как x [], x [размер, не индексирование], constness и т.д. Итак, 2 - это выражение, которое хорошо отображает (для использования декларации), "та же форма", которая отражает использование переменной, но not строго.
Бонус Золотого правила для непосвященных
#include <iostream>
int (&f())[3] {
static int m[3] = {1, 2, 3};
return m;
}
int main() {
for(int i = 0; i < sizeof(f()) / sizeof(f()[0]); ++i)
std::cout << f()[i] << std::endl;
return 0;
}
Чтение &
в декларациях работает почти так же, как чтение const
(&
не может быть помещено перед типом). В этом контексте это не операция для получения адреса и может быть визуализирована как новое ключевое слово, например ref
или reference
.
-
f()
: f
является функцией
-
&
return: его return - это ссылка
- ссылка
[3]
: ссылка относится к массиву из 3 элементов
-
int
массив [i]: элемент - это int
Итак, у вас есть функция, которая возвращает ссылку на массив из 3 целых чисел, и поскольку у нас есть правильная информация о времени компиляции массива, мы можем проверить ее с помощью sizeof
anytime =)
Конечный золотой наконечник для всего, что может быть помещено перед типом, когда в нескольких объявлениях оно применяется ко всем переменным одновременно и поэтому не может применяться индивидуально.
Этот const
нельзя поставить перед int
:
int * const p;
Итак, справедливо следующее:
int * const p1, * const p2;
Это может быть:
int const *p; // or const int *p;
Таким образом, следующее недопустимо:
int const *p1, const *p2;
Сменный const
должен применяться для всех:
int const *p1, *p2; // or const int *p1, *p2;
Соглашения об условных обозначениях
Из-за этого я всегда ставил все, что не может быть поставлено перед типом, ближе к переменной (int *a
, int &b
) и все, что может, прежде чем поставить (volatile int c
). Если бы я мог приклеить *const
к имени переменной, например, с помощью *&
, я бы сделал это.
Ответ 3
Per 7.1, [упрощенная] структура объявления С++
decl-specifier-seq init-declarator-list;
В соответствии с 7.1/1 спецификаторы класса хранения относятся к начальной "общей" части decl-specifier-seq
.
Per 8/1, init-declarator-list
- это последовательность деклараторов.
В 8/4 часть *
декларации указателя является частью отдельного декларатора в этой последовательности. Это немедленно означает, что все, что следует за *
, является частью этого индивидуального декларатора. Вот почему некоторые места размещения спецификаторов класса хранилища недействительны. Синтаксис Declarator не позволяет включать спецификаторы класса хранения.
Обоснование довольно очевидно: поскольку спецификаторы класса хранения должны применяться ко всем деклараторам во всей декларации, они помещаются в "общую" часть декларации.
Я бы сказал, что более интересная (и несколько связанная) ситуация имеет место с спецификаторами, которые могут присутствовать как в decl-specifier-seq
, так и в отдельных деклараторах, таких как спецификатор const
. Например, в следующем объявлении
int const *a, *b;
делает const
применимо ко всем деклараторам или только к первому? Грамматика диктует прежнюю интерпретацию: const
применяется ко всем деклараторам, т.е. Является частью decl-specifier-seq
.