The Answer
The compiler was, in fact, calculating an incorrect address for some of the class members. The culprit turned out to be a #pragma pack 1
directive hidden in an obscure header file that was #included
in some of the source files.
How I found it
50% persistence, 50% luck, and 10% math skills. :-)
I came across a very simple class with few methods and members, most of which were defined in the header file, including:
typedef int BOOL; // Legacy, makes my skin crawl; don't ask.
BOOL isConnected() { return m_bConnected; };
BOOL m_bConnected;
...and the function returned false
when I knew m_bConnected
was true
:
- The watch window showed the correct value. Programmatic changes to
m_bConnected
were reflected in the window.
- A watch on
&m_bConnected
showed that it began 8 bytes into the class.
- The raw memory window showed the correct value. Programmatic changes to
m_bConnected
were reflected there as well.
- The 4 bytes before
m_bConnected
were all 0
, which I interpreted to be padding.
- Stepping the debugger through the code clearly showed a return value of
false
!
So I checked the disassembly window and found this (comments are mine):
mov eax,dword ptr [this] ; address of the class instance
mov eax,dword ptr [eax+4] ; offset to m_bConnected
In other words, the compiler calculated the offset as 4, when it should have been 8.
Aha!
Interestingly, when I removed the definition of isConnected()
from the header and placed it into my source file, the address was calculated correctly! That convinced me that it was, indeed, a packing problem, and a search of the code base for #pragma pack
quickly identified the culprit, an ancient header file that (legitimately) required alignment on 1-byte boundaries but never reset the packing to the default.
The Fix
The solution was as easy as enclosing the offending header like this:
#pragma pack(push)
// original code here
#pragma pack(pop)
Thanks for your interest. And Sara, if you're reading, I'm about to dance on my desk!