12

いいえ、これは整数オーバーフローを検出する方法の複製ではありませんか? . 問題は同じですが、質問は異なります。


gcc コンパイラは、(-O2 を使用して) オーバーフロー チェックを最適化して取り除くことができます。次に例を示します。

int a, b;
b = abs(a);                     // will overflow if a = 0x80000000
if (b < 0) printf("overflow");  // optimized away

gcc の人々は、これはバグではないと主張しています。C 標準によれば、オーバーフローは未定義の動作であり、コンパイラは何でも実行できます。どうやら、オーバーフローが発生しないことを前提としたものは何でも含まれているようです。残念ながら、これにより、コンパイラはオーバーフロー チェックを最適化できなくなります。

オーバーフローをチェックする安全な方法は、最近のCERT 論文に記載されています。この論文では、2 つの整数を加算する前に次のようなことを行うことを推奨しています。

if ( ((si1^si2) | (((si1^(~(si1^si2) & INT_MIN)) + si2)^si2)) >= 0) { 
  /* handle error condition */
} else {
  sum = si1 + si2;
}

どうやら、結果が有効であることを確認したい場合、一連の計算で +、-、*、/ およびその他の操作の前に、このようなことを行う必要があるようです。たとえば、配列インデックスが範囲外でないことを確認したい場合。これは非常に面倒なので、実際に誰も行っていません。少なくとも、これを体系的に行う C/C++ プログラムは見たことがありません。

さて、これは根本的な問題です:

  • 配列にアクセスする前に配列インデックスをチェックすることは便利ですが、信頼性は高くありません。

  • CERT 法による一連の計算のすべての操作をチェックすることは信頼できますが、役に立ちません。

  • 結論: C/C++ でオーバーフローをチェックする便利で信頼できる方法はありません!

標準が書かれたときにこれが意図されていたとは信じられません。

問題を解決できる特定のコマンド ライン オプションがあることは知っていますが、標準またはその現在の解釈に根本的な問題があるという事実は変わりません。

ここで私の質問は次のとおりです。gcc の人々は、オーバーフロー チェックを最適化して取り除くことを許可するときに「未定義の動作」の解釈を取りすぎているのでしょうか、それとも C/C++ 標準が壊れているのでしょうか?

追加された注: 申し訳ありませんが、私の質問を誤解している可能性があります。私は問題を回避する方法を尋ねているのではありません - それはすでに他の場所で回答されています。C 標準について、より基本的な質問をしています。オーバーフローをチェックする便利で信頼できる方法がない場合、言語自体が怪しいです。たとえば、境界チェックを使用して安全な配列クラスを作成した場合、安全であるはずですが、境界チェックを最適化して取り除くことができる場合はそうではありません。

規格がこれを可能にする場合、規格を改訂する必要があるか、規格の解釈を改訂する必要があります。

注 2 を追加: ここにいる人々は、「未定義の動作」という疑わしい概念について議論したがらないようです。C99 標準に 191 種類の未定義の動作がリストされているという事実 (リンク) は、ずさんな標準を示しています。

多くのプログラマーは、「未定義の動作」により、ハードディスクのフォーマットを含め、あらゆることを行うライセンスが得られるという声明をすぐに受け入れます。標準が整数オーバーフローを配列境界外への書き込みと同じ危険なカテゴリに分類しているのは問題だと思います。

これら 2 種類の「未定義の動作」が異なるのはなぜですか? なぜなら:

  • 多くのプログラムは、整数のオーバーフローが無害であることに依存していますが、何があるか分からない場合に、配列境界の外側に書き込むことに依存するプログラムはほとんどありません。

  • 配列の境界外に書き込むと、実際にはハードディスクをフォーマットするのと同じくらい悪いことをする可能性があり (少なくとも DOS のような保護されていない OS では)、ほとんどのプログラマーはこれが危険であることを知っています。

  • 整数オーバーフローを危険な「なんでもあり」カテゴリに入れると、コンパイラが何をしているのかについて嘘をつくことを含め、コンパイラが何でもできるようになります (オーバーフロー チェックが最適化されていない場合)。

  • 配列境界外への書き込みなどのエラーはデバッガーで見つけることができますが、最適化は通常デバッグ時にオフになっているため、オーバーフロー チェックを最適化して取り除くエラーは発見できません。

  • gcc コンパイラーは、整数オーバーフローの場合に「なんでもあり」ポリシーを明らかに控えています。オーバーフローが不可能であることを確認できない限り、ループなどの最適化を控える場合が多くあります。なんらかの理由で、gcc 関係者は、ここで「なんでもあり」ポリシーに従えばエラーが多すぎることを認識していましたが、オーバーフロー チェックを最適化して除外するという問題に対しては異なる態度をとっています。

たぶん、ここはそのような哲学的な問題を議論するのに適切な場所ではありません. 少なくとも、ここでのほとんどの回答は的外れです。これを議論するためのより良い場所はありますか?

4

4 に答える 4

4

自問してみてください: チェックされた算術演算が実際に必要になる頻度はどれくらいですか? 頻繁に必要な場合はchecked_int、一般的な演算子をオーバーロードするクラスを作成し、チェックをこのクラスにカプセル化する必要があります。オープン ソースの Web サイトで実装を共有するための小道具。

さらに良いのは (ほぼ間違いなく)、big_integerそもそもオーバーフローが発生しないようにクラスを使用することです。

于 2011-07-28T08:50:10.127 に答える
4

gcc 開発者はここで完全に正しいです。標準が動作が未定義であると述べている場合、それはコンパイラに要件がないことを正確に意味します。

有効なプログラムは UB の原因となるようなことは何もできないので (その場合、それはもはや有効ではなくなるため)、コンパイラーは UB が発生しないと非常によく想定できます。それでも問題が解決しない場合は、コンパイラが行うことはすべて問題ありません。

オーバーフローに関する問題の 1 つの解決策は、計算が処理するはずの範囲を検討することです。たとえば、銀行口座の残高を調整する場合、金額が 10 億をはるかに下回ると想定できるため、32 ビットの int が機能します。

アプリケーション ドメインについては、オーバーフローが発生する可能性がある正確な場所について、おそらく同様の見積もりを行うことができます。次に、それらのポイントにチェックを追加するか、可能な場合は別のデータ型を選択できます。

于 2011-07-28T08:51:36.977 に答える
4
int a, b;
b = abs(a); // will overflow if a = 0x80000000
if (b < 0) printf("overflow");  // optimized away 

(あなたは2の補数を仮定しているようです...それで実行しましょう)

そのバイナリ パターンがある場合 (より正確には の場合) 、 abs(a)「オーバーフロー」と言うのは誰ですか? Linuxのmanページには次のように書かれています:aaINT_MINabs(int)

最も負の整数の絶対値を取得しようとすることは定義されていません。

定義されていないということは、必ずしもオーバーフローを意味するわけではありません。

bしたがって、 0未満になる可能性があるという前提は、「オーバーフロー」のテストであり、最初から根本的に欠陥があります。テストしたい場合は、未定義の動作が発生する可能性のある結果に対して実行することはできません。代わりに、操作の前に実行してください!

これが気になる場合は、C++ のユーザー定義型 (つまり、クラス) を使用して、必要な操作に関する独自のテスト セットを実装できます (または、既にそれを実行しているライブラリを見つけます)。言語は、そのようなライブラリで同等に効率的に実装できるため、組み込みのサポートを必要としません。使用のセマンティクスは変更されません。その根本的な力は、C++ の優れた点の 1 つです。

于 2011-07-28T09:01:25.600 に答える
2

に正しいタイプを使用するだけですb

int a;
unsigned b = a;
if (b == (unsigned)INT_MIN) printf("overflow");  // never optimized away
else b = abs(a);

編集: C でのオーバーフローのテストは、符号なしの型で安全に行うことができます。符号なしの型は算術演算でラップアラウンドするだけで、符号付きの型は安全に変換されます。そのため、好きなテストを行うことができます。最新のプロセッサでは、この変換は通常、レジスタの再解釈にすぎないため、ランタイム コストは発生しません。

于 2011-07-28T08:50:35.197 に答える