Где в объявлении может быть указан спецификатор класса хранения?

Например, давайте рассмотрим 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 C ++ 2012, g ++ 4.7.2 и Clang ++ 3.1. Объявления, помеченные как «неправильно сформированные», были отклонены всеми этими компиляторами.)

Это кажется странным, потому что спецификатор класса хранения применяется к объявленной переменной. Это объявленная переменная, которая является static, а не тип объявленной переменной. Почему e а также i плохо сформирован, но k хорошо сформирован?

Какие правила определяют правильное размещение спецификаторов класса хранения? Пока я использовал static в этом примере этот вопрос относится ко всем спецификаторам классов хранения. Предпочтительно, чтобы в полном ответе содержались ссылки на соответствующие разделы стандарта языка C ++ 11 и их пояснения.

19

Решение

Таким образом, в любом месте спецификатора декларации (см. Раздел 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 и C ++ требуют, чтобы ваши описатели хранилища соответствовали вашему описателю типа, а не вашему декларатору.
Так что теперь должно быть ясно, что следующее также неверно, так как вышеупомянутые три также неверны:

int *static a; // error

Ваш последний пример,

typedef int* pointer;
static pointer j;    // valid
pointer static k;    // valid

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

static int *j;
static int *k;

или же

int static *j;
int static *k;
17

Другие решения

Согласно 7.1, [упрощенная] структура объявления C ++ имеет вид

decl-specifier-seq init-declarator-list;

Согласно 7.1 / 1, спецификаторы класса хранения принадлежат в начальной «общей» части decl-specifier-seq,

За 8/1, init-declarator-list это последовательность деклараторов.

За 8/4 г. * Часть объявления указателя является частью отдельного объявления в этой последовательности. Это сразу означает, что все, что следует за * является частью этого индивидуального декларатора. Вот почему некоторые из ваших мест размещения спецификаторов класса хранения недопустимы. Синтаксис объявления не позволяет включать спецификаторы класса хранения.

Обоснование довольно очевидно: так как спецификаторы класса хранения должны применяться к все деклараторы во всей декларации, они помещаются в «общую» часть декларации.


Я бы сказал, что более интересная (и несколько связанная) ситуация имеет место со спецификаторами, которые могут присутствовать в и то и другое decl-specifier-seq и отдельные деклараторы, как const спецификатор. Например, в следующем объявлении

int const *a, *b;

делает const относится ко всем деклараторам или только к первому? Грамматика диктует прежнюю интерпретацию: const применяется ко всем деклараторам, т. е. является частью decl-specifier-seq,

5

Если вы нанимаете «Золотое правило» (что также не относится только к указателям) это следует естественно, интуитивно, и это позволяет избежать много ошибок а также подводные камни при объявлении переменных в C / C ++. «Золотое правило» не должно нарушаться (есть редкие исключения, такие как const применяется к массиву typedefs, который распространяется const на базовый тип и ссылки, которые пришли с C ++).

К&R, Приложение A, раздел 8.4, Значение деклараторов гласит:

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

Чтобы объявить переменную в C / 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's not a piece of information that goes well in the middle of a expression.*/
/*Neither it's a constraint the way const is, it just tells the storage of     */
/*what's being declared.                                                       */

Я думаю К&R хотел, чтобы мы использовали инвертированные рассуждения при объявлении переменных, это часто не обычная привычка. При использовании он позволяет избежать большинства сложных ошибок и трудностей в декларации.

* valid не в строгом смысле, так как встречаются некоторые вариации, такие как x [], x [size, not indexing], constness и т. д. Итак, 2 — это выражение, которое карты хорошо (для использования декларации), «та же форма», та, которая отражает использование переменной, но не строго.

Бонус Золотого правила для непосвященных

#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;
}

В контексте деклараций, & это не операция, чтобы получить адрес, она просто говорит, что ссылка.

  • f(): f это функция
  • &вернуть: его вернуть это ссылка
  • ссылка[3]: ссылка это к массив из 3 элементов
  • int массив [я]: элемент это инт

Итак, у вас есть функция, которая возвращает ссылку на массив из 3 целых чисел, и, поскольку у нас есть правильная информация времени компиляции о размере массива, мы можем проверить это с помощью sizeof в любое время =)

Последний золотой совет — для всего, что может быть помещено перед типом, когда в нескольких объявлениях оно применяется ко всем переменным одновременно, и поэтому не может применяться индивидуально.

это 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).

Есть много на эту тему в http://nosubstance.me/post/constant-bikeshedding/.

3
По вопросам рекламы [email protected]