web-dev-qa-db-ja.com

Cで変数に対してconstキーワードをいつ、どのような目的で使用する必要がありますか?

取得中 ここで私のコードをレビューしましたconstキーワードの使用に関する問題が発生しました。変数に読み取り専用の動作を実装するために使用されることを理解しています。

さまざまな状況で、どのような場合に役立つかについて混乱しています。

  • 関数プロトタイプをわかりやすくするために使用する必要がありますか?
  • コード開発中のセキュリティ対策として使用する必要がありますか?
  • ランタイム定数を宣言するためのさまざまな関数のスコープで使用する必要がありますか?
  • 使用する必要がありますか?

これらの質問は、私が直面している混乱のほんの一例です。一般的な混乱は

  • Cプログラミングでconstキーワードを使用する必要があるのはいつですか?
  • Cでこのキーワードを使用することで得られるさまざまな種類の利点は何ですか?
  • constキーワードを使用することの短所はありますか?

Cで変数に対してconstキーワードをいつ、どのような目的で使用する必要がありますか?

次のように言い換えることもできます

Cでのconstキーワードの適切な使用と同じ長所と短所。

65
Aseem Bansal

コードを確認するときは、次のルールを適用します。

  • 関数が指しているデータを関数が変更(または解放)しない場合、参照渡しの関数パラメーターには常にconstを使用します。

    int find(const int *data, size_t size, int value);
    
  • #defineまたはenumを使用して定義される可能性がある定数には、常にconstを使用してください。結果として、コンパイラーはデータを読み取り専用メモリー(ROM)に配置できます(ただし、組み込みシステムでは、リンカーがこの目的のためのより良いツールであることがよくあります)。

    const double PI = 3.14;
    
  • valueによって渡されるパラメーターの関数prototypeでconstを使用しないでください。それは意味がなく、それゆえ単に「ノイズ」です。

    // don't add const to 'value' or 'size'
    int find(const int *data, size_t size, const int value); 
    
  • 必要に応じて、プログラムでは変更できないが変更される可能性がある場所ではconst volatileを使用します。ここでは、ハードウェアレジスタが典型的な使用例です。たとえば、デバイスの状態を反映するステータスレジスタです。

    const volatile int32_t *DEVICE_STATUS =  (int32_t*) 0x100;
    

その他の用途はオプションです。たとえば、関数implementation内の関数のパラメーターは、constとしてマークできます。

// 'value' and 'size can be marked as const here
int find(const int *data, const size_t size, const int value)  
{
     ... etc

または取得された後、決して変更されない関数の戻り値または計算:

char *repeat_str(const char *str, size_t n) 
{
    const size_t len = strlen(str);
    const size_t buf_size = 1 + (len * n);
    char *buf = malloc(buf_size);
    ...

これらのconstの使用は、変数を変更しないことを示しています。変数の格納方法や場所は変更されません。もちろん、コンパイラーは変数が変更されないことを確認できますが、constを追加することで、変数を強制することができます。これは読者を助け、ある程度の安全性を追加することができます(ただし、関数が大きく、または複雑でこれにより大きな違いが生じる場合、間違いなく他の問題があります)。 編集-例ネストされたループと長いまたは類似の変数名を含む200行の密にコード化された関数。特定の変数は決して変更されないことがわかっているため、理解が大幅に容易になります。このような関数は、設計または保守が適切に行われていません。


constに関する問題。おそらく「const poisoning」という言葉を聞くでしょう。これは、関数パラメーターにconstを追加すると 'constness'が伝播するときに発生します。

編集-constポイズニング:たとえば関数内:

int function_a(char * str, int n)
{
    ...
    function_b(str);
    ...
}

strconstに変更する場合、fuction_bconstをとることを確認する必要があります。 function_bstrfunction_cに渡す場合なども同様です。ご想像のとおり、多くの個別のファイル/モジュールに伝播する場合、これは苦痛になる可能性があります。変更できない関数(システムライブラリなど)に伝播する場合は、キャストが必要になります。したがって、既存のコードにconstを振りかけると、問題が発生する可能性があります。ただし、新しいコードでは、必要に応じてconstを一貫して修飾することをお勧めします。

constのより陰湿な問題は、元の言語ではなかったことです。アドオンとしては、あまり適合しません。まず、2つの意味があります(上記のルールのように、「これを変更しない」と「これは変更できません」)。しかし、それ以上に、それは危険な場合があります。たとえば、このコードをコンパイルして実行すると、(コンパイラ/オプションによっては)実行時にクラッシュする可能性があります。

const char str[] = "hello world\n";
char *s = strchr(str, '\n');
*s = '\0';

strchrchar*ではなくconst char*を返します。その呼び出しパラメータはconstであるため、char*への呼び出しパラメータをキャストする必要があります。そしてこの場合、それは実際の読み取り専用ストレージプロパティをキャストします。 編集:-これは一般的に読み取り専用メモリの変数に適用されます。 「ROM」とは、物理的なROMだけでなく、一般的なOSで実行されるプログラムのコードセクションで発生する、書き込み保護されているメモリを意味します。

多くの標準ライブラリ関数は同じように動作するため、注意してください。real定数(つまり、ROMに格納されている)がある場合は、その定数を失わないように十分注意する必要があります。

74
William Morris

一般に、どのプログラミング言語でも、constまたは同等の修飾子を使用することが推奨されています。

  • 渡されたものが変更されないことを発信者に明確にすることができます
  • コンパイラーは、パラメーターを変更できる場合にのみ関連する特定の事項を省略できることがわかっているため、速度が向上する可能性があります。
  • 誤って値を変更する自分からの保護
10
TheLQ

はい、基本的にはTheLQの答えです。

変数を変更したり、変数を変更する可能性のある関数を呼び出さないようにするためのプログラマーのセキュリティ対策です。配列または構造体では、const指定子は、その内容の値が変更されないことを示し、コンパイラーでも変更できないようにします。ただし、キャストだけで変数の値を簡単に変更できます。

私が普段目にしていることのほとんどは、コードに定数値を追加したり、特定の関数を呼び出しても配列や構造が変更されないことを示したりするために主に使用されます。この最後の部分は重要です。配列または構造を変更する関数を呼び出すときに、元のバージョンを保持したい場合があるため、変数のコピーを作成してから関数に渡します。そうでない場合は、コピーは必要ありません。たとえば、変更できます。

int foo(Structure s);

int foo(const Structure * s);

コピーのオーバーヘッドを取得していません。

付け加えると、Cにはconst指定子に関する特定の規則があることに注意してください。例えば、

int b = 1;
const int * a = &b;

と同じではありません

int b = 1;
int * const a = &b;

最初のコードでは変更できません。 2番目のケースでは、ポインターは定数ですが、その内容は定数ではないため、コンパイラーは* a = 3;コンパイラエラーは発生しませんが、aを別のものへの参照にすることはできません。

1
lgarcia

TheLQの声明に同意して:

constを宣言するプログラマーのチームと共同で作業する場合、その変数を変更してはならないことを示す、または大規模なプロジェクトで自分を思い出させるだけの良い方法です。その意味で便利であり、多くの頭痛を軽減することができます。

0
David Otano