21

たとえば、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 言語標準の関連セクションを引用して説明する必要があります。

4

3 に答える 3

18

要約すると、宣言指定子 (ISO/IEC 14882-2012 のセクション 7.1 を参照) の任意の場所、つまり . の前*。の後の修飾子*は、型指定子ではなくポインター宣言子に関連付けられており、ポインター宣言子staticのコンテキスト内では意味がありません。

次のケースを考えてみましょう: 次のように、通常の int と int へのポインターを同じ宣言リストで宣言できます。

int a, *b;

これは、型指定子がintである場合、その型指定子inta、および へのポインター*aを宣言するポインター宣言子を使用する 2 つの宣言があるためintです。今考えてみましょう:

int a, static b;  // error
int a, *static b; // error
int a, static *b; // error

これは間違っているように見えるはずです (セクション 7.1 および 8.1 で定義されているように) 理由は、C および C++ では、記憶域指定子が宣言子ではなく、型指定子と一緒に使用する必要があるためです。したがって、上記の 3 つも間違っているため、次のことも間違っていることは明らかです。

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;
于 2012-10-25T17:38:16.120 に答える
5

7.1 によると、C++ 宣言の [簡略化された] 構造は次のとおりです。

decl-specifier-seq init-declarator-list;

7.1/1 では、ストレージ クラス指定子は最初の「共通」部分に属しdecl-specifier-seqます。

8/1 に従って、init-declarator-list宣言子のシーケンスです。

8/4 に従って、*ポインター宣言の部分は、そのシーケンス内の個々の宣言子の一部です。*これは、a に続くすべてのものがその個々の宣言子の一部であることを即座に意味します。これが、一部のストレージ クラス指定子の配置が無効である理由です。宣言子の構文では、ストレージ クラス指定子を含めることはできません。

理論的根拠はかなり明白です。記憶域クラス指定子は宣言全体のすべての宣言子に適用されるはずなので、宣言の「共通」部分に配置されます。


私は、指定子のように、個々の宣言子の両方 に存在できる指定子を使用すると、より興味深い (そして多少関連する) 状況が発生すると思います。たとえば、次の宣言ではdecl-specifier-seqconst

int const *a, *b;

すべての宣言子に適用されますconstか、それとも最初の宣言子のみに適用されますか? 文法は、前者の解釈を指示します。constこれはすべての宣言子に適用されます。つまり、 の一部ですdecl-specifier-seq

于 2012-10-25T21:32:01.917 に答える
4

「黄金律」 (ポインターだけに適用されるわけではありません)を採用すると、自然に直感的に従うことができ、C/C++ で変数を宣言する際の多くの間違い落とし穴を回避できます。「黄金律」に違反するべきではありません (基本型にconst伝播する配列の typedef に適用されるようなまれな例外constと、C++ に付属する参照があります)。

K&R、付録 A、セクション 8.4、宣言子の意味には次のように記載されています。

各宣言子は、宣言子と同じ形式の構造が式に現れると、指定された型とストレージ クラスのオブジェクトを生成するというアサーションと見なされます。

C/C++ で変数を宣言するには、基本型を取得するために変数に適用する必要がある式をよく考える必要があります。

1) 変数名が必要です

2) 次に、変数名に適用された宣言ステートメントから有効な*として式が出てきます

3) 次に、基本型やストレージなどの宣言の残りの情報とプロパティが続きます

ストレージは、たとえば constness とは対照的に、式の結果に常に付与できる特性ではありません。宣言時にのみ意味があります。したがって、ストレージは 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.                                                       */

K&R は、変数を宣言するときに逆推論を使用することを望んでいたと思いますが、それは一般的な習慣ではないことがよくあります。使用すると、複雑な宣言の間違いや困難のほとんどを回避できます。

*valid は厳密な意味ではありません。x[]、x[size、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です
  • &return : そのreturn参照です
  • reference[3] :参照3 つの要素の配列への参照です
  • int array[i] :要素は 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/でさらに詳しく説明しています。

于 2012-10-25T19:45:59.383 に答える