本文始发于公众号【高性能架构探索】,本公众号致力于分享干货、硬货以及工作上的bug分析,欢迎关注。回复【pdf】免费获取计算机经典书籍
昨天(2022.05.10)晚上19:33分的时候,突然收到一条文章回复。
当时心里突然一慌,难道我上篇文章中的代码,从编辑器中粘贴的时候出错了?第一感觉是不可能,因为每篇文章的代码都会在机器上运行之后,才会放到文章中,所以,感觉出问题的概率不大呀!
不过,既然有读者提出来了,我也得严格验证下,奈何当时在地铁上,没法在电脑上实操验证,所以找了个在线编辑器,写了个简单代码进行验证:
发现没问题,既然有读者反馈这样使用有问题,那么应该不会无缘无故评论的,所以当时在线找了几个人帮忙一起看了下。
这个:
这个:
还有这个:
从大家的反馈来看,还是不确定,都只是感觉有问题。
终于到家了,趁着娃在玩,赶紧拿出电脑,把文章中的代码cv了一下,然后编译、运行,一切正常,这就奇怪了,难道是环境不同导致的结果不同?
Visual VS Gcc
突然想起群里某个同学之前也跟我私聊过,说是文章中的代码他运行后会崩溃。
因为之前在忙工作的事,所以也没有仔细去了解,在咨询了该同学后,其回复是在Windows下执行编译的。
因为本地没有Windows环境,所以找人帮忙在VS上执行了下代码,结果如下:
Windows上报错,那么看来该读者就是Windows环境上执行该段代码了。
深入源码
既然在Windows上和Linux上运行结果不一样,那么就有必要去看看源码实现。
MSVC实现
vector
_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_Myfirst == _Mylast
),然后调用allocator.allocate()函数分配了内存,那么继续看vector
_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
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
const_reference
operator[](size_type __n) const
{ return *(this->_M_impl._M_start + __n); }
与MSVC的operator实现不同的是,gnu的实现中,直接通过首地址+n来进行元素访问(而没有进行size判断),因为内存已经完成分配,所以这样操作是没问题的,这就是Linux环境下执行没问题的原因。
结语
本想跟该读者继续私聊,无奈已经被取关😃。
昨天晚上,我也对读者提出的问题进行了反思,虽然代码在Linux环境上执行没问题,但是仍然会有一部分Windows的读者会认为代码是错的。所以后面的写文过程中,如果涉及到代码,我会尽量考虑编译环境的因素。
好了,本次的文章就到这,我们下期见!
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/218
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!