C++構造函數和編譯器自動生成代碼的陷阱
阿新 • • 發佈:2017-08-27
log bug () 很好 style 自動 pub 為我 ret
最近在項目中debug各種access violation的,其中這個問題比較有代表性,並且能夠被規範的代碼標準解決。
問題可以總結為以下的代碼:
1 class TestString 2 { 3 public: 4 TestString(const char* input) : m_value(input) {} 5 TestString(const TestString& input) : m_value(input.m_value) {} 6 operator const char*() const { return m_value.c_str(); }7 string m_value; 8 }; 9 10 void main() 11 { 12 TestString testStr("StringA"); 13 const char* stringB = "StringB"; 14 const char* result = true ? testStr : stringB; 15 // Will result point to "StringA"? 16 assert(result == testStr.m_value.c_str()); 17 }
以上代碼裏面,你可以定認為`result`會指向`testStr.m_value.c_str()`吧,因為我們重載了`operator const char*()`,
其實不然,如果你運行以上的代碼,你會發現`result`最後指向的是一個“隨機”的內存地址。
我在排除了周圍沒有任何問題之後,打開了匯編代碼的瀏覽器:
const char* result = true ? testStr : stringB; 00CE9585 mov eax,1 00CE958A test eax,eax 00CE958C je main+0B0h (0CE95D0h) 00CE958E lea ecx,[testStr] 00CE9591 push ecx 00CE9592 lea ecx,[ebp-138h] 00CE9598 call TestString::TestString (0CE1659h) ... 00CE95DA call TestString::TestString (0CE14DDh) ... 00CE9625 call TestString::operator char const * (0CE105Ah) ... 00CE964C call TestString::~TestString (0CE14A1h) ... 00CE9670 call TestString::~TestString (0CE14A1h)
在這裏,你能夠清楚的看到編譯器把`testStr`和`stringB`都準換成了類型為`TestString`的臨時對象,然後調用`operator const char*()`來吧結果轉換為`const char*`,不過之後這2個臨時對象都被自動銷毀了,所以你得到的結果也成為了Dangling pointer。
至於解決方案,你估計可以想到這樣改:
const char* result = true ? testStr.m_value.c_str() : stringB; 0008504C mov eax,1 00085051 test eax,eax 00085053 je main+55h (085065h) 00085055 lea ecx,[testStr] 00085058 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::c_str (081370h) 0008505D mov dword ptr [ebp-104h],eax 00085063 jmp main+5Eh (08506Eh) 00085065 mov ecx,dword ptr [stringB] 00085068 mov dword ptr [ebp-104h],ecx 0008506E mov edx,dword ptr [ebp-104h] 00085074 mov dword ptr [result],edx
通過顯式的調用`test.m_value().c_str()`來避免編譯器生成預期之外的類型轉化。
不過記得我在本文開始說的,這個問題可以通過很好的代碼規範來避免,這裏我們需要用到的方法是`explicit`。通過把帶一個參數的構造函數定義為`explicit`,我們可以避免編譯器對被標記的構造函數的隱性調用。
所以這裏我所建議的fix是,這樣定義你的TestString:
class TestString { public: explicit TestString(const char* input) : m_value(input) {} explicit TestString(const TestString& input) : m_value(input.m_value) {} operator const char*() const { return m_value.c_str(); } string m_value; };
然後我們來看看編譯器生成的新代碼:
const char* result = true ? testStr : stringB; 00F23C3B mov eax,1 00F23C40 test eax,eax 00F23C42 je main+74h (0F23C54h) 00F23C44 lea ecx,[testStr] 00F23C47 call TestString::operator char const * (0F21604h) 00F23C4C mov dword ptr [ebp-110h],eax 00F23C52 jmp main+7Dh (0F23C5Dh) 00F23C54 mov ecx,dword ptr [stringB] 00F23C57 mov dword ptr [ebp-110h],ecx 00F23C5D mov edx,dword ptr [ebp-110h] 00F23C63 mov dword ptr [result],edx
case close. :)
C++構造函數和編譯器自動生成代碼的陷阱