64ビットプログラムの長いループで32ビット変数のオーバーフローを検出する方法

64ビットアプリケーションの開発者が直面する問題の1つは、非常に長いサイクルでの32ビット変数のオーバーフローです。 PVS-Studioコードアナライザー(Viva64診断セット)は、このタスクを適切に処理します。 StackOverflow.comには、ループ内の変数オーバーフローのトピックに関する多くの質問があります。 しかし、私の答えは単なる広告であり、有用な情報ではないと考えられるため、記事でPVS-Studioの機能を説明することにしました。



典型的なC / C ++言語構成体はループです。 プログラムを64ビットアーキテクチャに移植する場合、コードを開発するときに、プログラムが数十億の反復を実行しなければならない場合、だれも事前に考えないため、ループは予期せず弱点になります。



この記事では、このような状況を64ビットエラーと呼びます。 これらは実際には単なる間違いです。 しかし、それらの特性は、64ビットプログラムでのみ現れることです。 32ビットプログラムでは、このような長いサイクルは発生しないため、 INT_MAXを超える要素数の配列を作成することはできません。



だから問題。 整数32ビット型のオーバーフローは、64ビットプログラムで発生します。 intunsignedlongなどの型について話します( Win64の場合)。 そのような危険な場所をすべて何らかの形で識別する必要があります。 PVS-Studioアナライザーでこれを行うことができます。これについては後で説明します。



長いサイクルに関連するオーバーフロー変数のさまざまなオプションを検討してください。



最初の状況。 StackOverflow Webサイトで次のように説明されています。「 わかりにくい64ビットの移植性の問題をどのように検出できますか? 」 次の形式のコードがあります。

int n; size_t pos, npos; /* ... initialization ... */ while((pos = find(ch, start)) != npos) { /* ... advance start position ... */ n++; // this will overflow if the loop iterates too many times }
      
      





プログラムは非常に長い行を処理します。 32ビットプログラムでは、文字列の長さがINT_MAXを超えることはできません。 したがって、エラーは発生しません。 はい、プログラムは大量のデータを処理できませんが、これはエラーではなく、32ビットアーキテクチャの機能の制限です。



64ビットプログラムでは、文字列の長さがINT_MAXよりも長くなる可能性があるため、変数nがオーバーフローする可能性があります。 これにより、未定義のプログラムの動作が発生します。 オーバーフローが単に2147483647を-2147483648に変えるとは思わないでください。 これは正確にあいまいな動作であり、結果を予測することは不可能です。 符号変数のオーバーフローがプログラムの予期しない変更につながると信じていない人のために、私の記事「 未定義の振る舞いはあなたが思っているより近い 」に精通することを提案します



そのため、変数nがオーバーフローする可能性があることを見つける必要があります。 これ以上簡単なことはありません。 PVS-Studioを実行し、警告を取得します。



V127 32ビット「n」変数のオーバーフローは、memsizeタイプのループカウンターを使用する長いサイクル内で発生する可能性があります。 mfcapplication2dlg.cpp 190



変数nのタイプをsize_tに変更すると、エラー、およびアナライザーからのメッセージが消えます。



また、特定する必要があるコードの別の例を示します。

 int i = 0; for (iter = c.begin(); iter != c.end(); iter++, i++) { /* ... */ }
      
      





PVS-Studioを起動すると、警告V127が再度表示されます。



V127 memsizeタイプのループカウンターを使用する長いサイクル内で、32ビットの「i」変数のオーバーフローが発生する可能性があります。 mfcapplication2dlg.cpp 201



StackOverflowのトピックでは、コードベースが巨大な場合の対処方法と、そのようなエラーをすべて見つける方法についての問題も提起されています。



ご覧のとおり、これらのエラーはPVS-Studio静的コードアナライザーを使用して検出できます。 そして、これが大規模プロジェクトに対処する唯一の方法です。 PVS-Studioには、多数の診断メッセージを処理するための便利なインターフェイスが用意されていることにも注意してください。 メッセージをインタラクティブにフィルタリングしたり、falseとしてマークしたりできます。 ただし、PVS-Studioの機能の説明は、この記事の範囲外です。 ツールに興味がある人のために、私は以下の資料に精通することを提案します:

また、900万行のコードを含む大規模プロジェクトを64ビットプラットフォームに移植した経験があることにも注意してください。 そして、PVS-Studioはこのプロジェクトの作業に優れていることが証明されました。



StackOverflow Webサイトの次のトピックに進みましょう。「 Klocwork(または他のツール)は型、typedef、#defineディレクティブを認識できますか?



私が理解しているように、32ビットループカウンターを使用して編成されたすべてのループを検索するための適切なツールを見つけるタスクを自分で設定しました。 つまり つまり、 int型が使用される場所です。



このタスクは、前のタスクとわずかに異なります。 しかし、そのようなサイクルは本当に求められるべきです。 結局、 int変数を使用すると、巨大な配列などを処理することは不可能です。



男は問題の解決に不当に近づいた。 これは彼のせいではありません。 彼はPVS-Studioの存在を知らないだけです。 今、あなたは私がそう言う理由を理解するでしょう。



そこで彼は探すことを計画しています:

 for (int i = 0; i < 10; i++) // ...
      
      





これはひどいです。 エラーにつながる可能性があるかどうかを理解するには、信じられないほどのサイクル数を調べる必要があります。 これは大きな仕事であり、注意を失うことなく実行することはほとんどできません。 多くの場合、多くの危険な場所が見逃されます。



たとえば、 intintptr_tに置き換えて、行内のすべてのサイクルを編集することも不適切なオプションです。 これは多くの作業とコードの変更です。



PVS-Studioアナライザーが役立ちます。 彼は上記のサイクルを見つけることができません。 求める必要がないからです。 単にエラーの余地はありません。 ループは10回の反復を実行します。 そして、そこにオーバーフローはあり得ません。 そのため、プログラマがこのコードに時間を無駄にすることはありません。



ただし、アナライザーは次のサイクルを指摘します。

 void Foo(std::vector<float> &v) { for (int i = 0; i < v.size(); i++) v[i] = 1.0; }
      
      





アナライザーはすぐに2つの警告を表示します。 最初の式では、式で32ビット型がmemsize型と比較されることを警告しています:



V104算術式での 'i'からmemsizeタイプへの暗黙的な変換:i <v.size()mfcapplication2dlg.cpp 210



実際、変数iのタイプは長いループを編成するのには適していません。



2番目の警告は、32ビット変数をインデックスとして使用するのは奇妙だと言っています。 配列が大きい場合、コードに誤りがあります。



V108インデックスタイプが正しくありません:v [memsize-typeではありません]。 代わりにmemsizeタイプを使用してください。 mfcapplication2dlg.cpp 211



正しいコードは次のようになります。

 void Foo(std::vector<float> &v) { for (std::vector<float>::size_type i = 0; i < v.size(); i++) v[i] = 1.0; }
      
      





コードが長くていため、 autoキーワードを使用する誘惑がありますが、これを行うことはできません-この方法で変更されたコードは再び間違っています:

 for (auto i = 0; i < v.size(); i++) v[i] = 1.0;
      
      





定数0はint型であるため、変数iint型になります。 そして、私たちは始めたところに戻りました。 ところで、C ++言語標準の新機能に関しては、記事「 C ++ 11 and 64-bit errors 」をご覧になることをお勧めします。



私はあなたが妥協して完璧ではないが正しいコードを書くことができると思います:

 for (size_t i = 0; i < v.size(); i++) v[i] = 1.0;
      
      





ご注意 もちろん、イテレータまたはfill()アルゴリズムはさらに正確です。 しかし、古いプログラムでの32ビット変数のオーバーフローの検索について話している。 したがって、コードを修正するためのそのようなオプションは考慮していません。 これは完全に異なるトピックです。



アナライザーは十分にスマートであり、プログラマーに迷惑をかけないようにすることを強調したいと思います。 たとえば、小さな配列が処理されていることが確認された場合、警告は発行されません。

 void Foo(int n) { float A[100]; for (int i = 0; i < n; i++) A[i] = 1.0; }
      
      





おわりに



PVS-Studioアナライザーは、64ビットエラーの検索におけるリーダーです。 当初は、プログラマーがプログラムを64ビットシステムに移植するためだけに作成されました。 当時はViva64とも呼ばれていました。 その後、汎用アナライザーになりましたが、既存の64ビット診断はどこにも消えず、誰もがあなたを助ける準備ができています。



こちらからデモをダウンロードしてください



64ビットプログラムの開発に関する詳細をお読みください。



All Articles