技术文被diss了

本文始发于公众号【高性能架构探索】,本公众号致力于分享干货、硬货以及工作上的bug分析,欢迎关注。回复【pdf】免费获取计算机经典书籍

昨天(2022.05.10)晚上19:33分的时候,突然收到一条文章回复。

技术文被diss了

当时心里突然一慌,难道我上篇文章中的代码,从编辑器中粘贴的时候出错了?第一感觉是不可能,因为每篇文章的代码都会在机器上运行之后,才会放到文章中,所以,感觉出问题的概率不大呀!

不过,既然有读者提出来了,我也得严格验证下,奈何当时在地铁上,没法在电脑上实操验证,所以找了个在线编辑器,写了个简单代码进行验证:

技术文被diss了

发现没问题,既然有读者反馈这样使用有问题,那么应该不会无缘无故评论的,所以当时在线找了几个人帮忙一起看了下。

这个:

技术文被diss了

这个:

技术文被diss了

还有这个:

技术文被diss了

从大家的反馈来看,还是不确定,都只是感觉有问题。

终于到家了,趁着娃在玩,赶紧拿出电脑,把文章中的代码cv了一下,然后编译、运行,一切正常,这就奇怪了,难道是环境不同导致的结果不同?

Visual VS Gcc

突然想起群里某个同学之前也跟我私聊过,说是文章中的代码他运行后会崩溃。

技术文被diss了

因为之前在忙工作的事,所以也没有仔细去了解,在咨询了该同学后,其回复是在Windows下执行编译的。

因为本地没有Windows环境,所以找人帮忙在VS上执行了下代码,结果如下:

技术文被diss了

Windows上报错,那么看来该读者就是Windows环境上执行该段代码了。

深入源码

既然在Windows上和Linux上运行结果不一样,那么就有必要去看看源码实现。

MSVC实现

vector::reserve()实现如下:

 _CONSTEXPR20 void reserve(_CRT_GUARDOVERFLOW const size_type _Newcapacity) {
      // ...  
        _Reallocate_exactly(_Newcapacity);
      // ...
    }

从上述代码来看,我们只需要关心_Reallocate_exactly实现即可,于是继续扒该函数实现,如下:

_CONSTEXPR20 void _Reallocate_exactly(const size_type _Newcapacity) {
        // set capacity to _Newcapacity (without geometric growth), provide strong guarantee
        auto& _Al         = _Getal();
        auto& _My_data    = _Mypair._Myval2;
        pointer& _Myfirst = _My_data._Myfirst;
        pointer& _Mylast  = _My_data._Mylast;

        const auto _Size = static_cast<size_type>(_Mylast - _Myfirst);

        const pointer _Newvec = _Al.allocate(_Newcapacity);

        // ...
    }

我们注意到vector::reserve中获取了_Myfirst和_Mylast(注意,此处_Myfirst == _Mylast),然后调用allocator.allocate()函数分配了内存,那么继续看vector::operator[]实现,如下:

_NODISCARD _CONSTEXPR20 const _Ty& operator[](const size_type _Pos) const noexcept /* strengthened */ {
        auto& _My_data = _Mypair._Myval2;
#if _CONTAINER_DEBUG_LEVEL > 0
        _STL_VERIFY(
            _Pos < static_cast<size_type>(_My_data._Mylast - _My_data._Myfirst), "vector subscript out of range");
#endif // _CONTAINER_DEBUG_LEVEL > 0

        return _My_data._Myfirst[_Pos];
    }

我们注意到上面一句_Pos < static_cast<size_type>(_My_data._Mylast - _My_data._Myfirst), "vector subscript out of range"),在前面内容中,有提到_Myfirst == _Mylast,因此该处代码不成立,所以引发了崩溃,这就是Windows下运行结果出错的原因

GNU实现

gnu下vector::reserve()实现如下:

template<typename _Tp, typename _Alloc>
     void
     vector<_Tp, _Alloc>::
     reserve(size_type __n)
     {
       if (__n > this->max_size())
     __throw_length_error(__N("vector::reserve"));
       if (this->capacity() < __n)
     {
       const size_type __old_size = size();
       pointer __tmp = _M_allocate_and_copy(__n,
          _GLIBCXX_MAKE_MOVE_ITERATOR(this->_M_impl._M_start),
          _GLIBCXX_MAKE_MOVE_ITERATOR(this->_M_impl._M_finish));
       std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
             _M_get_Tp_allocator());
       _M_deallocate(this->_M_impl._M_start,
             this->_M_impl._M_end_of_storage
             - this->_M_impl._M_start);
       this->_M_impl._M_start = __tmp;
       this->_M_impl._M_finish = __tmp + __old_size;
       this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
     }
     }

从上述实现可以看出,reserve()函数分配了所需要的内存空间,而且与MSVC一样的是经过reserve之后_M_start == _M_finish,这是因为__old_size为0。

好了,我们继续看下vector::operator[]的实现,如下所示:

const_reference
       operator[](size_type __n) const
       { return *(this->_M_impl._M_start + __n); }

与MSVC的operator实现不同的是,gnu的实现中,直接通过首地址+n来进行元素访问(而没有进行size判断),因为内存已经完成分配,所以这样操作是没问题的,这就是Linux环境下执行没问题的原因

结语

本想跟该读者继续私聊,无奈已经被取关😃。

昨天晚上,我也对读者提出的问题进行了反思,虽然代码在Linux环境上执行没问题,但是仍然会有一部分Windows的读者会认为代码是错的。所以后面的写文过程中,如果涉及到代码,我会尽量考虑编译环境的因素。

好了,本次的文章就到这,我们下期见!

版权归作者所有,如需转载请联系作者

(0)
上一篇 2022年5月5日 上午10:13
下一篇 2022年6月15日 下午3:32

相关推荐