web-dev-qa-db-ja.com

複雑なconst宣言を読み取るための簡単なルールは?

複雑なポインタ宣言を読み取るには、 右左規則 があります。

しかし、このルールはconst修飾子の読み方については触れていません。

たとえば、単純なポインタ宣言では、constはいくつかの方法で適用できます。

_char *buffer; // non-const pointer to non-const memory
const char *buffer; // non-const pointer to const memory
char const *buffer; // equivalent to previous declartion
char * const buffer = {0}; // const pointer to non-const memory
char * buffer const = {0}; // error
const char * const buffer = {0}; // const pointer to const memory
_

次に、ポインタ宣言のポインタでのconstの使用についてはどうですか?

_char **x; // no const;
const char **x;
char * const *x;
char * * const x;
const char * const * x;
const char * * const x;
const char * const * const x;
_

そして、それらの宣言を読むための簡単なルールは何ですか?どの宣言が意味がありますか?

時計回り/スパイラルルール は適用可能ですか?

2つの実世界の例

メソッド _ASTUnit::LoadFromCommandLine_ は_const char **_を使用してコマンドライン引数を提供します(llvm clangソース内)。

getopt() の引数ベクトルパラメータは次のように宣言されます。

_int getopt(int argc, char * const argv[], const char *optstring);
_

この場合、_char * const argv[]_は_char * const * argv_と同等です。

どちらの関数も同じ概念(引数を提供するための文字列へのポインターのベクトル)を使用しており、宣言が異なるため、明白な質問は次のとおりです。なぜ違うのですか?もう1つより意味がありますか?

意図は次のとおりです。const修飾子は、関数がこのベクトルの文字列を操作せず、ベクトルの構造を変更しないことを指定する必要があります。

43
maxschlepzig

const修飾子は取るに足らないものです。前に何もない場合を除いて、前にあるものを変更します。そう:

_char const* buffer;  // const modifies char
char* const buffer;  // const modifies *
_

、など。一般的に、constの前に何もないフォームは避けるのが最善ですが、実際にはそれらが表示されるため、constの前に型がない場合は、論理的にその後ろに移動する必要があることを覚えておく必要があります。最初のタイプ。そう:

_const char** buffer;
_

実際には:

_char const** buffer;
_

、つまりconst charへのポインタへのポインタ。

最後に、関数宣言では、_[]_ afterは_*_ beforeとして読み取ります。 (この場合も、この誤解を招くような表記は避けた方がいいでしょうが、それを目にすることになるので、対処する必要があります。)

_char * const argv[],  //  As function argument
_

です:

_char *const * argv,
_

charへのconstポインタへのポインタ。

65
James Kanze

(質問の他の側面に集中しようとする)

const宣言の経験則 は、右から左に読み取ることであり、constは次のトークンを変更します。例外:宣言の始めにconstは前のトークンを変更します。

この例外の背後にある根拠 -基本的な宣言の場合_const char c_は_char const c_よりも自然な人を探しますが、_const char c_の前駆形であると報告されています最終的なconstルールよりも古い。

getopt

_int getopt(int argc, char * const argv[], const char *optstring);
_

または

_int getopt(int argc, char * const * argv, const char *optstring);
_

つまり、argvは、非const文字列へのポインターのconstベクトルへのポインターです。

しかし、次の宣言が期待されます。

_int getopt(int argc, char const * const * argv, const char *optstring);
_

(const文字列へのconstベクトルへのポインター)

getopt()は、argvを介して参照される文字列を変更することを想定していないためです。

少なくとも_char **_(main()で使用される)は自動的に_char * const * argv_に変換されます。

クラン

_ASTUnit::LoadFromCommandLine(...,  const char **argv, ...);
_

つまり、argvは、const文字列へのポインターの非const配列へのポインターです。

再び、上記と同じ理由で_const char * const *argv_を期待します。

ただし、_char **_ 変換しない から_const char **_に変換されるため、これはより顕著になります。

_int main(int argc, char **argv) {
  const char **x = argv; // Compile error!
  return 0;
}
_

コンパイルエラーが発生します。

_int main(int argc, char **argv) {
  char * const *x = argv;
  return 0;
}
_

そして

_int main(int argc, char **argv) {
  const char * const *x = argv;
  return 0;
}
_

しない。

6
maxschlepzig