故名思意, 如果一个指针是NULL, (NullPtr == NULL), 则 NullPtr->Method() 会产生异常。
但是根据被调用函数不同, 分为 (1) NullPtr->Virtual_Method() (2) NullPtr->Member_Method() 和
// // 例子 // class AA { public: virtual void SayHello(int dwDummy) { cout << "Hello" << endl; } void SayHello(void) { m_nValue = 0; cout << "Hello" << endl; } private: int m_nValue; }; int main ( ) { SaySomething(); return 0; }
(1)【虚函数】NullPtr->Virtual_Method()
void SaySomething(void) { AA * pAA = NULL; pAA->SayHello(0); // 虚函数. }
DUMP 文件:
FAULTING_IP: ds!SaySomething+2c [f:my project1111dsds.cpp @ 121] 0019d91c 8b10 mov edx,dword ptr [eax] EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff) ExceptionAddress: 0019d91c (ds!SaySomething+0x0000002c) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 00000000 Attempt to read from address 00000000 FOLLOWUP_IP: ds!SaySomething+2c [f:my project1111dsds.cpp @ 121] 0019d91c 8b10 mov edx,dword ptr [eax] STACK_TEXT: 0033fa60 0019d983 00000000 00000000 7ffdb000 ds!SaySomething+0x2c [f:my project1111dsds.cpp @ 121] 0033fb34 0019f887 00000001 00411be8 00411c50 ds!main+0x23 [f:my project1111dsds.cpp @ 128] 0033fb80 0019f75f 0033fb94 76951114 7ffdb000 ds!__tmainCRTStartup+0x117 [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 266] 0033fb88 76951114 7ffdb000 0033fbd4 773bb299 ds!mainCRTStartup+0xf [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 182] 0033fb94 773bb299 7ffdb000 421f5b46 00000000 kernel32!BaseThreadInitThunk+0xe >> 在调用 NullPtr->Virtual_Method() 的函数内产生异常. <<
原因:
pAA->SayHello(0); 008DD915 mov esi,esp 008DD917 push 0 008DD919 mov eax,dword ptr [pAA] 008DD91C mov edx,dword ptr [eax] <------- AV : 因为 指针为0, 0地址的虚函数表指针也是0. 当访问虚函数表的时候, 就会出现异常。 008DD91E mov ecx,dword ptr [pAA] 008DD921 mov eax,dword ptr [edx] 008DD923 call eax
2) 【成员函数】NullPtr->Member_Method()
void SaySomething(void) { AA * pAA = NULL; pAA->SayHello(); // 成员函数. }
DUMP 文件:
FAULTING_IP: ds!AA::SayHello+26 [f:my project1111dsds.cpp @ 109] 01256996 c7400400000000 mov dword ptr [eax+4],0 EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff) ExceptionAddress: 01256996 (ds!AA::SayHello+0x00000026) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000001 Parameter[1]: 00000004 Attempt to write to address 00000004 FOLLOWUP_IP: ds!AA::SayHello+26 [f:my project1111dsds.cpp @ 109] 01256996 c7400400000000 mov dword ptr [eax+4],0 STACK_TEXT: 0016f964 0125693d 0016fb18 00000000 7ffdc000 ds!AA::SayHello+0x26 [f:my project1111dsds.cpp @ 109] 0016fa44 01256a73 00000000 00000000 7ffdc000 ds!SaySomething+0x2d [f:my project1111dsds.cpp @ 122] 0016fb18 01263567 00000001 004c1be8 004c1c50 ds!main+0x23 [f:my project1111dsds.cpp @ 128] 0016fb64 0126343f 0016fb78 76951114 7ffdc000 ds!__tmainCRTStartup+0x117 [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 266] 0016fb6c 76951114 7ffdc000 0016fbb8 773bb299 ds!mainCRTStartup+0xf [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 182] 0016fb78 773bb299 7ffdc000 4239a0bf 00000000 kernel32!BaseThreadInitThunk+0xe >> 在 被调用的 NullPtr->SayHello() 函数内产生异常 <<
原因:
pAA->SayHello();
因为是调用成员函数, 所以是 "静态绑定" 但是在 SayHello() 的内部:
m_nValue = 0; 013F6993 mov eax,dword ptr [this] 013F6996 mov dword ptr [eax+4],0 <--- 异常 : 因为 this 指针来源于 pAA (ECX), 因为 this 指针指向 0, 所以成员变量的地址是非法值 (0~64K), 所以AV.
PS : 如果成员函数不需要访问成员变量, 那么 NULL->Member_Method() 不会有问题.
同理:
class Mgr { ...... ...... AA m_myAAObj; } g_pMgr->m_myAAObj.SayHello();
SayHello() 要访问成员变量, 那么要知道 this 指针 (即该对象在内存中的位置).
ECX = g_pMgr + offsetof (m_myAAObj);
= g_pMgr 的值 + m_myAAObj 在 Mgr 中的偏移.
如果 g_pMgr 指向了 NULL, 那么 ECX 的值只是偏移; 反之, 当发现 ECX 的值是个很小的值, 如果该值是偏移, 说明 g_pMgr 值 NULL。
结论:
NULL->虚函数() 是访问虚函数表时引起AV.
NULL->成员函数() 是该成员函数访问成员变量引起的AV.