JPCERT コーディネーションセンター

DCL13-C. 関数の引数が関数自身によって変更されない値を参照するポインタならば、関数の引数をconstとして宣言する

関数の引数を const として宣言すると、その関数が引数の値を変更しないことを約束するということを意味する。

C 言語では、関数の引数は参照ではなく値として渡される。関数は渡された値を変更するかもしれないが、これらの値は、ひとたび関数が呼び出し元に戻ると破棄される。そのため、多くのプログラマは、関数はその引数を変更せず、関数の引数を const として宣言する必要はないと考えがちである。

void foo(int x) {
  x = 3; /* 関数が exit するまで有効 */
  /* ... */
}

ポインタも同様の動作をする。関数はポインタを他のオブジェクトや NULL を参照するように変更するかもしれないが、そのような変更は関数が exit する際に破棄される。したがって、ポインタを const として宣言することは不要である。

void foo(int *x) {
  x = NULL; /* 関数が exit するまで有効 */
  /* ... */
}
違反コード

値渡しの引数やポインタとは違い、ポインタによって参照される値は問題になる。関数はポインタが参照する値を変更する可能性があり、これは関数が exit した後にまで影響する副作用につながる。コンパイラはポインタによって「参照される値」の変更は診断せず、値の変更は意図した動作であるとみなす。

void foo(int *x) {
  if (x != NULL) {
    *x = 3; /* 関数外から可 */
  }
  /* ... */
}

関数の引数が const 修飾されている場合、ポインタによって参照される値を変更しようとするとコンパイラが致命的エラーを出す。

void foo(const int *x) {
  if (x != NULL) {
    *x = 3; /* コンパイルエラーを出す */
  }
  /* ... */
}

結局、問題を解決しなければコードをコンパイルできない。

適合コード

以下の適合コードでは、定数引数を変更しないようにすることで、問題に対処している。

void foo(const int * x) {
  if (x != NULL) {
    printf("Value is %d\n", *x);
  }
  /* ... */
}
違反コード

以下のコードでは、標準関数 strcat() の架空の関数 strcat_nc() を定義している。この関数が strcat() と異なる点は、第二引数がconst修飾されていない点である。

char *strcat_nc(char *s1, char *s2);

char *str1 = "str1";
const char *str2 = "str2";
char str3[9] = "str3";
const char str4[9] = "str4";

strcat_nc(str3, str2);	/* コンパイラは str2 が const であると警告する */
strcat_nc(str1, str3);	/* 文字列リテラルを上書きしようとしている! */
strcat_nc(str4, str3);	/* コンパイラは str4 が const であると警告する */

この関数は strcat() と同様に動作するが、コンパイラは間違った位置で警告を出し、正しい位置で警告を出し損ねる。

一番目のstrcat_nc() 呼び出しでは、コンパイラはstr2のconstをキャストによりはずそうとすることに警告を出す。警告がでるのは、strcat_nc() は第二引数を、変更しないにもかかわらず、constとして宣言していないからである。

二番目のstrcat_nc()呼び出しでは、コンパイラは警告を出さずにコンパイルするが、結果のコードはリテラル"str1" を変更しようとしている。これは「STR05-C. 文字列リテラルの参照には const へのポインタを使用する」と「STR30-C. 文字列リテラルを変更しない」に違反する。

最後の strcat_nc() 呼び出しでは、コンパイラは str4const をキャストによりはずそうとすることを警告するが、これは正しい警告である。

適合コード

以下の適合コードでは、C90のstrcat()プロトタイプを使用している。C90にはrestrict型修飾子は存在しなかったが、constは存在した。一般に、関数の引数は関数のセマンティックスと一貫した形で宣言されるべきである。strcat() の場合、第一引数は関数によって変更されるが、第二引数は変更されない。

char *strcat(char *s1, const char *s2); 

char *str1 = "str1";
const char *str2 = "str2";
char str3[9] = "str3";
const char str4[9] = "str4";

strcat(str3, str2); 

/* 引数を逆にして文字列リテラルが変更されないようにしている */ 
strcat(str3, str1);  
strcat(str4, str3); /* コンパイラは str4 が const であると警告する */

第二引数を const 修飾することにより最初の関数呼び出し時にでる嘘の警告が出ないようにしているが、const 修飾されたオブジェクトを第一引数(変更される)に渡している最後の関数呼び出しでは、正しい警告が出るようにしている。また、二番目のstrcat()呼び出しは正しい呼び出しになっている。str3 は結果を格納する先として有効であり、安全に値を変更できる。

リスク評価

不変の値を const として宣言していないと、関数は const としてキャストされた値を使って動作することはできない。この問題は、const 修飾をキャストによりはずすことで回避しうるが、そうすると今度は「EXP05-C. const 修飾をキャストではずさない」に違反する。

レコメンデーション

深刻度

可能性

修正コスト

優先度

レベル

DCL13-C

P3

L3

自動検出

ツール

バージョン

チェッカー

説明

Compass/ROSE

 

 

レコメンデーション「DCL00-C. 不変(immutable)オブジェクトは const 修飾する」の違反をチェックする際にこのレコメンデーションの違反を検出することができる。

ECLAIR

1.1

cnstpnte

実装済み

LDRA tool suite

V. 8.5.4

62 D

実装済み

PRQA QA-C 8.1

3673
0431(C)

実装済み
関連するガイドライン
CERT C++ Secure Coding Standard DCL13-CPP. Declare function parameters that are pointers to values not changed by the function as const
ISO/IEC TR 24772:2013 Passing Parameters and Return Values [CSJ]
翻訳元

これは以下のページを翻訳したものです。

DCL13-C. Declare function parameters that are pointers to values not changed by the function as const (revision 66)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter