web-dev-qa-db-ja.com

「署名されたオーバーフローを想定する」警告を理解していない

私は得ています:

警告:(X + c)<Xが常にfalseであると仮定すると、符号付きオーバーフローは発生しないと仮定します[-Wstrict-overflow]

この行:

if ( this->m_PositionIndex[in] < this->m_EndIndex[in] ) 

タイプm_PositionIndexhttp://www.itk.org/Doxygen/html/classitk_1_1Index.html )のm_EndIndexおよびitk::Index、およびそれらのoperator[]signed longを返します。

(ここでは37行目です: https://github.com/Kitware/ITK/blob/master/Modules/Core/Common/include/itkImageRegionConstIteratorWithIndex.hxx コンテキスト用)

誰かがここでその警告を引き起こす原因を説明できますか?パターン(x+c) < xはどこにも見当たりません-これは単にsigned longの比較であるためです。

私はそれを自己完結型の例で再現しようとしました:

#include <iostream>

namespace itk
{
  struct Index
  {
    signed long data[2];

    Index()
    {
      data[0] = 0;
      data[1] = 0;
    }

    signed long& operator[](unsigned int i)
    {
      return data[i];
    }
  };
}
int main (int argc, char *argv[])
{
  itk::Index positionIndex;
  itk::Index endIndex;

  for(unsigned int i = 0; i < 2; i++)
  {
    positionIndex[i]++;
    if ( positionIndex[i] < endIndex[i] )
    {
      std::cout << "something" << std::endl;
    }
  }

  return 0;
}

しかし、私はそこで警告を受けません。私のデモと実際のコードの違い、または実際のコードで警告を引き起こしている可能性があるものについて何か考えはありますか? -Wallフラグを指定したgcc4.7.0と4.7.2の両方で警告が表示されます。

23
David Doria

この警告を単に無効にするには、-Wno-strict-overflowを使用します。代わりに、この警告をトリガーする特定の最適化を無効にするには、-fno-strict-overflowまたは-fwrapvを使用します。

gcc manpage は、この警告をレベル-Wstrict-overflow=nで制御できることを示しています。

これが-Werrorのためにビルドを停止している場合は、-Wno-error=strict-overflow(または-Wno-errorをオーバーライドして-Werrorをオーバーライドする)を使用して、警告を非表示にせずに回避策を実行できます。


分析と解説...

同じ警告が表示され、小さな例で再現しようと数時間費やしましたが、成功しませんでした。私の実際のコードは、テンプレート化されたクラスでインライン関数を呼び出すことを含みましたが、アルゴリズムは次のように単純化されます...

int X = some_unpredictable_value_well_within_the_range_of_int();
for ( int c=0; c<4; c++ ) assert( X+c >= X ); ## true unless (X+c) overflows

私の場合、警告はオプティマイザーがforループを展開することと何らかの形で相関していたため、volatile int c=0を宣言することで回避策を講じることができました。それを修正したもう1つのことは、unsigned int c=0を宣言することでしたが、なぜそれが違いを生むのか正確にはわかりません。それを修正したもう1つのことは、ループが展開されないようにループカウントを十分に大きくすることでしたが、それは有用な解決策ではありません。

では、この警告は実際に何を言っているのでしょうか?オプティマイザーが(オーバーフローがないと仮定して)アルゴリズムのセマンティクスを変更したと言っているか、オプティマイザーがコードに符号付き整数をオーバーフローさせる未定義の動作。オーバーフローした符号付き整数がプログラムの意図された動作の一部でない限り、このメッセージはおそらくコードの問題を示していないため、通常のビルドでは無効にすることをお勧めします。この警告が表示されても問題のコードがわからない場合は、-fwrapvを使用して最適化を無効にするのが最も安全な場合があります。

ちなみに、私はGCC 4.7でこの問題に遭遇しましたが、同じコードが4.8を使用して警告なしにコンパイルされました。オプティマイザの違いによるものです)。


哲学...

[C++ 11: N3242 $ 5.0.4]には、次のように記載されています...

式の評価中に、結果が数学的に定義されていないか、そのタイプの表現可能な値の範囲内にない場合、動作は定義されていません。

これは、適合コンパイラがオーバーフローが発生しないと単純に想定できることを意味します(符号なし型の場合でも)。

C++では、テンプレートやコピーコンストラクターなどの機能により、無意味な操作の elision は重要なオプティマイザー機能です。ただし、C++を低レベルの「システム言語」として使用している場合は、コンパイラーに指示どおりの処理を実行させ、基盤となるハードウェアの動作に依存させたい場合があります。標準の言語を考えると、コンパイラに依存しない方法でこれを実現する方法がわかりません。

15
nobar

コンパイラーは、署名された操作がオーバーフローしないと仮定してテストを最適化できれば、テストが常に成功することを知るのに十分なスニペットの静的知識があることを通知しています。

言い換えれば、唯一の方法はx + 1 < xは、xが署名されている場合、xがすでに最大署名値である場合に、trueを返します。 [-Wstrict-overflow]署名された加算がオーバーフローしないと想定できる場合にコンパイラに警告しましょう。署名されたオーバーフローは未定義の動作であるため、基本的にはテストを最適化することを示しています。

警告を抑制したい場合は、テストを削除してください。

6
MSN

質問の年齢にもかかわらず、コードのその部分をまだ変更していないので、問題はまだ存在し、実際にここで何が起こっているのかについての有用な説明がまだ得られなかったと思いますので、試してみましょう:

C(およびC++)では、2つのSIGNED整数を追加するとオーバーフローが発生する場合、プログラム実行全体の動作は未定義です。したがって、プログラムを実行する環境は、必要なことを何でも実行できます(必要なハードウェアが存在する場合は、ハードディスクをフォーマットするか、核兵器戦争を開始します)。

gccは通常どちらも実行しませんが、他の厄介なことを実行します(それでも、不運な状況ではどちらかにつながる可能性があります)。これを実証するために、Felix von Leitner @ http://ptrace.fefe.de/int.c からの簡単な例を挙げましょう。

#include <assert.h>
#include <stdio.h>

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

int main() {
  foo(100);
  foo(0x7fffffff);
}

-> printfが宣言されていないという警告を取り除くために、stdio.hを追加しました。

これを実行すると、整数オーバーフローが発生してチェックされるため、fooの2回目の呼び出しでコードがアサートされることが期待されます。だから、それをやってみましょう:

$ gcc -O3 int.c -o int && ./int
200 100
-2147483549 2147483647

WTF? (WTF、ドイツ語で「WastäteFefe」-「Fefeは何をするか」。FefeはFelix von Leitnerのニックネームで、コード例を借りました)。ああ、そして、ところで、täteFefeでしたか?右:この問題について2007年にバグレポートを書いてください! https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475

質問に戻ります。ここで掘り下げようとすると、アセンブリ出力(-S)を作成し、調査してassertが完全に削除されたことを確認できます。

$ gcc -S -O3 int.c -o int.s && cat int.s
[...]
foo:
        pushq   %rbx             // save "callee-save" register %rbx
        leal    100(%rdi), %edx  // calc a+100 -> %rdx for printf
        leaq    .LC0(%rip), %rsi // "%d %d\n" for printf
        movl    %edi, %ebx       // save a to %rbx
        movl    %edi, %ecx       // move a to %rcx for printf
        xorl    %eax, %eax       // more prep for printf
        movl    $1, %edi         // and even more prep
        call    __printf_chk@PLT // printf call
        movl    %ebx, %eax       // restore a to %rax as return value
        popq    %rbx             // recover "callee-save" %rbx
        ret                      // and return

ここではどこにも主張はありません。

それでは、コンパイル中に警告をオンにしましょう。

$ gcc -Wall -O3 int.c -o int.s
int.c: In function 'foo':
int.c:5:2: warning: assuming signed overflow does not occur when assuming that (X + c) >= X is always true [-Wstrict-overflow]
  assert(a+100 > a);
  ^~~~~~

したがって、このメッセージが実際に言っているのは、+ 100がオーバーフローして未定義の動作を引き起こす可能性があるということです。あなたは非常に熟練したプロのソフトウェア開発者であり、何も悪いことをすることはないので、私(gcc)は確かにa + 100がオーバーフローしないことを知っています。私はそれを知っているので、a + 100 > aが常に真であることも知っています。私はそれを知っているので、アサーションが決して起動しないことを知っています。そして、私はそれを知っているので、「デッドコード除去」最適化でアサーション全体を排除することができます。

そしてそれはまさに、gccがここで行うことです(そしてあなたに警告します)。

さて、あなたの小さな例では、データフロー分析は、この整数が実際にはオーバーフローしていないことを決定できます。したがって、gccはオーバーフローしないように仮定する必要はありません。代わりに、gccはオーバーフローしないように証明できます。この場合、コードを削除してもまったく問題ありません(コンパイラーはここでデッドコードの除去について警告することができますが、dceは頻繁に発生するため、おそらく誰もこれらの警告を望んでいません)。しかし、「実世界のコード」では、データフロー分析を実行するために必要なすべての情報が存在するわけではないため、データフロー分析は失敗します。したがって、gccは、a ++がオーバーフローするかどうかを知りません。したがって、それは決して起こらないと仮定することを警告します(そしてgccはifステートメント全体を削除します)。

ここでの問題を解決(または非表示!!!)する1つの方法は、a < INT_MAXを実行する前にa++をアサートすることです。とにかく、私はあなたがそこにいくつかの本当のバグを持っているかもしれないことを恐れます、しかし私はそれを理解するためにもっと多くを調査しなければならないでしょう。ただし、MVCEを適切な方法で作成して、自分で理解することはできます。警告付きのソースコードを取得し、インクルードファイルから必要なものを追加して、ソースコードをスタンドアロンにします(gcc -Eは少し極端です)しかし、仕事をします)、それから、もう何も削除できないコードができるまで、警告が消えないものを削除し始めます。

3
Bodo Thiesen

コンパイラが変わったと思います

positionIndex[i]++;
if ( positionIndex[i] < endIndex[i] )

より最適化されたものに

if ( positionIndex[i]++ < endIndex[i] )

そう

if ( positionIndex[i] + 1 < endIndex[i] )

オーバーフローが発生した場合、未定義の動作が発生します(Sethに感謝)。

1
Jack

私の場合、これはコンパイラからの良い警告でした。ループ内にループがあり、それぞれが同じ条件のセットであるという愚かなコピー/貼り付けのバグがありました。このようなもの:

for (index = 0; index < someLoopLimit; index++)
{
    // blah, blah, blah.....
    for (index = 0; index < someLoopLimit; index++)
    {
        //  More blah, blah, blah....
    }
}

この警告により、デバッグの時間を節約できました。ありがとう、gcc !!!

1
user2246302

3つの考えられるエラーのどれであるかはわかりませんが、これはほぼ間違いなくエラーです。

Gccは、「this-> m_PositionIndex [in]」などの値が何から計算されているかを何らかの形で把握していると思います。つまり、両方の値が同じ値から派生しており、オフセットが1つあると正になることもあります。 。その結果、if/else構文の1つのブランチが到達不能コードであることがわかります。

これは悪いです。間違いなく。どうして?まあ、正確に3つの可能性があります:

  1. gccは、コードに到達できないという点で正しいのですが、これが事実であることを見逃していました。この場合、あなたは単に肥大化したコードを持っています。 3つの可能性のうちの最良のものですが、コード品質の点ではまだ悪いです。

  2. gccは、コードに到達できないという点で正しいですが、到達可能にしたかったのです。この場合、到達できない理由をもう一度確認し、ロジックのバグを修正する必要があります。

  3. gccが間違っている、あなたはそれにバグを見つけました。可能性は非常に低いですが、可能であり、残念ながら証明するのは非常に困難です。

本当に悪いことは、コードが到達不能である理由を正確に見つける必要があるか、gccが正しいことを反証する必要があるということです(これは言うのは簡単です)。マーフィーの法則によれば、ケース1または3を証明せずに仮定すると、ケース2であることが確認され、バグをもう一度探す必要があります...