Constを知っていると思いますか?

翻訳者から:

2016年のhabr記事Cでの書き方のヒントに関するセンセーショナルな記事の著者であるMatt Stancliffによるブログの投稿の翻訳を提供します。

ここで、Mattはconst



修飾子の知識を共有しています。
反抗的な見出しにもかかわらず、おそらくここで説明されているものの多くはあなたに知られているでしょうが、何か新しいものもあることを望みます。

良い読書をしてください。



const



for Cを使用するためのすべてのルールを知っていると思いますか? もう一度考えてください。



定数の基本



スカラー変数



Cの単純なconst



ルールに精通している。

 const uint32_t hello = 3;
      
      





const



before hello



は、 コンパイル中に hello



が変更されないことを確認することを意味します。



hello



を変更またはオーバーライドしようとすると、コンパイラーはユーザーを停止します。

 clang-700.1.81: error: read-only variable is not assignable hello++; ~~~~~^ error: read-only variable is not assignable hello = 92; ~~~~~ ^ gcc-5.3.0: error: increment of read-only variable 'hello' hello++; ^ error: assignment of read-only variable 'hello' hello = 92; ^
      
      





さらに、Cはconst



が識別子の前にある限り、どこにあるかについてあまり心配していないため、 const uint32_t



宣言const uint32_t



uint32_t const



同一です。

 const uint32_t hello = 3; uint32_t const hello = 3;
      
      





プロトタイプのスカラー変数



次の関数のプロトタイプと実装を比較します。

 void printTwo(uint32_t a, uint64_t b); void printTwo(const uint32_t a, const uint64_t b) { printf("%" PRIu32 " %" PRIu64 "\n", a, b); }
      
      





const



修飾子を持つスカラーパラメーターがprintTwo()



関数の実装で指定されていて、プロトタイプに指定されていない場合、コンパイラーは誓いますか?



いや。



スカラー引数の場合、プロトタイプと関数の実装でconst



修飾子が一致しないことは完全に正常です。

なぜそれが良いのですか? すべてが非常に単純です。関数はスコープの外でb



b



変更できないため、 const



は渡すものに影響を与えません。 あなたのコンパイラは、それがa



b



コピーでa



ことを理解するのに十分賢いので、この場合、 const



有無はプログラムの物理的または精神的なモデルに影響を与えません。



コンパイラは、値によって関数にコピーされ、転送された変数の元の値は常に変更されないため、ポインターまたは配列ではないパラメーターのconst



修飾子の不一致を気にしません。



ただし、コンパイラーはポインターまたは配列であるパラメーターのconst



不一致について文句を言います。この場合、関数は渡されたポインターによって参照されるデータを操作できるからです。



配列



配列全体にconst



を指定できます。

 const uint16_t things[] = {5, 6, 7, 8, 9};
      
      





const



は、型宣言の後に指定することもできます。

 uint16_t const things[] = {5, 6, 7, 8, 9};
      
      





things[]



を変更しようとすると、コンパイラは次のことを停止します。

 clang-700.1.81: error: read-only variable is not assignable things[3] = 12; ~~~~~~~~~ ^ gcc-5.3.0: error: assignment of read-only location 'things[3]' things[3] = 12; ^
      
      







構造



従来の構造



構造全体にconst



を指定できます。

 struct aStruct { int32_t a; uint64_t b; }; const struct aStruct someStructA = {.a = 3, .b = 4};
      
      





または:

 struct const aStruct someStructA = {.a = 3, .b = 4};
      
      





someStructA



メンバーを変更しようとした場合:

 someStructA.a = 9;
      
      





エラーが発生します someStructA



const



として宣言されます。 決定後にメンバーを変更することはできません。

 clang-700.1.81: error: read-only variable is not assignable someStructA.a = 9; ~~~~~~~~~~~~~ ^ gcc-5.3.0: error: assignment of member 'a' in read-only object someStructA.a = 9; ^
      
      





const内部構造



構造体の個々のメンバーにconst



を指定できます。

 struct anotherStruct { int32_t a; const uint64_t b; }; struct anotherStruct someOtherStructB = {.a = 3, .b = 4};
      
      





someOtherStructB



メンバーを変更しようとした場合:

 someOtherStructB.a = 9; someOtherStructB.b = 12;
      
      





b



が変更されたときにのみエラーが発生します。 b



const



として宣言されます:

 clang-700.1.81: error: read-only variable is not assignable someOtherStructB.b = 12; ~~~~~~~~~~~~~~~~~~ ^ gcc-5.3.0: error: assignment of read-only member 'b' someOtherStructB.b = 12;
      
      





const



修飾子を使用して構造体のインスタンス全体を宣言することは、すべてのメンバーがconst



として定義されている構造体の特別なコピーを宣言することと同等です。 100% const



構造が必要ない場合は、必要な場合にのみ、構造を宣言するときに特定のメンバーに対してのみconst



を指定できます。



ポインタ



ポインターのconst



は、楽しみの始まりです。



単一のconst



例として整数ポインターを使用してみましょう。

 uint64_t bob = 42; uint64_t const *aFour = &bob;
      
      





これはポインターであるため、ここには2つのリポジトリーがあります。



それで、 aFour



何ができるでしょうか? いくつか試してみましょう。

彼が指し示す価値は変えられると思いますか?

 *aFour = 44;
      
      





 clang-700.1.81: error: read-only variable is not assignable *aFour = 44; ~~~~~~ ^ gcc-5.3.0: error: assignment of read-only location '*aFour' *aFour = 44; ^
      
      





指す値を変更せずにconst



ポインターを更新するのはどうですか?

 aFour = NULL;
      
      





本当に機能し、完全に有効です。 「不変データへのポインター」を意味するuint64_t const *



を宣言しuint64_t const *



が、ポインター自体は不変ではありません(注: const uint64_t *



も同じ意味です)。



データとポインターの両方を同時に不変にする方法は? 会う:double const







2つのconst



別のconst



を追加して、状況がどうなるかを見てみましょう。

 uint64_t bob = 42; uint64_t const *const anotherFour = &bob; *anotherFour = 45; anotherFour = NULL;
      
      





結果は何ですか?

 clang-700.1.81: error: read-only variable is not assignable *anotherFour = 45; ~~~~~~~~~~~~ ^ error: read-only variable is not assignable anotherFour = NULL; ~~~~~~~~~~~ ^ gcc-5.3.0: error: assignment of read-only location '*anotherFour' *anotherFour = 45; ^ error: assignment of read-only variable 'anotherFour' anotherFour = NULL; ^
      
      





ええ、データとポインター自体の両方を不変にすることができました。



const *const



どういう意味ですか?


ここでの意味はそれほど明白ではないようです。

実際には変数宣言を右から左へ (またはさらに悪いことに、 スパイラルで読むことが推奨さているため、値は非常に不安定です。

この場合、右から左に読むと2 、この宣言は次を意味します。

 uint64_t const *const anotherFour = &bob;
      
      





anotherFour



は次のanotherFour



です。



「通常の」構文を使用して、右から左に読みます。

 uint64_t const *aFour = &bob;
      
      





aFour



は:



今見たものは何ですか?

重要な違いがあります。人々は通常、 const uint64_t *bob



を「不変のポインター」として呼び出しますが、それはここでは起こりません。 これは、実際には「不変データへの可変ポインター」です。



間奏-const宣言の説明



しかし、待って、もっともっと!



ポインタを表現することで、 const



修飾子を宣言するための4つの異なるオプションがどのように得られるかを見ました。 できること:



これは1つのポインターと2つの const



に対するものですが、別のポインターを追加するとどうなりますか?



3つのconst





ダブルポインターにconst



を追加する方法はいくつありますか?



すぐに確認してみましょう。

 uint64_t const **moreFour = &aFour;
      
      





上記の発表に基づいて許可されるこれらの操作はどれですか?

 **moreFour = 46; *moreFour = NULL; moreFour = NULL;
      
      





 clang-700.1.81: error: read-only variable is not assignable **moreFour = 46; ~~~~~~~~~~ ^ gcc-5.3.0: error: assignment of read-only location '**moreFour' **moreFour = 46; ^
      
      





宣言を右から左に読んだ場合、最初の割り当てのみが機能しませんでした。

 uint64_t const **moreFour = &aFour;
      
      





moreFour







ご覧のとおり、実行できなかった唯一の操作は、保存された値を変更することでした。 ポインターとポインターをポインターに正常に変更しました。





別のconst



修飾子をさらに深いレベルに追加する場合はどうなりますか?

 uint64_t const *const *evenMoreFour = &aFour;
      
      





2つのconst



3が与えられた場合 今何ができる?

 **evenMoreFour = 46; *evenMoreFour = NULL; evenMoreFour = NULL;
      
      





 clang-700.1.81: error: read-only variable is not assignable **evenMoreFour = 46; ~~~~~~~~~~~~~~ ^ error: read-only variable is not assignable *evenMoreFour = NULL; ~~~~~~~~~~~~~ ^ gcc-5.3.0: error: assignment of read-only location '**evenMoreFour' **evenMoreFour = 46; ^ error: assignment of read-only location '*evenMoreFour' *evenMoreFour = NULL; ^
      
      





広告を右から左に読むと、変更から2回保護されます。

 uint64_t const *const *evenMoreFour = &aFour;
      
      





evenMoreFour



は:







2つよりも少し上手くできます。 会う:3 const







ダブルポインターを宣言するときにすべての変更をブロックする場合はどうでしょうか。

 uint64_t const *const *const ultimateFour = &aFour;
      
      





今、私たちは何ができますか?

 **ultimateFour = 48; *ultimateFour = NULL; ultimateFour = NULL;
      
      





 clang-700.1.81: error: read-only variable is not assignable **ultimateFour = 46; ~~~~~~~~~~~~~~ ^ error: read-only variable is not assignable *ultimateFour = NULL; ~~~~~~~~~~~~~ ^ error: read-only variable is not assignable ultimateFour = NULL; ~~~~~~~~~~~~ ^ gcc-5.3.0: error: assignment of read-only location '**ultimateFour' **ultimateFour = 46; ^ error: assignment of read-only location '*ultimateFour' *ultimateFour = NULL; ^ error: assignment of read-only variable 'ultimateFour' ultimateFour = NULL; ^
      
      





何も動作しません! 成功!



もう一度行きましょう。

 uint64_t const *const *const ultimateFour = &aFour;
      
      





ultimateFour



は:



追加のルール







カーキ色



タイプキャストハック



あなたが賢く、不変のストレージへの可変ポインタを作成したらどうでしょうか?

 const uint32_t hello = 3; uint32_t *getAroundHello = &hello; *getAroundHello = 92;
      
      





あなたのコンパイラはconst



を落とすと文句を言うでしょうが、ただ警告を投げるだけです4 あなたは5を無効にすることができます

 clang-700.1.81: warning: initializing 'uint32_t *' (aka 'unsigned int *') with an expression of type 'const uint32_t *' (aka 'const unsigned int *') discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers] uint32_t *getAroundHello = &hello; ^ ~~~~~~ gcc-5.3.0: warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] uint32_t *getAroundHello = &hello; ^
      
      





これはCなので、明示的な型変換によってconst修飾子を破棄し、警告(およびconst



初期化違反)を取り除くことができます。

 uint32_t *getAroundHello = (uint32_t *)&hello;
      
      





コンパイラーに明示的にreal &hello



型を無視し、代わりにuint32_t *



を使用するように指示したため、コンパイルの警告はありません。



メモリーカーキ



構造体にconst



メンバーが含まれているが、宣言後に構造体に格納されているデータを変更した場合はどうなりますか?



メンバーの不変性のみが異なる2つの構造体を宣言しましょう。

 struct exampleA { int64_t a; uint64_t b; }; struct exampleB { int64_t a; const uint64_t b; }; const struct exampleA someStructA = {.a = 3, .b = 4}; struct exampleB someOtherStructB = {.a = 3, .b = 4};
      
      





someOtherStructB



const someStructA



コピーしてみましょう。

 memcpy(&someStructA, &someOtherStructB, sizeof(someStructA));
      
      





これは機能しますか?

 clang-700.1.81: warning: passing 'const struct aStruct *' to parameter of type 'void *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers] memcpy(&someStructA, &someOtherStructB, sizeof(someStructA)); ^~~~~~~~~~~~ gcc-5.3.0: In file included from /usr/include/string.h:186:0: warning: passing argument 1 of '__builtin___memcpy_chk' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] memcpy(&someStructA, &someOtherStructB, sizeof(someStructA)); ^ note: expected 'void *' but argument is of type 'const struct aStruct *'
      
      





いいえ、それは機能しません。プロトタイプ6 memcpy



場合、次のようになります。

 void *memcpy(void *restrict dst, const void *restrict src, size_t n);
      
      





someStructA



、コピー中にdst



が変更されるため(およびsomeStructA



不変であるため)、不変のポインターをdst



引数として渡すことを許可しません。



ただし、 const



パラメーターのチェックは、関数のプロトタイプによってのみ実行されます。 dst



として別のconst



フィールドを持つ部分的に不変の構造を使用すると、コンパイラは文句を言いますか?



const someStructA



をmutableにコピーしようとしたが、 const



メンバーsomeOtherStructB



を1つ含むとsomeOtherStructB



ますか?

 memcpy(&someOtherStructB, &someStructA, sizeof(someOtherStructB));
      
      





これで、関数のプロトタイプテストに合格し、完全に変更されていない構造の不変メンバーを上書きしても、 memcpy



に関する警告は表示されません。



おわりに



不必要に変更可能な値を作成しないでください。 計画どおりにプログラムが実際に機能するように注意してください。



自分で試してみてください
 #include <stddef.h> /*   NULL */ #include <stdint.h> /*      */ int main(void) { uint64_t bob = 42; const uint64_t *aFour = &bob; /* uint64_t const *aFour = &bob; */ *aFour = 44; /*  */ aFour = NULL; const uint64_t *const anotherFour = &bob; /* uint64_t const *const anotherFour = &bob; */ *anotherFour = 45; /*  */ anotherFour = NULL; /*  */ const uint64_t **moreFour = &aFour; /* uint64_t const **moreFour = &aFour; */ **moreFour = 46; /*  */ *moreFour = NULL; moreFour = NULL; const uint64_t *const *evenMoreFour = &aFour; /* uint64_t const *const *evenMoreFour = &aFour; */ **evenMoreFour = 47; /*  */ *evenMoreFour = NULL; /*  */ evenMoreFour = NULL; const uint64_t *const *const ultimateFour = &aFour; /* uint64_t const *const *const ultimateFour = &aFour; */ **ultimateFour = 48; /*  */ *ultimateFour = NULL; /*  */ ultimateFour = NULL; /*  */ return 0; }
      
      














1-また、スカラー変数の初期値を変更することはできないため、 const



スカラーを非const



パラメーターとして使用する関数に渡すことは絶対に安全であることを意味します。 ^



2-そのような場合、これらの宣言は両方ともまったく同じ結果につながるため、 const uint64_t *



uint64_t const *



ではなくuint64_t const *



を記述する方が良いかもしれませんが、 const



修飾子が型の後に続く場合は、宣言を右から左に読む方が便利です。 ^



3-また、 const



を追加すると、ポインターは前の修飾子ではなく次の修飾子に接続されるため、ポインターの正しい構文がtype *name



ではなく type* name



あり、確かにtype *name



はないことを無条件に確認します。 例:

間違った

 uint64_t const* const* evenMoreFour; /*       const */
      
      





そうだね

 uint64_t const *const *evenMoreFour; /* const    . */
      
      



^



4-まあ、コンパイラモデルに応じて非標準フラグを使用する必要があるため、ビルドプロセスでは、これらの警告をオフにするさまざまなコンパイラとの互換性のために多くの冗長フラグが必要になる場合があります。 ^



5-覚えていますconst



コンパイル時にのみチェックれます。 const



によって課せられた制限を破ることができない場合にのみプログラムの動作を変更することはありません(他の値を変更するだけでプログラムの動作が変更されるだけです)が、期待どおりに動作しない可能性があります。 また、コンパイラーは読み取り専用コードセグメントに不変データを配置でき、これらのconst



ブロックをバイパスしようとすると、未定義の動作が発生する可能性があります。 ^



6- memcpy()



プロトタイプのrestrict



キーワードにも注意してください。 restrict



は、「このポインターのデータが現在のスコープ内の他のデータと交差しない」ことを意味し、 memcpy()



そのパラメーターを処理する方法を決定します。

コピーするときに、宛先へのポインターがデータの送信元へのポインターに部分的に重なる場合、 memmove()



関数を使用する必要があります。そのプロトタイプにはrestrict



修飾子が含まrestrict



いません。

 void *memmove(void *dst, const void *src, size_t len);
      
      



^



All Articles