プログラミングするとき、時々物事が壊れます。間違いがあり、プログラムが間違ったアドレスから読み取ろうとした。
多くの場合、これらの例外は次のようなものであることが私にとって際立っています:
Access violation at address 012D37BC in module 'myprog.exe'. Read of address 0000000C.
現在、多くのエラーログが表示され、目立つのは:0000000Cです。これは「特別な」アドレスですか?不正な読み取りによる他のアクセス違反が見られますが、アドレスはランダムに見えるだけですが、これはまったく異なる状況で繰り返し発生します。
00000000
は特別なアドレス(nullポインター)です。 0000000C
は、nullポインターに12のオフセットを追加したときに得られるものです。おそらく、誰かが実際にnullであるポインターを介して以下のような構造のz
メンバーを取得しようとしたためです。
struct Foo {
int w, x, y; // or anything else that takes 12 bytes including padding
// such as: uint64_t w; char x;
// or: void *w; char padding[8];
// all assuming an ordinary 32 bit x86 system
int z;
}
Windowsでは、 最初と最後のページ全体 を逆参照することは違法です。つまり、プロセスメモリの最初または最後の64 KiB(範囲0x00000000
から0x0000ffff
および0xffff0000
から0xffffffff
(32ビットアプリケーションの場合)。
これは、nullポインターまたはインデックスをnull配列に逆参照するという未定義の動作をトラップするためです。また、ページサイズは64 KiBなので、Windowsは最初または最後のページに有効な範囲が割り当てられないようにする必要があります。
これは、任意の値(有効なアドレスを含む)を持つ可能性のある初期化されていないポインターから保護しません。
なぜ0x0C
が0x08
よりも一般的であるように見えるのか(それは本当ですか?わかりません。どのような種類のアプリケーションですか?)、これは仮想メソッドテーブルポインターに関係している可能性があります。これはコメントのようなものです(ワイルドマス推測:)少し大きいので、ここにいきます...仮想メソッドを持つクラスがある場合、独自のフィールドは0x04
によってシフトされます。たとえば、別の仮想クラスから継承するクラスには、次のようなメモリレイアウトがある場合があります。
0x00 - VMT pointer for parent
0x04 - Field 1 in parent
0x08 - VMT pointer for child
0x0C - Field 1 in child
これは一般的なシナリオですか、それとも近いですか?よく分かりません。ただし、64ビットアプリケーションでは、これは0x0C
値にさらに興味深い方向にシフトする可能性があることに注意してください。
0x00 - VMT parent
0x08 - Field 1 parent
0x0C - VMT child
0x14 - Field 2 child
したがって、実際には、アプリケーションでnullポインターのオフセットが大幅に重複する場合が多くあります。これは、子クラスの最初のフィールド、またはその仮想メソッドテーブルポインタになる可能性があります。インスタンスで仮想メソッドを呼び出すたびに必要となるため、null
ポインタで仮想メソッドを呼び出す場合は、そのVMTオフセットでアクセス違反が発生します。この特定の値の普及は、同様の継承パターンを持つクラスを提供するいくつかの一般的なAPI、または特定のインターフェイス(DirectXゲームなどの一部のアプリケーションクラスではかなり可能)と関係がある可能性があります。このような単純な一般的な原因を追跡することは可能かもしれませんが、私はnull逆参照を非常に迅速に行うアプリケーションを取り除く傾向があるので...