2015幎の64ビットコヌド起こりうる問題の蚺断の新機胜



64ビット゚ラヌは、時限爆匟に䌌おいるため、怜出するのが十分に困難です。すぐに感じられない堎合がありたす。 PVS-Studio静的アナラむザヌを䜿甚するず、このような゚ラヌを簡単に芋぀けお修正できたす。 ただし、この方向でいく぀かの手順が実行されたした。最近、64ビット蚺断がより慎重に改蚂され、その結果、重芁床レベルによる分垃が倉曎されたした。 この蚘事では、これらの倉曎ず、これがツヌルの動䜜ず゚ラヌの怜玢にどのように圱響したかに぀いお説明したす。 実際のアプリケヌションからの64ビット゚ラヌの䟋が含たれおいたす。



に぀いおの蚘事は䜕ですか



たず、コンテンツの詳现を玹介したいず思いたす。 この蚘事では、次のトピックに぀いお説明したす。
  1. 64ビット゚ラヌの怜玢に圱響するPVS-Studioアナラむザヌの倉曎。
  2. PVS-Studioアナラむザヌによっお怜出された64ビットの第1レベル゚ラヌのレビュヌ、およびそれらに関する簡単なコメント。
  3. PVS-StudioずMicrosoft Visual Studio 2013を䜿甚しお最も重芁な゚ラヌを芋぀ける際の有効性の比范。


最初の段萜はそれ自身を物語っおいたす64ビット゚ラヌの分析に関するPVS-Studioの䞻な倉曎ず、それらがツヌルの動䜜にどのように圱響するかを考慮したす。



2番目のメむンセクションは、実際のプロゞェクトで芋぀かった64ビット゚ラヌに専念したす。 プロゞェクトのコヌドスニペットに加えお、コメントも提䟛されるため、自分で䜕か新しいこずを孊ぶこずができたす。



3番目のセクションでは、これらの゚ラヌの怜玢の有効性をPVS-Studio静的アナラむザヌおよびMicrosoft Visual Studio 2013環境のツヌルず比范したす。さらに、Visual Studioの堎合、コンパむラヌず静的アナラむザヌの䞡方を䜿甚しお゚ラヌを怜玢したした。



いく぀かの゚ラヌのみがここに曞き出されるこずを忘れないでください。 実際のプロゞェクトでは、それらは確実にはるかに倧きく、より倚様になりたす。 蚘事の最埌に、64ビット゚ラヌの䞖界をより完党に玹介するリンクが提䟛されおいたす。



64ビット゚ラヌに関連するPVS-Studioの倉曎



少し前たでは、64ビット蚺断を泚意深く調べ、重芁床レベルに応じおより正確に配垃したした。



64ビット゚ラヌの分垃は次のようになりたす。



レベル1。アプリケヌションに損害を䞎える重倧な゚ラヌ。 䟋ずしお、32ビットのint倉数にポむンタヌを栌玍したす。 64ビットアプリケヌションを開発しおいる堎合は、必ず最初のレベルの譊告を調べお修正する必芁がありたす。



レベル2。通垞、倧量のデヌタを凊理するアプリケヌションでのみ珟れる゚ラヌ。 䟋は、巚倧な配列にむンデックスを付けるための「int」型倉数の䜿甚です。



レベル3。その他すべお。 原則ずしお、これらの譊告は関係ありたせん。 ただし、アプリケヌションによっおは、特定の蚺断が非垞に圹立぀堎合がありたす。



したがっお、第1レベルの64ビット゚ラヌでフィルタリングするず、゚ラヌの可胜性が高いコヌドセクションを瀺すメッセヌゞのリストが衚瀺されたす。 これらの譊告を過小評䟡しないでください。64ビット゚ラヌの結果は倧きく異なる可胜性がありたすが、明らかに䞍快であり、しばしば予期しないものです。 私たちが話すのは圌らに぀いおです。



PVS-Studioなどのツヌルなしでこのような゚ラヌを怜出するこずはどれほど難しいでしょうか。この蚘事を読むず理解できるず思いたす。



64ビット゚ラヌ分析



デヌタ型が正しく䜿甚されるように泚意する必芁がありたす。 おそらく、これから始めたす。



LRESULT CSaveDlg::OnGraphNotify(WPARAM wParam, LPARAM lParam) { LONG evCode, evParam1, evParam2; while (pME && SUCCEEDED(pME->GetEvent(&evCode, (LONG_PTR*)&evParam1, (LONG_PTR*)&evParam2, 0))) { .... } return 0; }
      
      





アナラむザヌの譊告

゚ラヌの本質を理解するには、倉数「evParam1」、「evParam2」のタむプ、および「GetEvent」メ゜ッドの宣蚀を調べる必芁がありたす。



 virtual HRESULT STDMETHODCALLTYPE GetEvent( /* [out] */ __RPC__out long *lEventCode, /* [out] */ __RPC__out LONG_PTR *lParam1, /* [out] */ __RPC__out LONG_PTR *lParam2, /* [in] */ long msTimeout) = 0;
      
      





アナラむザヌメッセヌゞからわかるように、危険な明瀺的な型倉換が実行されたす。 実際には、タむプ 'LONG_PTR'は ' memsizeタむプ 'であり、Win32 ILP32デヌタモデル  では 32ビット、Win64アヌキテクチャ LLP64デヌタモデル  では 64ビットのサむズを持ちたす。 同時に、「LONG」タむプは䞡方のアヌキテクチャで32ビットです。 前述の型は64ビットアヌキテクチャ䞊でサむズが異なるため、これらのポむンタヌによっお参照されるオブゞェクトの誀った操䜜が可胜です。



危険な型倉換のトピックを続けたす。 次のコヌドを芋おください。



 BOOL WINAPI TrackPopupMenu( _In_ HMENU hMenu, _In_ UINT uFlags, _In_ int x, _In_ int y, _In_ int nReserved, _In_ HWND hWnd, _In_opt_ const RECT *prcRect ); struct JABBER_LIST_ITEM { .... }; INT_PTR CJabberDlgGcJoin::DlgProc(....) { .... int res = TrackPopupMenu( hMenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, NULL); .... if (res) { JABBER_LIST_ITEM *item = (JABBER_LIST_ITEM *)res; .... } .... }
      
      





アナラむザヌの譊告 V204 32ビット敎数型からポむンタヌ型ぞの明瀺的な倉換JABBER_LIST_ITEM *res test.cpp 57



たず、このコヌドで䜿甚されおいる「TrackPopupMenu」関数を芋おみるずいいでしょう。 ナヌザヌが遞択したメニュヌ項目の識別子を返したす。゚ラヌの堎合や遞択肢がなかった堎合はnull倀を返したす。 これらの目的で「BOOL」ず入力するず、明らかに倱敗したすが、どうすればよいですか。



この関数の実行結果は、コヌドからわかるように、倉数「res」に入力されたす。 それでも䞀郚の芁玠がナヌザヌによっお遞択された堎合res= 0、この倉数は構造䜓ぞのポむンタヌにキャストされたす。 興味深いアプロヌチですが、この蚘事では64ビット゚ラヌに぀いお説明しおいるので、このコヌドが32ビットおよび64ビットアヌキテクチャでどのように実行されるか、そしお䜕が問題になるのか考えおみたしょう。



キャッチは、32ビットアヌキテクチャでは、「ポむンタヌ」ず「ブヌル」の型が同じサむズであるため、このような倉換は受け入れ可胜で実行可胜であるずいうこずです。 しかし、このレヌキは64ビットアヌキテクチャで感じられたす。 Win64アプリケヌションでは、䞊蚘のタむプのサむズは異なりたすそれぞれ64ビットず32ビット。 朜圚的な゚ラヌは、ポむンタヌの最䞊䜍ビットが倱われる可胜性があるこずです。



レビュヌを継続したす。 コヌドスニペット



 static int hash_void_ptr(void *ptr) { int hash; int i; hash = 0; for (i = 0; i < (int)sizeof(ptr) * 8 / TABLE_BITS; i++) { hash ^= (unsigned long)ptr >> i * 8; hash += i * 17; hash &= TABLE_MASK; } return hash; }
      
      





アナラむザヌの譊告 V205ポむンタヌ型から32ビット敎数型ぞの明瀺的な倉換笊号なしlongptr test.cpp 76



この関数で「void *」型の倉数を「unsigned long」型にキャストする問題が䜕であるかを芋おみたしょう。 既に述べたように、これらのタむプはLLP64デヌタモデルでサむズが異なり、「void *」タむプは64ビットを占有し、「unsigned long」は32ビットです。 この結果、倉数「ptr」に含たれる䞊䜍ビットは切り捚おられたす倱われたす。 この結果、倉数 'i'の倀は反埩が進むに぀れお増加したす。反埩が進むに぀れおビット単䜍で右にシフトするず、増加するビット数に圱響したす。 倉数 'ptr'のサむズは切り捚おられおいるため、ある反埩から、それに含たれるすべおのビットが0で埋められたす。䞊蚘のすべおの結果-Win64ベヌスのアプリケヌションでは、 'hash'は正しくコンパむルされたせん。 「ハッシュ」にれロを入力するず、 衝突が発生する可胜性がありたす。぀たり、異なる入力デヌタこの堎合はポむンタヌに察しお同じハッシュを取埗したす。 その結果、これは非効率的なプログラム操䜜に぀ながる可胜性がありたす。 'memsize type'ぞのキャストが実行された堎合、切り捚おは行われず、シフトしたがっおハッシュのコンパむルが正しく実行されたす。



次のコヌドを芋おみたしょう。



 class CValueList : public CListCtrl { .... public: BOOL SortItems(_In_ PFNLVCOMPARE pfnCompare, _In_ DWORD_PTR dwData); .... }; void CLastValuesView::OnListViewColumnClick(....) { .... m_wndListCtrl.SortItems(CompareItems, (DWORD)this); .... }
      
      





アナラむザヌの譊告 V220型キャストの䞍審なシヌケンスmemsize-> 32ビット敎数-> memsize。 キャストされる倀 'this'。 test.cpp 87



Diagnostics V220は、二重の危険なデヌタ倉換を通知したす。 最初に、「memsize type」の倉数は32ビット倀になり、すぐに「memsize type」に戻りたす。 実際、これは最䞊䜍ビットの倀が「カットオフ」されるこずを意味したす。 これはほずんどの堎合間違いです。



危険な倉換のトピックに぀いおは、匕き続き拡匵したす。



 #define YAHOO_LOGINID "yahoo_id" DWORD_PTR __cdecl CYahooProto::GetCaps(int type, HANDLE /*hContact*/) { int ret = 0; switch (type) { .... case PFLAG_UNIQUEIDSETTING: ret = (DWORD_PTR)YAHOO_LOGINID; break; .... } return ret; }
      
      





アナラむザヌの譊告 V221型キャストの疑わしいシヌケンスポむンタヌ-> memsize- > 32ビット敎数。 キャストされる倀 '"yahoo_id"'。 test.cpp 99



私は、倉換の各䟋でたすたす倚くなる傟向に気づきたした。 䞊蚘のすべおず同じ理由で、最倧3぀あり、そのうち2぀は危険です。 「YAHOO_LOGINID」は文字列リテラルであるため、そのタむプは「const char *」であり、64ビットアヌキテクチャでは「DWORD_PTR」タむプず同じサむズであるため、明瀺的な倉換は正しいです。 しかし、その埌、悪いこずが始たりたす。 型「DWORD_PTR」は、暗黙的に32ビット敎数にキャストされたす。 しかし、それだけではありたせん。 関数によっお返される結果は 'DWORD_PTR'タむプであるため、別の暗黙的な倉換が実行され、今回は 'memsizeタむプ'に戻りたす。 明らかに、この堎合、戻り倀の䜿甚はあなた自身の責任です。



Visual Studio 2013コンパむラが次のような譊告を発行したこずに泚意しおください。



è­Šå‘ŠC4244「=」「DWORD_PTR」から「int」ぞの倉換、デヌタの損倱の可胜性



実際の質問はここで可胜になりたすVisual Studio 2013によっお発行された譊告がこの䟋でのみ衚瀺されるのはなぜですか 質問は公平ですが、忍耐匷く、これは以䞋に曞かれたす。



それたでの間、゚ラヌの怜蚎を続けおいたす。 クラス階局を含む次のコヌドを怜蚎しおください。



 class CWnd : public CCmdTarget { .... virtual void WinHelp(DWORD_PTR dwData, UINT nCmd = HELP_CONTEXT); .... }; class CFrameWnd : public CWnd { .... }; class CFrameWndEx : public CFrameWnd { .... virtual void WinHelp(DWORD dwData, UINT nCmd = HELP_CONTEXT); .... };
      
      





アナラむザヌの譊告 V301予期しない関数のオヌバヌロヌド動䜜。 掟生クラス「CFrameWndEx」および基本クラス「CWnd」の関数「WinHelpA」の最初の匕数を参照しおください。 test.cpp 122



䟋は、Visual C ++ 2012ラむブラリをチェックするずきにレポヌトから取埗されるずいう点で興味深いですが、ご芧のように、Visual C ++開発者でさえ64ビット゚ラヌを䜜成したす。



この゚ラヌに関する詳现は、 察応する蚘事に蚘茉されおいたす 。 ここでその本質を簡単に説明したかった。 32ビットアヌキテクチャでは、「DWORD」ず「DWORD_PTR」のタむプが同じサむズであるため、このコヌドは正しく凊理されたす。埌継クラスでは、この関数がオヌバヌラむドされ、コヌドが正しく実行されたす。 しかし、萜ずし穎はただここにあり、64ビットアヌキテクチャで自分自身に぀いお知るこずができたす。 この堎合、タむプ「DWORD」ず「DWORD_PTR」のサむズは異なるため、ポリモヌフィズムは砎棄されたす。 2぀の異なる機胜を手元に甚意したすが、これは意図したものに反したす。



そしお最埌の䟋



 void CSymEngine::GetMemInfo(CMemInfo& rMemInfo) { MEMORYSTATUS ms; GlobalMemoryStatus(&ms); _ultot_s(ms.dwMemoryLoad, rMemInfo.m_szMemoryLoad, countof(rMemInfo.m_szMemoryLoad), 10); .... }
      
      





アナラむザヌの譊告 V303関数 'GlobalMemoryStatus'は、Win64システムでは非掚奚です。 'GlobalMemoryStatusEx'関数を䜿甚する方が安党です。 test.cpp 130



原則ずしお、特別な説明は必芁ありたせん。アナラむザヌのメッセヌゞからすべおが明らかです。 「GlobalMemoryStatus」関数は64ビットアヌキテクチャでは正しく動䜜しない可胜性があるため、「GlobalMemoryStatusEx」関数を䜿甚する必芁がありたす。 詳现に぀いおは、MSDN の察応する関数の説明を参照しおください。



ご泚意



䞊蚘の゚ラヌはすべお、最も䞀般的なアプリケヌション゜フトりェアで発生する可胜性があるこずに泚意しおください。 それらが発生するために、プログラムは倧量のメモリで動䜜する必芁はありたせん。 そしお、それがこれらの゚ラヌを怜出する蚺断が最初のレベルに属する理由です。



Visual Studio 2013は䜕を䌝えたすか



コンパむラの譊告



Visual Studio 2013で静的アナラむザヌをチェックした結果に぀いお説明する前に、コンパむラヌの譊告に぀いお詳しく説明したす。 気配りのある読者は、おそらくこのような譊告がテキストで1぀だけ䞎えられおいるこずに気づいたでしょう。 問題は䜕ですか しかし、実際には、64ビット゚ラヌに関連する譊告はもうありたせんでした。 そしお、これは譊告を発行する第3レベルです。



ただし、すべおの譊告を有効にしおEnableAllWarnings、この䟋をコンパむルする䟡倀がありたす。











さらに、非垞に予期せず、譊告がヘッダヌファむルwinnt.hなどに぀ながりたす。 あなたがあたりにも怠zyではなく、この譊告の山でプロゞェクトに関連するものを芋぀けた堎合、あなたはただ䜕か面癜いものを抜出するこずができたす、䟋えば



è­Šå‘ŠC4312 '型キャスト' 'int'から 'JABBER_LIST_ITEM *'ぞの倧きなサむズの倉換



è­Šå‘ŠC4311「型キャスト」「void *」から「unsigned long」ぞのポむンタヌの切り捚お



è­Šå‘ŠC4311「型キャスト」「CLastValuesView * const」から「DWORD」ぞのポむンタヌの切り捚お



è­Šå‘ŠC4263 'void CFrameWndEx :: WinHelpADWORD、UINT'メンバヌ関数は、基本クラスの仮想メンバヌ関数をオヌバヌラむドしたせん



䞀般に、コンパむラはこれらの䟋を含むファむルで10個の譊告を発行したした。 このリストの3぀の譊告のみが64ビット゚ラヌを明確に瀺しおいたすコンパむラ譊告C4311およびC4312。 これらの譊告の䞭には、絞り蟌み型倉換C4244を瀺すものや、仮想関数がオヌバヌラむドされないものC4263がありたす。 これらの譊告は、64ビット゚ラヌも間接的に瀺しおいたす。



その結果、䜕らかの方法で繰り返し繰り返される譊告を陀倖するず、怜蚎䞭の64ビット゚ラヌに関する5぀の譊告が衚瀺されたす。



ご芧のずおり、Visual Studioコンパむラはすべおの64ビット゚ラヌを怜出できたせんでした。 PVS-Studioアナラむザヌが同じファむルで最初のレベルの9぀の゚ラヌを怜出したこずを思い出したす。



「しかし、Visual Studio 2013に組み蟌たれおいる静的アナラむザヌはどうですか」 たぶん、圌はより良くしお、より倚くの間違いを芋぀けたしたか 芋おみたしょう。



Visual Studio 2013に含たれる静的アナラむザヌ



Visual Studio 2013に組み蟌たれた静的アナラむザヌでこれらの䟋をチェックした結果、3぀の譊告がありたした。

しかし、私たちは64ビット゚ラヌを芋おいたすよね このリストからいく぀の゚ラヌが64ビットに適甚されたすか 埌者のみ誀った結果を返す可胜性のある関数を䜿甚。



Visual Studio 2013静的アナラむザヌは、PVS-Studioアナラむザヌによっお怜出された9に察しお1 64ビット゚ラヌを怜出したこずがわかりたした。 印象的ですね。 倧芏暡なプロゞェクトの違いを想像しおください。



そしお、゚ラヌ怜出に関する機胜の芳点から、Visual Studio 2013環境ずVisual Studio 2015環境に組み蟌たれた静的コヌドアナラむザヌは同じであるこずをもう䞀床思い出しおおきたす詳现に぀いおは、 察応する泚で説明したす。



結果は䜕ですか



最も明確なのは、コヌド䟋の怜蚌結果を衚圢匏で反映するこずです。











衚からわかるように、9個の64ビット゚ラヌがPVS-Studioを䜿甚しお怜出され、6個がMicrosoft Visual Studio 2013の䞀般的な手段によっお怜出されたした。おそらく、これはそれほど倧きな違いではないでしょう。 私は同意したせん。 理由を理解したしょう

䞊蚘からわかるように、Microsoft Visual Studio 2013で怜出された64ビット゚ラヌを怜出するには、ある皋床の䜜業を行う必芁がありたす。 それがどれだけ増加するか想像しおみおください、それが本圓の、本圓に倧きなプロゞェクトであるなら。



PVS-Studioはどうですか 数回のマりスクリックで蚺断を実行し、64ビット゚ラヌず必芁な譊告のフィルタリングを蚭定するず、結果が埗られたす。



おわりに



64ビットアヌキテクチャぞのアプリケヌションの移怍には倚くの困難が䌎うこずを瀺すこずができたず思いたす。 この蚘事で説明したような゚ラヌは簡単に発生したすが、芋぀けるのは非垞に困難です。 これに、このような゚ラヌのすべおがMicrosoft Visual Studio 2013によっお怜出されるわけではないずいう事実ず、特定の䜜業を行うために必芁な゚ラヌを芋぀けるために远加したす。 同時に、 PVS-Studio静的アナラむザヌはタスクに察凊し、適切な結果を瀺したした。 さらに、゚ラヌの怜玢ずフィルタリングのプロセスはより簡単で䟿利です。 このようなツヌルを䜿甚しない本圓に倧きなプロゞェクトでは、タむトにする必芁があるこずを認めなければなりたせん。そのため、このような堎合には優れた静的アナラむザヌが必芁になりたす。



64ビットアプリケヌションを開発しおいたすか PVS-Studioトラむアルをダりンロヌドし、プロゞェクトをチェックしお、最初のレベルの64ビットメッセヌゞの数を確認したす。 それでもいく぀か衚瀺される堎合は、それらを修正しお、この䞖界を少し良くしおください。



远加資料



玄束どおり、64ビット゚ラヌのトピックに関する远加資料のリストを瀺したす。



この蚘事は英語です。



この蚘事を英語圏の聎衆ず共有したい堎合は、翻蚳ぞのリンクを䜿甚しおくださいSergey Vasiliev。 2015幎の64ビットコヌド考えられる問題の蚺断の新機胜 。



蚘事を読んで質問がありたすか
倚くの堎合、蚘事には同じ質問が寄せられたす。 ここで回答を集めたした PVS-Studioバヌゞョン2015に関する蚘事の読者からの質問ぞの回答 。 リストをご芧ください。




All Articles