不同编译器对C++标准库的实现都有各自的逻辑和风格,对于字符串类型,gnu c++
template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string {
struct _Alloc_hider : allocator_type
{
_Alloc_hider(pointer __dat, const _Alloc &__a)
: allocator_type(__a), _M_p(__dat) {}
_Alloc_hider(pointer __dat, _Alloc &&__a = _Alloc())
: allocator_type(std::move(__a)), _M_p(__dat) {}
pointer _M_p;
};
_Alloc_hider _M_dataplus;
size_type _M_string_length;
enum { _S_local_capacity = 15 / sizeof(_CharT) };
union {
_CharT _M_local_buf[_S_local_capacity + 1];
size_type _M_allocated_capacity;
};
};
_Alloc_hider _M_dataplus;
size_type _M_string_length;
union {
_CharT _M_local_buf[_S_local_capacity + 1];
size_type _M_allocated_capacity;
};
gnu c++制定string的方法中,常会判断字符串当前保存在不在栈上,具体方式通过:
pointer _M_data() const { return _M_dataplus._M_p; }
const_pointer _M_local_data() const {
#if __cplusplus >= 201103L
return std::pointer_traits<const_pointer>::pointer_to(*_M_local_buf);
#else
return const_pointer(_M_local_buf);
#endif
}
bool _M_is_local() const { return _M_data() == _M_local_data(); }
当string长度小于等于_S_local_capacity 时,将每个字符存在_M_local_buf 指向的栈上。 当string长度大于_S_local_capacity 时,在堆上分配。
string和vector很像,他的capacity方法返回值均为预占空间大小
size_type capacity() const _GLIBCXX_NOEXCEPT {
return _M_is_local() ? size_type(_S_local_capacity) : _M_allocated_capacity;
}
从源码可以看到,首先判断字符串当前保存在不在栈上,是则返回_S_local_capacity ,不是则返回堆上开辟的字符串大小,他们均不包含尾部\0
另外C++11后涉及到移动,可以预料到栈上15个字节是通过数组拷贝,而大于15字节后,将内存移入堆上,堆上的移动,就是指针的交换了。
basic_string(basic_string&& __str) noexcept
: _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator())) {
if (__str._M_is_local()) {
traits_type::copy(_M_local_buf, __str._M_local_buf, _S_local_capacity + 1);
} else {
_M_data(__str._M_data());
_M_capacity(__str._M_allocated_capacity);
}
_M_length(__str.length());
__str._M_data(__str._M_local_data());
__str._M_set_length(0);
}
短字符使用栈内存存放的优势得益于栈的快捷,在性能上略高于操作堆上数据。
之所以单独讨论gun c++,因为它相对于msvc对于短字符的处理是不同的,在msvc中,即使是短字符,也是申请在堆上,除此以外几乎相同。 可使用一下方式,在linux和windows上面分别使用g++或者llvm,对比msvc的输出结果,需要指出一定要编译器支持或者打开C++11
#include <cstdio>
#include <cstdlib>
#include <new>
#include <string>
std::size_t allocated = 0;
void* operator new(size_t sz) {
void* p = std::malloc(sz);
allocated += sz;
return p;
}
void operator delete(void* p) noexcept { return std::free(p); }
auto main() -> int {
allocated = 0;
std::string s("11111111111111111");
std::printf(
"stack space = %zu, heap space = %zu, capacity = %zu, size = %zu\n",
sizeof(s), allocated, s.capacity(), s.size());
std::string ss;
ss.swap(s);
std::printf(
"stack space = %zu, heap space = %zu, capacity = %zu, size = %zu\n",
sizeof(s), allocated, s.capacity(), s.size());
}
参考:知乎
|