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_tuint32_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修飾子が一致しないことは完全に正常です。
なぜそれが良いのですか? すべてが非常に単純です。関数はスコープの外でbb変更できないため、 constは渡すものに影響を与えません。 あなたのコンパイラは、それがabコピーで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}; 

someOtherStructBconst 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); 
^

Source: https://habr.com/ru/post/J301332/


All Articles