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

MSC15-C. 未定義の動作に依存しない

MSC15-C. 未定義の動作に依存しない

C99 セクション 3.4.3 は 未定義の動作を以下のように定義している。

可搬性がない若しくは正しくないプログラム構成要素を使用したときの動作、又は正しくないデータを使用したときの動作であり、この規格が何ら要求を課さないもの。

C99 のセクション 4 は、この規格が未定義の動作をどのようにして特定するか記述している。

この規格の制約以外の箇所で現れる“…(し)なければならない”又は“…(し)てはならない”という要求をプログラムが守っていない場合、その動作は未定義とする。この規格では、このほかに、用語“未定義の動作”を使うことによるか又は動作の明示的な定義を与えないことによって未定義の動作を示すこともある。これらの三つの書き方は強調度において何ら変わりはなく、それらはすべて“未定義の動作”を規定する。

C99 の附属書 J.2、「未定義の動作」には、C99 における明示的な未定義の動作がまとめられている。

以下に示す理由で、C 標準化委員会は動作を未定義として分類することがある。

規格合致処理系は未定義の動作をさまざまな方法で処理することができる。たとえば、状況を完全に無視して予期せぬ結果とする方法、その環境に固有の明文化されている方法で(診断メッセージを発行して、または発行せずに)プログラムを翻訳または実行する方法、あるいは(診断メッセージを発行して)翻訳や実行を終了する方法などである。コンパイラは未定義の動作に対応するコードを生成する義務はないため、未定義の動作は最適化の対象になる。未定義の動作が生じないと想定することで、コンパイラは高い性能特性を持つコードを生成できる。

未定義の動作は、不幸にも攻撃者の面前で発生することが多い。最適化処理されてしまうと、未定義の動作が生じた場合にシステムがどのように動作するか、特定することが難しくなる。これが特に困難を伴うのは、未定義の動作に依存したソースコードを目視で検査するときである。コードレビューでは、そのコードがコンパイルされるのか、それとも最適化により除外されるのかがわからない。また、現行のコンパイラがある未定義の動作に対してオブジェクトコードを生成したとしても、そのコンパイラの未来のバージョンが同じようにその動作に対してコードを生成するとは限らない。つまり、その動作は将来最適化される可能性があるものと見なせる。コンパイラは未定義の動作に対して診断メッセージを発行しなくてもよく、コード内の未定義の動作は容易には特定できない。

これらすべてのことから、コンパイラの補助のあるなしに関わらず、プログラマが規格に厳密に合致したコードを書くのは重荷になっている。性能はC言語で特に重視されているため、状況は改善するどころか悪くなる傾向がある。

違反コード

C99 における未定義の動作の一例に、符号付き整数のオーバーフロー時の動作がある。以下のコード例は、この未定義の動作を使ってオーバーフローを捕捉している。

#include <assert.h>

int foo(int a) {
  assert(a + 100 > a);
  printf("%d %d\n", a + 100, a);
  return a;
}

int main(void) {
  foo(100);
  foo(INT_MAX);
}

このコードは a + 100 > a が真であるかどうかを判定することで符号付き整数のオーバーフローを判定している。整数オーバーフローが発生しない限り、この判定は偽にならない。しかし、規格合致実装は未定義の動作に対してコードを生成することが求められておらず、符号付き整数オーバーフローは未定義の動作であるため、このコードはコンパイル時に除外される可能性がある。たとえば、GCC 4.1.1 はすべての最適化レベルでアサートを除外し、GCC 4.2.3 は -O2 レベル以上の最適化でコンパイルされたプログラムのアサートを除外する。

プラットフォームによっては、整数オーバーフローは(プログラムに判定の機会を与えずに)プログラムを終了させることがある。

適合コード

以下の解決法は未定義の動作には依存していない。

#include <assert.h>

int foo(int a) {
  assert(a < (INT_MAX - 100));
  printf("%d %d\n", a + 100, a);
  return a;
}

int main(void) {
  foo(100);
  foo(INT_MAX);
}

リスク評価

アプリケーション全体が規格に厳密に合致することはまれだが、目標はコードのほとんどが(未定義の動作を避けることも含め)規格厳密合致プログラムとして認められることである。処理系依存のコードは、プラットフォームの相違に合わせて調整する必要があるとプログラマが認識している範囲に納めるべきである。

レコメンデーション 深刻度 可能性 修正コスト 優先度 レベル
MSC15-C P18 L1
参考情報
翻訳元

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

MSC15-C. Do not depend on undefined behavior

Top へ

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