bug () 很好 style 自动 pub 为我 ret
最近在项目中 debug 各种 access violation 的,其中这个问题比较有代表性,并且能够被规范的代码标准解决。
问题可以总结为以下的代码:
- class TestString {
- public: TestString(const char * input) : m_value(input) {}
- TestString(const TestString & input) : m_value(input.m_value) {}
- operator const char * () const {
- return m_value.c_str();
- }
- string m_value;
- };
- void main() {
- TestString testStr("StringA");
- const char * stringB = "StringB";
- const char * result = true ? testStr: stringB;
- // Will result point to "StringA"?
- assert(result == testStr.m_value.c_str());
- }
以上代码里面,你可以定认为 `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++ 构造函数和编译器自动生成代码的陷阱
来源: http://www.bubuko.com/infodetail-2280362.html