设为首页 加入收藏

TOP

C++构造函数和编译器自动生成代码的陷阱(一)
2017-09-19 14:21:00 】 浏览:7872
Tags:构造 函数 编译器 自动生成 代码 陷阱

最近在项目中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 
 test        eax,eax 
 je          main+55h (085065h) 
 lea        ecx,[testStr] 
 call        std::basic_string<char,std::char_traits<char>,std::allocator<char> >::c_str (081370h) 
0008505D  mov        dword ptr [ebp-104h],eax 
 jmp        main+5Eh (08506Eh) 
 mov        ecx,dword ptr [stringB] 
 mov        dword ptr [ebp-104h],ecx 
0008506E  mov        edx,dword ptr [ebp-104h] 
 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&n

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇C# 8.0先睹为快 下一篇shell脚本示例:计算毫秒级、微秒..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目